/* * JBoss, a division of Red Hat * Copyright 2012, Red Hat Middleware, LLC, and individual * contributors as indicated by the @authors tag. See the * copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.gatein.integration.wsrp; import java.io.InputStream; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.servlet.ServletContext; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.ExoContainerContext; import org.exoplatform.container.configuration.ConfigurationManager; import org.exoplatform.container.xml.InitParams; import org.exoplatform.portal.config.DataStorage; import org.exoplatform.portal.mop.EventType; import org.exoplatform.portal.pc.ExoKernelIntegration; import org.exoplatform.portal.pom.config.POMSessionManager; import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator; import org.exoplatform.services.listener.ListenerService; import org.gatein.common.logging.Logger; import org.gatein.common.logging.LoggerFactory; import org.gatein.integration.wsrp.jcr.JCRPersister; import org.gatein.integration.wsrp.plugins.AS7Plugins; import org.gatein.integration.wsrp.structure.MOPConsumerStructureProvider; import org.gatein.integration.wsrp.structure.MOPPortalStructureAccess; import org.gatein.integration.wsrp.structure.PortalStructureAccess; import org.gatein.pc.api.PortletInvoker; import org.gatein.pc.federation.FederatingPortletInvoker; import org.gatein.pc.portlet.PortletInvokerInterceptor; import org.gatein.pc.portlet.aspects.EventPayloadInterceptor; import org.gatein.pc.portlet.container.ContainerPortletInvoker; import org.gatein.pc.portlet.impl.state.StateConverterV0; import org.gatein.pc.portlet.impl.state.StateManagementPolicyService; import org.gatein.pc.portlet.state.StateConverter; import org.gatein.pc.portlet.state.producer.PortletStatePersistenceManager; import org.gatein.pc.portlet.state.producer.ProducerPortletInvoker; import org.gatein.registration.RegistrationManager; import org.gatein.registration.RegistrationPersistenceManager; import org.gatein.registration.impl.RegistrationManagerImpl; import org.gatein.wci.ServletContainer; import org.gatein.wci.ServletContainerFactory; import org.gatein.wci.WebApp; import org.gatein.wci.WebAppEvent; import org.gatein.wci.WebAppLifeCycleEvent; import org.gatein.wci.WebAppListener; import org.gatein.wsrp.WSRPConstants; import org.gatein.wsrp.api.plugins.PluginsAccess; import org.gatein.wsrp.consumer.migration.JCRMigrationService; import org.gatein.wsrp.consumer.migration.MigrationService; import org.gatein.wsrp.consumer.registry.ConsumerRegistry; import org.gatein.wsrp.consumer.registry.JCRConsumerRegistry; import org.gatein.wsrp.consumer.registry.RegisteringPortletInvokerResolver; import org.gatein.wsrp.payload.WSRPEventPayloadInterceptor; import org.gatein.wsrp.producer.ProducerHolder; import org.gatein.wsrp.producer.WSRPProducer; import org.gatein.wsrp.producer.config.JCRProducerConfigurationService; import org.gatein.wsrp.producer.config.ProducerConfigurationService; import org.gatein.wsrp.producer.invoker.RegistrationCheckingPortletInvoker; import org.gatein.wsrp.producer.state.JCRPortletStatePersistenceManager; import org.gatein.wsrp.registration.JCRRegistrationPersistenceManager; import org.picocontainer.Startable; /** * @author <a href="mailto:chris.laprun@jboss.com">Chris Laprun</a> * @version $Revision$ */ public class WSRPServiceIntegration implements Startable, WebAppListener { private static final Logger log = LoggerFactory.getLogger(WSRPServiceIntegration.class); private static final String DEFAULT_PRODUCER_CONFIG_LOCATION = "classpath:/conf/wsrp-producer-config.xml"; private static final String DEFAULT_CONSUMERS_CONFIG_LOCATION = "classpath:/conf/wsrp-consumers-config.xml"; private static final String PRODUCER_CONFIG_LOCATION = "producerConfigLocation"; private static final String CONSUMERS_CONFIG_LOCATION = "consumersConfigLocation"; public static final String CONSUMERS_INIT_DELAY = "consumersInitDelay"; public static final int DEFAULT_DELAY = 2; public static final String FILE = "file://"; private InputStream producerConfigurationIS; private String producerConfigLocation; private WSRPProducer producer; private InputStream consumersConfigurationIS; private String consumersConfigLocation; private JCRConsumerRegistry consumerRegistry; private ExoContainer container; private final ExoKernelIntegration exoKernelIntegration; private static final String WSRP_ADMIN_GUI_CONTEXT_PATH = "/wsrp-admin-gui"; private int consumersInitDelay; /** * Indicates whether the integration has been initialized or not. This is required because we need to perform the * initial setup only once, and this component is created once for each context/portal. * We could have the option of moving this component to a higher level, so that it's created only once, but we * need access to each of the contexts/portals, as we need to set the consumer's registry to each of them. * So, we still get created N times, but we initialize only once. */ private final boolean initialized; public WSRPServiceIntegration(ExoContainerContext context, InitParams params, ConfigurationManager configurationManager, ExoKernelIntegration pc, NodeHierarchyCreator nhc, AS7Plugins plugins) throws Exception { // IMPORTANT: even though NodeHierarchyCreator is not used anywhere in the code, it's still needed for pico // to properly make sure that this service is started after the PC one. Yes, Pico is crap. :/ ConsumerRegistry existingRegistry = ExoContainerContext.getTopContainer().getComponentInstanceOfType(ConsumerRegistry.class); container = context.getContainer(); exoKernelIntegration = pc; // if we have already an existing consumer registry, then we bypass the init // as we are going to reuse it on the start() method initialized = (null != existingRegistry); if (!initialized) { if (params != null) { producerConfigLocation = computePath(params.getValueParam(PRODUCER_CONFIG_LOCATION).getValue()); consumersConfigLocation = computePath(params.getValueParam(CONSUMERS_CONFIG_LOCATION).getValue()); String delayString = params.getValueParam(CONSUMERS_INIT_DELAY).getValue(); try { consumersInitDelay = Integer.parseInt(delayString); } catch (NumberFormatException e) { consumersInitDelay = DEFAULT_DELAY; } } else { throw new IllegalArgumentException("Improperly configured service: missing values for " + PRODUCER_CONFIG_LOCATION + "and " + CONSUMERS_CONFIG_LOCATION); } try { producerConfigurationIS = configurationManager.getInputStream(producerConfigLocation); } catch (Exception e) { producerConfigLocation = DEFAULT_PRODUCER_CONFIG_LOCATION; producerConfigurationIS = configurationManager.getInputStream(DEFAULT_PRODUCER_CONFIG_LOCATION); } try { consumersConfigurationIS = configurationManager.getInputStream(consumersConfigLocation); } catch (Exception e) { consumersConfigLocation = DEFAULT_CONSUMERS_CONFIG_LOCATION; consumersConfigurationIS = configurationManager.getInputStream(DEFAULT_CONSUMERS_CONFIG_LOCATION); } PluginsAccess.register(plugins); } } private String computePath(String pathFromConfig) { // if the specified path doesn't start with one of the recognized protocol, then it should be a file URL if (!pathFromConfig.startsWith("jar:") && !pathFromConfig.startsWith("classpath:") && !pathFromConfig.startsWith("war:") && !pathFromConfig.startsWith("file:")) { return FILE + pathFromConfig; } else { return pathFromConfig; } } public void start() { if (!initialized) { try { startProducer(); startConsumers(); // listen for web app events so that we can inject services into WSRP admin UI "cleanly" // todo: this service injection should really be done using CDI... :/ ServletContainer servletContainer = ServletContainerFactory.getServletContainer(); servletContainer.addWebAppListener(this); log.info("WSRP Service version '" + WSRPConstants.WSRP_SERVICE_VERSION + "' STARTED"); } catch (Exception e) { log.error("WSRP Service version '" + WSRPConstants.WSRP_SERVICE_VERSION + "' FAILED to start", e); } } else { // reuse the consumer registry FederatingPortletInvoker federatingPortletInvoker = (FederatingPortletInvoker) container.getComponentInstanceOfType(FederatingPortletInvoker.class); JCRConsumerRegistry existingRegistry = (JCRConsumerRegistry) ExoContainerContext.getTopContainer().getComponentInstanceOfType(ConsumerRegistry.class); RegisteringPortletInvokerResolver resolver = new RegisteringPortletInvokerResolver(); resolver.setConsumerRegistry(existingRegistry); federatingPortletInvoker.setPortletInvokerResolver(resolver); } } private void startProducer() { JCRProducerConfigurationService producerConfigurationService; try { JCRPersister persister = new JCRPersister(container, JCRPersister.WSRP_WORKSPACE_NAME); persister.initializeBuilderFor(JCRProducerConfigurationService.mappingClasses); producerConfigurationService = new JCRProducerConfigurationService(persister); producerConfigurationService.setConfigurationIS(producerConfigurationIS); producerConfigurationService.reloadConfiguration(); } catch (Exception e) { log.debug("Couldn't load WSRP producer configuration from " + producerConfigLocation, e); throw new RuntimeException("Couldn't load WSRP producer configuration from " + producerConfigLocation, e); } container.registerComponentInstance(ProducerConfigurationService.class, producerConfigurationService); RegistrationPersistenceManager registrationPersistenceManager; try { JCRPersister persister = new JCRPersister(container, JCRPersister.WSRP_WORKSPACE_NAME); persister.initializeBuilderFor(JCRRegistrationPersistenceManager.mappingClasses); registrationPersistenceManager = new JCRRegistrationPersistenceManager(persister); } catch (Exception e) { log.debug("Couldn't instantiate RegistrationPersistenceManager", e); throw new RuntimeException("Couldn't instantiate RegistrationPersistenceManager", e); } RegistrationManager registrationManager = new RegistrationManagerImpl(); registrationManager.setPersistenceManager(registrationPersistenceManager); // retrieve container portlet invoker from eXo kernel ContainerPortletInvoker containerPortletInvoker = (ContainerPortletInvoker) container .getComponentInstanceOfType(ContainerPortletInvoker.class); // iterate over the container stack so that we can insert the WSRP-specific event payload interceptor PortletInvokerInterceptor previous = containerPortletInvoker; PortletInvokerInterceptor next = previous; do { PortletInvoker invoker = previous.getNext(); if (invoker instanceof EventPayloadInterceptor) { // create a new WSRPEventPayloadInterceptor and make its next one the current event payload invoker WSRPEventPayloadInterceptor eventPayloadInterceptor = new WSRPEventPayloadInterceptor(); eventPayloadInterceptor.setNext(invoker); // replace the current event payload interceptor by the WSRP-specific one previous.setNext(eventPayloadInterceptor); // we're done break; } else { previous = next; if (invoker instanceof PortletInvokerInterceptor) { next = (PortletInvokerInterceptor) invoker; } else { next = null; } } } while (next != null); // The producer persistence manager PortletStatePersistenceManager producerPersistenceManager; try { JCRPersister persister = new JCRPersister(container, JCRPersister.PORTLET_STATES_WORKSPACE_NAME); persister.initializeBuilderFor(JCRPortletStatePersistenceManager.mappingClasses); producerPersistenceManager = new JCRPortletStatePersistenceManager(persister); } catch (Exception e) { log.debug("Couldn't instantiate PortletStatePersistenceManager", e); throw new RuntimeException("Couldn't instantiate PortletStatePersistenceManager", e); } // The producer state management policy StateManagementPolicyService producerStateManagementPolicy = new StateManagementPolicyService(); producerStateManagementPolicy.setPersistLocally(true); // The producer state converter StateConverter producerStateConverter = new StateConverterV0(); // The producer portlet invoker ProducerPortletInvoker producerPortletInvoker = new ProducerPortletInvoker(); producerPortletInvoker.setNext(containerPortletInvoker); producerPortletInvoker.setPersistenceManager(producerPersistenceManager); producerPortletInvoker.setStateManagementPolicy(producerStateManagementPolicy); producerPortletInvoker.setStateConverter(producerStateConverter); RegistrationCheckingPortletInvoker wsrpPortletInvoker = new RegistrationCheckingPortletInvoker(); wsrpPortletInvoker.setNext(producerPortletInvoker); wsrpPortletInvoker.setRegistrationManager(registrationManager); // create and wire WSRP producer producer = ProducerHolder.getProducer(true); producer.setPortletInvoker(wsrpPortletInvoker); producer.setRegistrationManager(registrationManager); producer.setConfigurationService(producerConfigurationService); exoKernelIntegration.getPortletApplicationRegistry().addListener(producer); producer.start(); log.info("WSRP Producer started"); } private void startConsumers() { // retrieve federating portlet invoker from container FederatingPortletInvoker federatingPortletInvoker = (FederatingPortletInvoker) container .getComponentInstanceOfType(FederatingPortletInvoker.class); // add our Session event listener to the ListenerService for use in org.exoplatform.web.GenericHttpListener ListenerService listenerService = (ListenerService) container.getComponentInstanceOfType(ListenerService.class); SessionEventListenerAndBroadcaster sessionEventBroadcaster = new SessionEventListenerAndBroadcaster(); // events from org.exoplatform.web.GenericHttpListener listenerService.addListener("org.exoplatform.web.GenericHttpListener.sessionCreated", sessionEventBroadcaster); listenerService.addListener("org.exoplatform.web.GenericHttpListener.sessionDestroyed", sessionEventBroadcaster); try { JCRPersister persister = new JCRPersister(container, JCRPersister.WSRP_WORKSPACE_NAME); persister.initializeBuilderFor(JCRConsumerRegistry.mappingClasses); consumerRegistry = new JCRConsumerRegistry(persister); consumerRegistry.setFederatingPortletInvoker(federatingPortletInvoker); consumerRegistry.setSessionEventBroadcaster(sessionEventBroadcaster); consumerRegistry.setConfigurationIS(consumersConfigurationIS); // if we run in a cluster, use a distributed cache for consumers /* * if (ExoContainer.getProfiles().contains("cluster")) { CacheService cacheService = * (CacheService)container.getComponentInstanceOfType(CacheService.class); DistributedConsumerCache consumerCache = * new DistributedConsumerCache(cacheService); consumerRegistry.setConsumerCache(consumerCache); } */ // create ConsumerStructureProvider and register it to listen to page events POMSessionManager sessionManager = (POMSessionManager) container .getComponentInstanceOfType(POMSessionManager.class); PortalStructureAccess structureAccess = new MOPPortalStructureAccess(sessionManager); MOPConsumerStructureProvider structureprovider = new MOPConsumerStructureProvider(structureAccess); listenerService.addListener(EventType.PAGE_CREATED, structureprovider); listenerService.addListener(EventType.PAGE_DESTROYED, structureprovider); listenerService.addListener(EventType.PAGE_UPDATED, structureprovider); listenerService.addListener(DataStorage.PAGE_UPDATED, structureprovider); // migration service persister = new JCRPersister(container, JCRPersister.WSRP_WORKSPACE_NAME); persister.initializeBuilderFor(JCRMigrationService.mappingClasses); MigrationService migrationService = new JCRMigrationService(persister); migrationService.setStructureProvider(structureprovider); consumerRegistry.setMigrationService(migrationService); // wait 'delay' seconds before starting the consumers to give JBoss WS a chance to publish the WSDL and not deadlock ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.schedule(new Runnable() { public void run() { try { consumerRegistry.start(); } catch (Exception e) { throw new RuntimeException(e); } } }, consumersInitDelay, TimeUnit.SECONDS); // set up a PortletInvokerResolver so that when a remote producer is queried, we can start it if needed RegisteringPortletInvokerResolver resolver = new RegisteringPortletInvokerResolver(); resolver.setConsumerRegistry(consumerRegistry); federatingPortletInvoker.setPortletInvokerResolver(resolver); } catch (Exception e) { log.debug(e); throw new RuntimeException("Couldn't start WSRP consumers registry from configuration " + consumersConfigLocation, e); } ExoContainerContext.getTopContainer().registerComponentInstance(consumerRegistry); log.info("WSRP Consumers started"); } /** * Stops the component if the event is related to the main portal context. */ public void stop() { if (!initialized) { // stop listening to web app events ServletContainer servletContainer = ServletContainerFactory.getServletContainer(); servletContainer.removeWebAppListener(this); } } /** * Stops the producers and consumers if the event is related to the main portal context, before the component itself * gets stopped. * * @see org.gatein.integration.wsrp.WSRPServiceIntegration#stop() */ public void stopContainer() { // this code was originally on the stop() method, but it seems that it's called "too late" in the process, // as we don't have access to the RepositoryContainer anymore, so, we are unable to properly get a fresh // list of consumers, for instance. if (!initialized) { stopProducer(); stopConsumers(); } } private void stopProducer() { producer.stop(); producer = null; } private void stopConsumers() { try { consumerRegistry.stop(); } catch (Exception e) { log.debug(e); // We do not throw an exception here, as we cannot guarantee that we'll close before the connection // gets closed, so, we just shutdown uncleanly... } consumerRegistry = null; } public void onEvent(WebAppEvent event) { if (event instanceof WebAppLifeCycleEvent) { WebAppLifeCycleEvent lifeCycleEvent = (WebAppLifeCycleEvent) event; WebApp webApp = event.getWebApp(); ServletContext context = webApp.getServletContext(); // if we see the WSRP admin GUI being deployed or undeployed, inject or remove services if (WSRP_ADMIN_GUI_CONTEXT_PATH.equals(webApp.getContextPath())) { switch (lifeCycleEvent.getType()) { case WebAppLifeCycleEvent.ADDED: context.setAttribute("ConsumerRegistry", consumerRegistry); context.setAttribute("ProducerConfigurationService", producer.getConfigurationService()); break; case WebAppLifeCycleEvent.REMOVED: context.removeAttribute("ConsumerRegistry"); context.removeAttribute("ProducerConfigurationService"); break; } } } } }