/* * (C) Copyright 2012-2014 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * bstefanescu * vpasquier <vpasquier@nuxeo.com> * slacoin <slacoin@nuxeo.com> */ package org.nuxeo.ecm.automation.core; import static org.nuxeo.ecm.automation.core.Constants.T_BOOLEAN; import static org.nuxeo.ecm.automation.core.Constants.T_DATE; import static org.nuxeo.ecm.automation.core.Constants.T_DOCUMENT; import static org.nuxeo.ecm.automation.core.Constants.T_DOCUMENTS; import static org.nuxeo.ecm.automation.core.Constants.T_FLOAT; import static org.nuxeo.ecm.automation.core.Constants.T_INTEGER; import static org.nuxeo.ecm.automation.core.Constants.T_LONG; import static org.nuxeo.ecm.automation.core.Constants.T_PROPERTIES; import static org.nuxeo.ecm.automation.core.Constants.T_RESOURCE; import static org.nuxeo.ecm.automation.core.Constants.T_STRING; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringEscapeUtils; import org.nuxeo.common.utils.StringUtils; import org.nuxeo.common.xmap.annotation.XContent; import org.nuxeo.common.xmap.annotation.XNode; import org.nuxeo.common.xmap.annotation.XNodeList; import org.nuxeo.common.xmap.annotation.XNodeMap; import org.nuxeo.common.xmap.annotation.XObject; import org.nuxeo.ecm.automation.OperationChain; import org.nuxeo.ecm.automation.OperationDocumentation; import org.nuxeo.ecm.automation.OperationException; import org.nuxeo.ecm.automation.OperationParameters; import org.nuxeo.ecm.automation.core.impl.adapters.helper.TypeAdapterHelper; import org.nuxeo.ecm.automation.core.scripting.Scripting; import org.nuxeo.ecm.automation.core.util.Properties; import org.nuxeo.ecm.core.api.impl.DocumentRefListImpl; import org.nuxeo.ecm.core.schema.utils.DateParser; import org.osgi.framework.Bundle; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ @XObject("chain") public class OperationChainContribution { @XNode("@id") protected String id; @XNode("@replace") protected boolean replace = true; @XNode("description") protected String description; @XNodeList(value = "operation", type = Operation[].class, componentType = Operation.class) protected Operation[] ops = new Operation[0]; @XNode("public") protected boolean isPublic = true; @XNodeList(value = "param", type = OperationDocumentation.Param[].class, componentType = OperationDocumentation.Param.class) protected OperationDocumentation.Param[] params = new OperationDocumentation.Param[0]; /** * @since 7.1 */ @XNodeList(value = "aliases/alias", type = String[].class, componentType = String.class) protected String[] aliases; @XObject("operation") public static class Operation { @XNode("@id") protected String id; @XNodeList(value = "param", type = ArrayList.class, componentType = Param.class) protected List<Param> params = new ArrayList<>(); public String getId() { return id; } public List<Param> getParams() { return params; } } @XObject("param") public static class Param { @XNode("@name") protected String name; // string, boolean, date, integer, float, uid, path, expression, // template, resource @XNode("@type") protected String type = "string"; // why not XNode here? XContent requires to unescape XML entities, see // below @XContent protected String value; // Optional map for properties type values @XNodeMap(value = "property", key = "@key", type = HashMap.class, componentType = String.class, nullByDefault = true) protected Map<String, String> map; public String getName() { return name; } public String getValue() { return value; } public String getType() { return type; } public Map<String, String> getMap() { return map; } } public OperationDocumentation.Param[] getParams() { return params; } public String getId() { return id; } public OperationChain toOperationChain(Bundle bundle) throws OperationException { OperationChain chain = new OperationChain(id); chain.setDescription(description); chain.setPublic(isPublic); chain.setAliases(aliases); for (Operation op : ops) { OperationParameters params = chain.add(op.id); for (Param param : op.params) { param.value = param.value.trim(); // decode XML entities in every case param.value = StringEscapeUtils.unescapeXml(param.value); if (param.value.startsWith("expr:")) { param.value = param.value.substring(5); if (param.value.contains("@{")) { params.set(param.name, Scripting.newTemplate(param.value)); } else { params.set(param.name, Scripting.newExpression(param.value)); } } else { Object val = null; String type = param.type.toLowerCase(); char c = type.charAt(0); switch (c) { case 's': // string if (T_STRING.equals(type)) { val = param.value; } break; case 'p': if (T_PROPERTIES.equals(type)) { if (param.map != null && !param.map.isEmpty()) { val = new Properties(param.map); } else { try { val = new Properties(param.value); } catch (IOException e) { throw new OperationException(e); } } } break; case 'i': if (T_INTEGER.equals(type)) { val = Integer.parseInt(param.value); } break; case 'l': if (T_LONG.equals(type)) { val = Long.valueOf(param.value); } break; case 'b': if (T_BOOLEAN.equals(type)) { val = Boolean.valueOf(param.value); } break; case 'd': if (T_DOCUMENT.equals(type)) { val = TypeAdapterHelper.createDocumentRefOrExpression(param.value); } else if (T_DOCUMENTS.equals(type)) { String[] ar = StringUtils.split(param.value, ',', true); DocumentRefListImpl result = new DocumentRefListImpl(ar.length); for (String ref : ar) { result.add(TypeAdapterHelper.createDocumentRef(ref)); } val = result; } else if (T_DATE.equals(type)) { val = DateParser.parseW3CDateTime(param.value); } break; case 'f': if (T_FLOAT.equals(type)) { val = Double.valueOf(param.value); } break; case 'r': if (T_RESOURCE.equals(type)) { if (param.value.contains(":/")) { // a real URL try { val = new URL(param.value); } catch (MalformedURLException e) { throw new OperationException(e); } } else { // try with class loader val = bundle.getEntry(param.value); } } break; } if (val == null) { val = param.value; } params.set(param.name, val); } } } return chain; } public Operation[] getOps() { return ops; } public String getLabel() { return id; } public String getRequires() { return ""; } public String getCategory() { return Constants.CAT_CHAIN; } public String getSince() { return ""; } public String getDescription() { return description; } public String[] getAliases() { return aliases; } public static OperationChainContribution contribOf(OperationChain chain, boolean replace) { OperationChainContribution contrib = new OperationChainContribution(); contrib.id = chain.getId(); contrib.aliases = chain.getAliases(); contrib.description = "inlined chain of " + contrib.id; contrib.isPublic = false; contrib.params = paramsOf(chain.getChainParameters()); contrib.ops = operationsOf(chain.getOperations()); contrib.replace = replace; return contrib; } public static OperationDocumentation.Param[] paramsOf(Map<String, ?> args) { return args.entrySet().stream().map(entry -> { OperationDocumentation.Param param = new OperationDocumentation.Param(); param.name = entry.getKey(); param.type = entry.getClass().getName(); return param; }).toArray(OperationDocumentation.Param[]::new); } public static Operation[] operationsOf(List<OperationParameters> operations) { return operations.stream().map(params -> { Operation op = new Operation(); op.id = params.id(); params.map().forEach((k,v) -> { Param param = new Param(); param.name = k; param.type = "unknown"; param.value = v == null ? "null" : v.toString(); op.params.add(param); }); return op; }).toArray(Operation[]::new); } }