001/*
002 * (C) Copyright 2015 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 *     Thierry Delprat <tdelprat@nuxeo.com>
018 */
019package org.nuxeo.automation.scripting.internals.operation;
020
021import java.net.URL;
022import java.util.Arrays;
023import java.util.Calendar;
024import java.util.List;
025import java.util.Map;
026
027import javax.script.ScriptException;
028
029import org.nuxeo.automation.scripting.internals.ScriptOperationContext;
030import org.nuxeo.ecm.automation.AutomationService;
031import org.nuxeo.ecm.automation.OperationContext;
032import org.nuxeo.ecm.automation.OperationDocumentation;
033import org.nuxeo.ecm.automation.OperationException;
034import org.nuxeo.ecm.automation.core.Constants;
035import org.nuxeo.ecm.automation.core.impl.InvokableMethod;
036import org.nuxeo.ecm.automation.core.impl.OperationTypeImpl;
037import org.nuxeo.ecm.automation.core.scripting.Expression;
038import org.nuxeo.ecm.automation.core.util.BlobList;
039import org.nuxeo.ecm.core.api.Blob;
040import org.nuxeo.ecm.core.api.DocumentModel;
041import org.nuxeo.ecm.core.api.DocumentModelList;
042import org.nuxeo.ecm.core.api.DocumentRef;
043import org.nuxeo.ecm.core.api.DocumentRefList;
044import org.nuxeo.ecm.core.api.NuxeoException;
045
046/**
047 * @since 7.2
048 */
049public class ScriptingOperationTypeImpl extends OperationTypeImpl {
050
051    protected final AutomationService service;
052
053    protected final ScriptingOperationDescriptor desc;
054
055    protected final InvokableMethod[] methods;
056
057    public ScriptingOperationTypeImpl(AutomationService service,
058            ScriptingOperationDescriptor desc) {
059        this.service = service;
060        this.desc = desc;
061        this.inputType = desc.getInputType();
062        this.methods = new InvokableMethod[] { runMethod() };
063    }
064
065
066    @Override
067    public String getContributingComponent() {
068        return null;
069    }
070
071    @Override
072    public OperationDocumentation getDocumentation() {
073        OperationDocumentation doc = new OperationDocumentation(getId());
074        doc.label = getId();
075        doc.category = desc.getCategory();
076        doc.description = desc.getDescription();
077        doc.params = desc.getParams();
078        doc.signature = new String[] { desc.getInputType(), desc.getOutputType() };
079        doc.aliases = desc.getAliases();
080        return doc;
081    }
082
083    @Override
084    public String getId() {
085        return desc.getId();
086    }
087
088    @Override
089    public String[] getAliases() {
090        return desc.getAliases();
091    }
092
093    @Override
094    public List<InvokableMethod> getMethods() {
095        return Arrays.asList(methods);
096    }
097
098    @Override
099    public InvokableMethod[] getMethodsMatchingInput(Class<?> in) {
100        return methods;
101    }
102
103    @Override
104    public AutomationService getService() {
105        return service;
106    }
107
108    @Override
109    public Class<?> getType() {
110        return ScriptingOperationImpl.class;
111    }
112
113    @SuppressWarnings("unchecked")
114    @Override
115    public Object newInstance(OperationContext ctx, Map<String, Object> args) throws OperationException {
116        try {
117            resolveArguments(ctx, args);
118            ScriptOperationContext sctx = new ScriptOperationContext(ctx);
119            return new ScriptingOperationImpl(desc.getScript(), sctx, args);
120        } catch (ScriptException e) {
121            throw new NuxeoException(e);
122        }
123    }
124
125    /**
126     * As
127     * {@link org.nuxeo.ecm.automation.core.impl.OperationTypeImpl#inject(org.nuxeo.ecm.automation.OperationContext, java.util.Map, java.lang.Object)}
128     * is not called in this OperationTypeImpl, we have to inject into arguments all context variables to play the
129     * fallback on chains variables and evaluate script expression like MVEL.
130     *
131     * @since 8.3
132     * @param ctx Automation Context
133     * @param args Operation Parameters
134     */
135    protected void resolveArguments(OperationContext ctx, Map<String, Object> args) {
136        for (String key : args.keySet()) {
137            if (args.get(key) instanceof Expression) {
138                Object obj = ((Expression) args.get(key)).eval(ctx);
139                args.put(key, obj);
140            }
141        }
142        if (ctx.getVars().containsKey(Constants.VAR_RUNTIME_CHAIN)) {
143            args.putAll((Map<String, Object>) ctx.getVars().get(Constants.VAR_RUNTIME_CHAIN));
144        }
145    }
146
147    protected String getParamDocumentationType(Class<?> type, boolean isIterable) {
148        String t;
149        if (DocumentModel.class.isAssignableFrom(type) || DocumentRef.class.isAssignableFrom(type)) {
150            t = isIterable ? Constants.T_DOCUMENTS : Constants.T_DOCUMENT;
151        } else if (DocumentModelList.class.isAssignableFrom(type) || DocumentRefList.class.isAssignableFrom(type)) {
152            t = Constants.T_DOCUMENTS;
153        } else if (BlobList.class.isAssignableFrom(type)) {
154            t = Constants.T_BLOBS;
155        } else if (Blob.class.isAssignableFrom(type)) {
156            t = isIterable ? Constants.T_BLOBS : Constants.T_BLOB;
157        } else if (URL.class.isAssignableFrom(type)) {
158            t = Constants.T_RESOURCE;
159        } else if (Calendar.class.isAssignableFrom(type)) {
160            t = Constants.T_DATE;
161        } else {
162            t = type.getSimpleName().toLowerCase();
163        }
164        return t;
165    }
166
167    protected InvokableMethod runMethod() {
168        try {
169            return new InvokableMethod(this, ScriptingOperationImpl.class.getMethod("run", Object.class));
170        } catch (NoSuchMethodException | SecurityException e) {
171            throw new UnsupportedOperationException("Cannot use reflection for run method", e);
172        }
173    }
174
175}