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 static org.nuxeo.ecm.automation.core.Constants.T_BOOLEAN; 024import static org.nuxeo.ecm.automation.core.Constants.T_DATE; 025import static org.nuxeo.ecm.automation.core.Constants.T_DOCUMENT; 026import static org.nuxeo.ecm.automation.core.Constants.T_DOCUMENTS; 027import static org.nuxeo.ecm.automation.core.Constants.T_FLOAT; 028import static org.nuxeo.ecm.automation.core.Constants.T_INTEGER; 029import static org.nuxeo.ecm.automation.core.Constants.T_LONG; 030import static org.nuxeo.ecm.automation.core.Constants.T_PROPERTIES; 031import static org.nuxeo.ecm.automation.core.Constants.T_RESOURCE; 032import static org.nuxeo.ecm.automation.core.Constants.T_STRING; 033 034import java.io.IOException; 035import java.net.MalformedURLException; 036import java.net.URL; 037import java.util.ArrayList; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Map; 041 042import org.apache.commons.lang.StringEscapeUtils; 043import org.nuxeo.common.utils.StringUtils; 044import org.nuxeo.common.xmap.annotation.XContent; 045import org.nuxeo.common.xmap.annotation.XNode; 046import org.nuxeo.common.xmap.annotation.XNodeList; 047import org.nuxeo.common.xmap.annotation.XNodeMap; 048import org.nuxeo.common.xmap.annotation.XObject; 049import org.nuxeo.ecm.automation.OperationChain; 050import org.nuxeo.ecm.automation.OperationDocumentation; 051import org.nuxeo.ecm.automation.OperationException; 052import org.nuxeo.ecm.automation.OperationParameters; 053import org.nuxeo.ecm.automation.core.impl.adapters.helper.TypeAdapterHelper; 054import org.nuxeo.ecm.automation.core.scripting.Scripting; 055import org.nuxeo.ecm.automation.core.util.Properties; 056import org.nuxeo.ecm.core.api.impl.DocumentRefListImpl; 057import org.nuxeo.ecm.core.schema.utils.DateParser; 058import org.osgi.framework.Bundle; 059 060/** 061 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 062 */ 063@XObject("chain") 064public class OperationChainContribution { 065 066 @XNode("@id") 067 protected String id; 068 069 @XNode("@replace") 070 protected boolean replace = true; 071 072 @XNode("description") 073 protected String description; 074 075 @XNodeList(value = "operation", type = Operation[].class, componentType = Operation.class) 076 protected Operation[] ops = new Operation[0]; 077 078 @XNode("public") 079 protected boolean isPublic = true; 080 081 @XNodeList(value = "param", type = OperationDocumentation.Param[].class, componentType = OperationDocumentation.Param.class) 082 protected OperationDocumentation.Param[] params = new OperationDocumentation.Param[0]; 083 084 /** 085 * @since 7.1 086 */ 087 @XNodeList(value = "aliases/alias", type = String[].class, componentType = String.class) 088 protected String[] aliases; 089 090 @XObject("operation") 091 public static class Operation { 092 @XNode("@id") 093 protected String id; 094 095 @XNodeList(value = "param", type = ArrayList.class, componentType = Param.class) 096 protected List<Param> params = new ArrayList<>(); 097 098 public String getId() { 099 return id; 100 } 101 102 public List<Param> getParams() { 103 return params; 104 } 105 } 106 107 @XObject("param") 108 public static class Param { 109 @XNode("@name") 110 protected String name; 111 112 // string, boolean, date, integer, float, uid, path, expression, 113 // template, resource 114 @XNode("@type") 115 protected String type = "string"; 116 117 // why not XNode here? XContent requires to unescape XML entities, see 118 // below 119 @XContent 120 protected String value; 121 122 // Optional map for properties type values 123 @XNodeMap(value = "property", key = "@key", type = HashMap.class, componentType = String.class, nullByDefault = true) 124 protected Map<String, String> map; 125 126 public String getName() { 127 return name; 128 } 129 130 public String getValue() { 131 return value; 132 } 133 134 public String getType() { 135 return type; 136 } 137 138 public Map<String, String> getMap() { 139 return map; 140 } 141 } 142 143 public OperationDocumentation.Param[] getParams() { 144 return params; 145 } 146 147 public String getId() { 148 return id; 149 } 150 151 public OperationChain toOperationChain(Bundle bundle) throws OperationException { 152 OperationChain chain = new OperationChain(id); 153 chain.setDescription(description); 154 chain.setPublic(isPublic); 155 chain.setAliases(aliases); 156 for (Operation op : ops) { 157 OperationParameters params = chain.add(op.id); 158 for (Param param : op.params) { 159 param.value = param.value.trim(); 160 // decode XML entities in every case 161 param.value = StringEscapeUtils.unescapeXml(param.value); 162 if (param.value.startsWith("expr:")) { 163 String value = param.value.substring(5); 164 if (value.contains("@{")) { 165 params.set(param.name, Scripting.newTemplate(value)); 166 } else { 167 params.set(param.name, Scripting.newExpression(value)); 168 } 169 } else { 170 Object val = null; 171 String type = param.type.toLowerCase(); 172 char c = type.charAt(0); 173 switch (c) { 174 case 's': // string 175 if (T_STRING.equals(type)) { 176 val = param.value; 177 } 178 break; 179 case 'p': 180 if (T_PROPERTIES.equals(type)) { 181 if (param.map != null && !param.map.isEmpty()) { 182 val = new Properties(param.map); 183 } else { 184 try { 185 val = new Properties(param.value); 186 } catch (IOException e) { 187 throw new OperationException(e); 188 } 189 } 190 } 191 break; 192 case 'i': 193 if (T_INTEGER.equals(type)) { 194 val = Integer.parseInt(param.value); 195 } 196 break; 197 case 'l': 198 if (T_LONG.equals(type)) { 199 val = Long.valueOf(param.value); 200 } 201 break; 202 case 'b': 203 if (T_BOOLEAN.equals(type)) { 204 val = Boolean.valueOf(param.value); 205 } 206 break; 207 case 'd': 208 if (T_DOCUMENT.equals(type)) { 209 val = TypeAdapterHelper.createDocumentRefOrExpression(param.value); 210 } else if (T_DOCUMENTS.equals(type)) { 211 String[] ar = StringUtils.split(param.value, ',', true); 212 DocumentRefListImpl result = new DocumentRefListImpl(ar.length); 213 for (String ref : ar) { 214 result.add(TypeAdapterHelper.createDocumentRef(ref)); 215 } 216 val = result; 217 } else if (T_DATE.equals(type)) { 218 val = DateParser.parseW3CDateTime(param.value); 219 } 220 break; 221 case 'f': 222 if (T_FLOAT.equals(type)) { 223 val = Double.valueOf(param.value); 224 } 225 break; 226 case 'r': 227 if (T_RESOURCE.equals(type)) { 228 if (param.value.contains(":/")) { // a real URL 229 try { 230 val = new URL(param.value); 231 } catch (MalformedURLException e) { 232 throw new OperationException(e); 233 } 234 } else { // try with class loader 235 val = bundle.getEntry(param.value); 236 } 237 } 238 break; 239 } 240 if (val == null) { 241 val = param.value; 242 } 243 params.set(param.name, val); 244 } 245 } 246 } 247 return chain; 248 } 249 250 public Operation[] getOps() { 251 return ops; 252 } 253 254 public String getLabel() { 255 return id; 256 } 257 258 public String getRequires() { 259 return ""; 260 } 261 262 public String getCategory() { 263 return Constants.CAT_CHAIN; 264 } 265 266 public String getSince() { 267 return ""; 268 } 269 270 public String getDescription() { 271 return description; 272 } 273 274 public String[] getAliases() { 275 return aliases; 276 } 277 278 public static OperationChainContribution contribOf(OperationChain chain, boolean replace) { 279 OperationChainContribution contrib = new OperationChainContribution(); 280 contrib.id = chain.getId(); 281 contrib.aliases = chain.getAliases(); 282 contrib.description = "inlined chain of " + contrib.id; 283 contrib.isPublic = false; 284 contrib.params = paramsOf(chain.getChainParameters()); 285 contrib.ops = operationsOf(chain.getOperations()); 286 contrib.replace = replace; 287 return contrib; 288 } 289 290 public static OperationDocumentation.Param[] paramsOf(Map<String, ?> args) { 291 return args.entrySet().stream().map(entry -> { 292 OperationDocumentation.Param param = new OperationDocumentation.Param(); 293 param.name = entry.getKey(); 294 param.type = entry.getClass().getName(); 295 return param; 296 }).toArray(OperationDocumentation.Param[]::new); 297 } 298 299 public static Operation[] operationsOf(List<OperationParameters> operations) { 300 return operations.stream().map(params -> { 301 Operation op = new Operation(); 302 op.id = params.id(); 303 params.map().forEach((k,v) -> { 304 Param param = new Param(); 305 param.name = k; 306 param.type = "unknown"; 307 param.value = v == null ? "null" : v.toString(); 308 op.params.add(param); 309 }); 310 return op; 311 }).toArray(Operation[]::new); 312 } 313}