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