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}