001/*
002 * (C) Copyright 2012-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 *     vpasquier <vpasquier@nuxeo.com>
019 *     slacoin <slacoin@nuxeo.com>
020 */
021package org.nuxeo.ecm.automation.core;
022
023import org.apache.commons.lang.StringEscapeUtils;
024import org.nuxeo.common.utils.StringUtils;
025import org.nuxeo.common.xmap.annotation.XContent;
026import org.nuxeo.common.xmap.annotation.XNode;
027import org.nuxeo.common.xmap.annotation.XNodeList;
028import org.nuxeo.common.xmap.annotation.XNodeMap;
029import org.nuxeo.common.xmap.annotation.XObject;
030import org.nuxeo.ecm.automation.OperationChain;
031import org.nuxeo.ecm.automation.OperationDocumentation;
032import org.nuxeo.ecm.automation.OperationException;
033import org.nuxeo.ecm.automation.OperationParameters;
034import org.nuxeo.ecm.automation.core.impl.adapters.StringToDocRef;
035import org.nuxeo.ecm.automation.core.scripting.Scripting;
036import org.nuxeo.ecm.automation.core.util.Properties;
037import org.nuxeo.ecm.core.api.impl.DocumentRefListImpl;
038import org.nuxeo.ecm.core.schema.utils.DateParser;
039import org.osgi.framework.Bundle;
040
041import java.io.IOException;
042import java.net.MalformedURLException;
043import java.net.URL;
044import java.util.ArrayList;
045import java.util.HashMap;
046import java.util.Map;
047
048import static org.nuxeo.ecm.automation.core.Constants.T_BOOLEAN;
049import static org.nuxeo.ecm.automation.core.Constants.T_DATE;
050import static org.nuxeo.ecm.automation.core.Constants.T_DOCUMENT;
051import static org.nuxeo.ecm.automation.core.Constants.T_DOCUMENTS;
052import static org.nuxeo.ecm.automation.core.Constants.T_FLOAT;
053import static org.nuxeo.ecm.automation.core.Constants.T_INTEGER;
054import static org.nuxeo.ecm.automation.core.Constants.T_LONG;
055import static org.nuxeo.ecm.automation.core.Constants.T_PROPERTIES;
056import static org.nuxeo.ecm.automation.core.Constants.T_RESOURCE;
057import static org.nuxeo.ecm.automation.core.Constants.T_STRING;
058
059/**
060 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
061 */
062@XObject("chain")
063public class OperationChainContribution {
064
065    @XNode("@id")
066    protected String id;
067
068    @XNode("@replace")
069    protected boolean replace = true;
070
071    @XNode("description")
072    protected String description;
073
074    @XNodeList(value = "operation", type = Operation[].class, componentType = Operation.class)
075    protected Operation[] ops = new Operation[0];
076
077    @XNode("public")
078    protected boolean isPublic = true;
079
080    @XNodeList(value = "param", type = OperationDocumentation.Param[].class, componentType = OperationDocumentation.Param.class)
081    protected OperationDocumentation.Param[] params = new OperationDocumentation.Param[0];
082
083    /**
084     * @since 7.1
085     */
086    @XNodeList(value = "aliases/alias", type = String[].class, componentType = String.class)
087    protected String[] aliases;
088
089    @XObject("operation")
090    public static class Operation {
091        @XNode("@id")
092        protected String id;
093
094        @XNodeList(value = "param", type = ArrayList.class, componentType = Param.class)
095        protected ArrayList<Param> params;
096
097        public String getId() {
098            return id;
099        }
100
101        public ArrayList<Param> getParams() {
102            return params;
103        }
104    }
105
106    @XObject("param")
107    public static class Param {
108        @XNode("@name")
109        protected String name;
110
111        // string, boolean, date, integer, float, uid, path, expression,
112        // template, resource
113        @XNode("@type")
114        protected String type = "string";
115
116        // why not XNode here? XContent requires to unescape XML entities, see
117        // below
118        @XContent
119        protected String value;
120
121        // Optional map for properties type values
122        @XNodeMap(value = "property", key = "@key", type = HashMap.class, componentType = String.class, nullByDefault = true)
123        protected Map<String, String> map;
124
125        public String getName() {
126            return name;
127        }
128
129        public String getValue() {
130            return value;
131        }
132
133        public String getType() {
134            return type;
135        }
136
137        public Map<String, String> getMap() {
138            return map;
139        }
140    }
141
142    public OperationDocumentation.Param[] getParams() {
143        return params;
144    }
145
146    public String getId() {
147        return id;
148    }
149
150    public OperationChain toOperationChain(Bundle bundle) throws OperationException {
151        OperationChain chain = new OperationChain(id);
152        chain.setDescription(description);
153        chain.setPublic(isPublic);
154        chain.setAliases(aliases);
155        for (Operation op : ops) {
156            OperationParameters params = chain.add(op.id);
157            for (Param param : op.params) {
158                param.value = param.value.trim();
159                // decode XML entities in every case
160                param.value = StringEscapeUtils.unescapeXml(param.value);
161                if (param.value.startsWith("expr:")) {
162                    param.value = param.value.substring(5);
163                    if (param.value.contains("@{")) {
164                        params.set(param.name, Scripting.newTemplate(param.value));
165                    } else {
166                        params.set(param.name, Scripting.newExpression(param.value));
167                    }
168                } else {
169                    Object val = null;
170                    String type = param.type.toLowerCase();
171                    char c = type.charAt(0);
172                    switch (c) {
173                    case 's': // string
174                        if (T_STRING.equals(type)) {
175                            val = param.value;
176                        }
177                        break;
178                    case 'p':
179                        if (T_PROPERTIES.equals(type)) {
180                            if (param.map != null && !param.map.isEmpty()) {
181                                val = new Properties(param.map);
182                            } else {
183                                try {
184                                    val = new Properties(param.value);
185                                } catch (IOException e) {
186                                    throw new OperationException(e);
187                                }
188                            }
189                        }
190                        break;
191                    case 'i':
192                        if (T_INTEGER.equals(type)) {
193                            val = Integer.parseInt(param.value);
194                        }
195                        break;
196                    case 'l':
197                        if (T_LONG.equals(type)) {
198                            val = Long.valueOf(param.value);
199                        }
200                        break;
201                    case 'b':
202                        if (T_BOOLEAN.equals(type)) {
203                            val = Boolean.valueOf(param.value);
204                        }
205                        break;
206                    case 'd':
207                        if (T_DOCUMENT.equals(type)) {
208                            if (param.value.startsWith(".")) {
209                                val = Scripting.newExpression("Document" + ".resolvePathAsRef(\"" + param.value + "\")");
210                            } else {
211                                val = StringToDocRef.createRef(param.value);
212                            }
213                        } else if (T_DOCUMENTS.equals(type)) {
214                            String[] ar = StringUtils.split(param.value, ',', true);
215                            DocumentRefListImpl result = new DocumentRefListImpl(ar.length);
216                            for (String ref : ar) {
217                                result.add(StringToDocRef.createRef(ref));
218                            }
219                            val = result;
220                        } else if (T_DATE.equals(type)) {
221                            val = DateParser.parseW3CDateTime(param.value);
222                        }
223                        break;
224                    case 'f':
225                        if (T_FLOAT.equals(type)) {
226                            val = Double.valueOf(param.value);
227                        }
228                        break;
229                    case 'r':
230                        if (T_RESOURCE.equals(type)) {
231                            if (param.value.contains(":/")) { // a real URL
232                                try {
233                                    val = new URL(param.value);
234                                } catch (MalformedURLException e) {
235                                    throw new OperationException(e);
236                                }
237                            } else { // try with class loader
238                                val = bundle.getEntry(param.value);
239                            }
240                        }
241                        break;
242                    }
243                    if (val == null) {
244                        val = param.value;
245                    }
246                    params.set(param.name, val);
247                }
248            }
249        }
250        return chain;
251    }
252
253    public Operation[] getOps() {
254        return ops;
255    }
256
257    public String getLabel() {
258        return id;
259    }
260
261    public String getRequires() {
262        return "";
263    }
264
265    public String getCategory() {
266        return Constants.CAT_CHAIN;
267    }
268
269    public String getSince() {
270        return "";
271    }
272
273    public String getDescription() {
274        return description;
275    }
276
277    public String[] getAliases() {
278        return aliases;
279    }
280}