001/*
002 * (C) Copyright 2015-2017 Nuxeo (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 *     Vladimir Pasquier <vpasquier@nuxeo.com>
019 */
020package org.nuxeo.ecm.automation.core;
021
022import java.util.ArrayList;
023import java.util.List;
024
025import javax.management.InstanceAlreadyExistsException;
026import javax.management.InstanceNotFoundException;
027import javax.management.JMException;
028import javax.management.MBeanRegistrationException;
029import javax.management.MBeanServer;
030import javax.management.MalformedObjectNameException;
031import javax.management.NotCompliantMBeanException;
032import javax.management.ObjectName;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.ecm.automation.AutomationAdmin;
037import org.nuxeo.ecm.automation.AutomationFilter;
038import org.nuxeo.ecm.automation.AutomationService;
039import org.nuxeo.ecm.automation.ChainException;
040import org.nuxeo.ecm.automation.OperationChain;
041import org.nuxeo.ecm.automation.OperationException;
042import org.nuxeo.ecm.automation.OperationParameters;
043import org.nuxeo.ecm.automation.TypeAdapter;
044import org.nuxeo.ecm.automation.context.ContextHelperDescriptor;
045import org.nuxeo.ecm.automation.context.ContextHelperRegistry;
046import org.nuxeo.ecm.automation.context.ContextService;
047import org.nuxeo.ecm.automation.context.ContextServiceImpl;
048import org.nuxeo.ecm.automation.core.events.EventHandler;
049import org.nuxeo.ecm.automation.core.events.EventHandlerRegistry;
050import org.nuxeo.ecm.automation.core.exception.ChainExceptionFilter;
051import org.nuxeo.ecm.automation.core.exception.ChainExceptionImpl;
052import org.nuxeo.ecm.automation.core.impl.ChainTypeImpl;
053import org.nuxeo.ecm.automation.core.impl.OperationServiceImpl;
054import org.nuxeo.ecm.automation.core.trace.TracerFactory;
055import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
056import org.nuxeo.ecm.platform.forms.layout.descriptors.WidgetDescriptor;
057import org.nuxeo.runtime.RuntimeMessage.Level;
058import org.nuxeo.runtime.api.Framework;
059import org.nuxeo.runtime.management.ServerLocator;
060import org.nuxeo.runtime.model.ComponentContext;
061import org.nuxeo.runtime.model.ComponentInstance;
062import org.nuxeo.runtime.model.DefaultComponent;
063
064/**
065 * Nuxeo component that provide an implementation of the {@link AutomationService} and handle extensions registrations.
066 *
067 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
068 * @author <a href="mailto:grenard@nuxeo.com">Guillaume Renard</a>
069 */
070public class AutomationComponent extends DefaultComponent {
071
072    private static final Log log = LogFactory.getLog(AutomationComponent.class);
073
074    public static final String XP_OPERATIONS = "operations";
075
076    public static final String XP_ADAPTERS = "adapters";
077
078    public static final String XP_CHAINS = "chains";
079
080    public static final String XP_EVENT_HANDLERS = "event-handlers";
081
082    public static final String XP_CHAIN_EXCEPTION = "chainException";
083
084    public static final String XP_AUTOMATION_FILTER = "automationFilter";
085
086    public static final String XP_CONTEXT_HELPER = "contextHelpers";
087
088    protected OperationServiceImpl service;
089
090    protected EventHandlerRegistry handlers;
091
092    protected TracerFactory tracerFactory;
093
094    protected ContextHelperRegistry contextHelperRegistry;
095
096    protected ContextService contextService;
097
098    @Override
099    public void activate(ComponentContext context) {
100        service = new OperationServiceImpl();
101        tracerFactory = new TracerFactory();
102        handlers = new EventHandlerRegistry(service);
103        contextHelperRegistry = new ContextHelperRegistry();
104        contextService = new ContextServiceImpl(contextHelperRegistry);
105    }
106
107    protected void bindManagement() throws JMException {
108        ObjectName objectName = new ObjectName("org.nuxeo.automation:name=tracerfactory");
109        MBeanServer mBeanServer = Framework.getService(ServerLocator.class).lookupServer();
110        mBeanServer.registerMBean(tracerFactory, objectName);
111    }
112
113    protected void unBindManagement() throws MalformedObjectNameException, NotCompliantMBeanException,
114            InstanceAlreadyExistsException, MBeanRegistrationException, InstanceNotFoundException {
115        final ObjectName on = new ObjectName("org.nuxeo.automation:name=tracerfactory");
116        final ServerLocator locator = Framework.getService(ServerLocator.class);
117        if (locator != null) {
118            MBeanServer mBeanServer = locator.lookupServer();
119            mBeanServer.unregisterMBean(on);
120        }
121    }
122
123    @Override
124    public void deactivate(ComponentContext context) {
125        service = null;
126        handlers = null;
127        tracerFactory = null;
128    }
129
130    @Override
131    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
132        if (XP_OPERATIONS.equals(extensionPoint)) {
133            OperationContribution opc = (OperationContribution) contribution;
134            List<WidgetDefinition> widgetDefinitionList = new ArrayList<>();
135            if (opc.widgets != null) {
136                for (WidgetDescriptor widgetDescriptor : opc.widgets) {
137                    widgetDefinitionList.add(widgetDescriptor.getWidgetDefinition());
138                }
139            }
140            try {
141                Class<?> type = Class.forName(opc.type);
142                service.putOperation(type, opc.replace, contributor.getName().toString(), widgetDefinitionList);
143            } catch (ClassNotFoundException e) {
144                throw new IllegalArgumentException("Invalid operation class '" + opc.type + "': class not found.");
145            } catch (OperationException e) {
146                throw new RuntimeException(e);
147            }
148        } else if (XP_CHAINS.equals(extensionPoint)) {
149            OperationChainContribution occ = (OperationChainContribution) contribution;
150            try {
151                ChainTypeImpl docChainType = new ChainTypeImpl(service,
152                        occ.toOperationChain(contributor.getContext().getBundle()), occ,
153                        contributor.getName().toString());
154                service.putOperation(docChainType, occ.replace);
155            } catch (OperationException e) {
156                throw new RuntimeException(e);
157            }
158        } else if (XP_CHAIN_EXCEPTION.equals(extensionPoint)) {
159            ChainExceptionDescriptor chainExceptionDescriptor = (ChainExceptionDescriptor) contribution;
160            ChainException chainException = new ChainExceptionImpl(chainExceptionDescriptor);
161            service.putChainException(chainException);
162        } else if (XP_AUTOMATION_FILTER.equals(extensionPoint)) {
163            AutomationFilterDescriptor automationFilterDescriptor = (AutomationFilterDescriptor) contribution;
164            ChainExceptionFilter chainExceptionFilter = new ChainExceptionFilter(automationFilterDescriptor);
165            service.putAutomationFilter(chainExceptionFilter);
166        } else if (XP_ADAPTERS.equals(extensionPoint)) {
167            TypeAdapterContribution tac = (TypeAdapterContribution) contribution;
168            TypeAdapter adapter;
169            try {
170                adapter = tac.clazz.getDeclaredConstructor().newInstance();
171            } catch (ReflectiveOperationException e) {
172                throw new RuntimeException(e);
173            }
174            service.putTypeAdapter(tac.accept, tac.produce, adapter);
175        } else if (XP_EVENT_HANDLERS.equals(extensionPoint)) {
176            EventHandler eh = (EventHandler) contribution;
177            if (eh.isPostCommit()) {
178                handlers.putPostCommitEventHandler(eh);
179            } else {
180                handlers.putEventHandler(eh);
181            }
182        } else if (XP_CONTEXT_HELPER.equals(extensionPoint)) {
183            contextHelperRegistry.addContribution((ContextHelperDescriptor) contribution);
184        }
185    }
186
187    @Override
188    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
189        if (XP_OPERATIONS.equals(extensionPoint)) {
190            try {
191                Class<?> type = Class.forName(((OperationContribution) contribution).type);
192                service.removeOperation(type);
193            } catch (ClassNotFoundException e) {
194                // ignore
195            }
196        } else if (XP_CHAINS.equals(extensionPoint)) {
197            OperationChainContribution occ = (OperationChainContribution) contribution;
198            service.removeOperationChain(occ.getId());
199        } else if (XP_CHAIN_EXCEPTION.equals(extensionPoint)) {
200            ChainExceptionDescriptor chainExceptionDescriptor = (ChainExceptionDescriptor) contribution;
201            ChainException chainException = new ChainExceptionImpl(chainExceptionDescriptor);
202            service.removeExceptionChain(chainException);
203        } else if (XP_AUTOMATION_FILTER.equals(extensionPoint)) {
204            AutomationFilterDescriptor automationFilterDescriptor = (AutomationFilterDescriptor) contribution;
205            AutomationFilter automationFilter = new ChainExceptionFilter(automationFilterDescriptor);
206            service.removeAutomationFilter(automationFilter);
207        } else if (XP_ADAPTERS.equals(extensionPoint)) {
208            TypeAdapterContribution tac = (TypeAdapterContribution) contribution;
209            service.removeTypeAdapter(tac.accept, tac.produce);
210        } else if (XP_EVENT_HANDLERS.equals(extensionPoint)) {
211            EventHandler eh = (EventHandler) contribution;
212            if (eh.isPostCommit()) {
213                handlers.removePostCommitEventHandler(eh);
214            } else {
215                handlers.removeEventHandler(eh);
216            }
217        } else if (XP_CONTEXT_HELPER.equals(extensionPoint)) {
218            contextHelperRegistry.removeContribution((ContextHelperDescriptor) contribution);
219        }
220    }
221
222    @Override
223    public <T> T getAdapter(Class<T> adapter) {
224        if (adapter == AutomationService.class || adapter == AutomationAdmin.class) {
225            return adapter.cast(service);
226        }
227        if (adapter == EventHandlerRegistry.class) {
228            return adapter.cast(handlers);
229        }
230        if (adapter == TracerFactory.class) {
231            return adapter.cast(tracerFactory);
232        }
233        if (adapter == ContextService.class) {
234            return adapter.cast(contextService);
235        }
236        return null;
237    }
238
239    @Override
240    public void start(ComponentContext context) {
241        checkOperationChains();
242        if (!tracerFactory.getRecordingState()) {
243            log.info("You can activate automation trace mode to get more informations on automation executions");
244        }
245        try {
246            bindManagement();
247        } catch (JMException e) {
248            throw new RuntimeException("Cannot bind management", e);
249        }
250    }
251
252    /**
253     * Checks operation references in chains
254     *
255     * @since 11.3
256     */
257    protected void checkOperationChains() {
258        List<OperationChain> chains = service.getOperationChains();
259        for (OperationChain chain : chains) {
260            List<OperationParameters> opps = chain.getOperations();
261            for (OperationParameters opp : opps) {
262                if (!service.hasOperation(opp.id())) {
263                    String msg = String.format("Operation chain with id '%s' references unknown operation with id '%s'",
264                            chain.getId(), opp.id());
265                    log.error(msg);
266                    addRuntimeMessage(Level.ERROR, msg);
267                }
268            }
269        }
270    }
271
272    @Override
273    public void stop(ComponentContext context) {
274        service.flushCompiledChains();
275        try {
276            unBindManagement();
277        } catch (JMException e) {
278            throw new RuntimeException("Cannot unbind management", e);
279        }
280    }
281}