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}