001    /*
002     * Copyright 2006-2011 The Kuali Foundation
003     *
004     * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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    
017    package org.kuali.rice.ksb.messaging.config;
018    
019    import java.util.ArrayList;
020    import java.util.Arrays;
021    import java.util.Collection;
022    import java.util.Collections;
023    import java.util.LinkedList;
024    import java.util.List;
025    
026    import javax.sql.DataSource;
027    
028    import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
029    import org.apache.commons.httpclient.protocol.Protocol;
030    import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
031    import org.kuali.rice.core.api.config.CoreConfigHelper;
032    import org.kuali.rice.core.api.config.module.RunMode;
033    import org.kuali.rice.core.api.config.property.Config;
034    import org.kuali.rice.core.api.config.property.ConfigContext;
035    import org.kuali.rice.core.api.exception.RiceRuntimeException;
036    import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
037    import org.kuali.rice.core.api.lifecycle.Lifecycle;
038    import org.kuali.rice.core.api.resourceloader.ResourceLoader;
039    import org.kuali.rice.core.framework.persistence.jpa.OrmUtils;
040    import org.kuali.rice.core.impl.config.module.ModuleConfigurer;
041    import org.kuali.rice.core.impl.lifecycle.ServiceDelegatingLifecycle;
042    import org.kuali.rice.core.util.ClassLoaderUtils;
043    import org.kuali.rice.core.util.RiceConstants;
044    import org.kuali.rice.ksb.api.KsbApiConstants;
045    import org.kuali.rice.ksb.api.KsbApiServiceLocator;
046    import org.kuali.rice.ksb.api.bus.ServiceDefinition;
047    import org.kuali.rice.ksb.messaging.AlternateEndpoint;
048    import org.kuali.rice.ksb.messaging.AlternateEndpointLocation;
049    import org.kuali.rice.ksb.messaging.MessageFetcher;
050    import org.kuali.rice.ksb.messaging.resourceloader.KSBResourceLoaderFactory;
051    import org.kuali.rice.ksb.messaging.serviceconnectors.HttpInvokerConnector;
052    import org.kuali.rice.ksb.service.KSBServiceLocator;
053    import org.kuali.rice.ksb.util.KSBConstants;
054    import org.quartz.Scheduler;
055    import org.springframework.transaction.PlatformTransactionManager;
056    
057    
058    /**
059     * Used to configure the embedded workflow. This could be used to configure
060     * embedded workflow programmatically but mostly this is a base class by which
061     * to hang specific configuration behavior off of through subclassing
062     * 
063     * @author Kuali Rice Team (rice.collab@kuali.org)
064     * 
065     */
066    public class KSBConfigurer extends ModuleConfigurer {
067            
068            private static final String SERVICE_BUS_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbServiceBusClientSpringBeans.xml";
069            private static final String MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbMessageClientSpringBeans.xml";
070            private static final String OJB_MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbMessageClientSpringBeans.xml";
071            private static final String BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbBamSpringBeans.xml";
072            private static final String OJB_BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbBamSpringBeans.xml";
073            private static final String MODULE_SPRING = "classpath:org/kuali/rice/ksb/config/KsbModuleConfigurationSpringBeans.xml";
074            private static final String REGISTRY_SERVER_SPRING = "classpath:org/kuali/rice/ksb/config/KsbRegistryServerSpringBeans.xml";
075            private static final String OJB_REGISTRY_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbRegistrySpringBeans.xml";
076            private static final String WEB_SPRING = "classpath:org/kuali/rice/ksb/config/KsbWebSpringBeans.xml";
077    
078            private List<ServiceDefinition> services = new ArrayList<ServiceDefinition>();
079            
080        private List<AlternateEndpointLocation> alternateEndpointLocations = new ArrayList<AlternateEndpointLocation>();
081    
082            private List<AlternateEndpoint> alternateEndpoints = new ArrayList<AlternateEndpoint>();
083    
084            private DataSource registryDataSource;
085    
086            private DataSource messageDataSource;
087            
088            private DataSource nonTransactionalMessageDataSource;
089            
090            private DataSource bamDataSource;
091    
092            private Scheduler exceptionMessagingScheduler;
093    
094            private PlatformTransactionManager platformTransactionManager;
095            
096            private List<Lifecycle> internalLifecycles;
097            
098            public KSBConfigurer() {
099                    super(KsbApiConstants.KSB_MODULE_NAME);
100                    setValidRunModes(Arrays.asList(RunMode.REMOTE, RunMode.LOCAL));
101                    this.internalLifecycles = new ArrayList<Lifecycle>();
102            }
103            
104            @Override
105            public void addAdditonalToConfig() {
106                    configureDataSource();
107                    configureScheduler();
108                    configurePlatformTransactionManager();
109                    configureAlternateEndpoints();
110            }
111    
112            @Override
113            public List<String> getPrimarySpringFiles(){
114                    final List<String> springFileLocations = new ArrayList<String>();
115                                    
116                    boolean isJpa = OrmUtils.isJpaEnabled("rice.ksb");
117                    if (isJpa) {
118                            // TODO redo this once we're back to JPA
119                    // springFileLocations.add("classpath:org/kuali/rice/ksb/config/KSBJPASpringBeans.xml");
120                    throw new UnsupportedOperationException("JPA not currently supported for KSB");
121                    }
122                    
123                    springFileLocations.add(SERVICE_BUS_CLIENT_SPRING);
124                    
125                    if (isMessagePersistenceEnabled()) {
126                            springFileLocations.add(MESSAGE_CLIENT_SPRING);
127                            springFileLocations.add(OJB_MESSAGE_CLIENT_SPRING);
128                    }
129            
130            if (isBamEnabled()) {
131                    springFileLocations.add(BAM_SPRING);
132                    springFileLocations.add(OJB_BAM_SPRING);
133            }
134            
135            if (getRunMode().equals( RunMode.LOCAL )) {
136                    // TODO hack 'cause KSB used KNS - this needs to be fixed!!!
137                    springFileLocations.add("classpath:org/kuali/rice/krad/config/KRADSpringBeans.xml");
138                springFileLocations.add("classpath:org/kuali/rice/kns/config/KNSSpringBeans.xml");
139                    springFileLocations.add(REGISTRY_SERVER_SPRING);
140                    springFileLocations.add(OJB_REGISTRY_SPRING);
141                    if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.LOAD_KRAD_MODULE_CONFIGURATION, false)) {
142                    springFileLocations.add(MODULE_SPRING);
143                    springFileLocations.add(WEB_SPRING);
144                }
145            }
146            
147            return springFileLocations;
148            }
149            
150            /**
151             * Returns true - KSB UI should always be included.
152             * 
153             * @see org.kuali.rice.core.ConfigContext.getCurrentContextConfig().ModuleConfigurer#shouldRenderWebInterface()
154             */
155            @Override
156            public boolean shouldRenderWebInterface() {
157                    return true;
158            }
159            
160            @Override
161            public Collection<ResourceLoader> getResourceLoadersToRegister() throws Exception{
162                    ResourceLoader ksbRemoteResourceLoader = KSBResourceLoaderFactory.createRootKSBRemoteResourceLoader();
163                    ksbRemoteResourceLoader.start();
164                    return Collections.singletonList(ksbRemoteResourceLoader);
165            }
166            
167            @Override
168            public List<Lifecycle> loadLifecycles() throws Exception {
169                    List<Lifecycle> lifecycles = new LinkedList<Lifecycle>();
170                    // this validation of our service list needs to happen after we've
171                    // loaded our configs so it's a lifecycle
172                    lifecycles.add(new BaseLifecycle() {
173    
174                            @Override
175                            public void start() throws Exception {
176                                    // first check if we want to allow self-signed certificates for SSL communication
177                                    if (Boolean.valueOf(ConfigContext.getCurrentContextConfig().getProperty(KSBConstants.Config.KSB_ALLOW_SELF_SIGNED_SSL)).booleanValue()) {
178                                        Protocol.registerProtocol("https", new Protocol("https",
179                                                (ProtocolSocketFactory) new EasySSLProtocolSocketFactory(), 443));
180                                    }
181                                    super.start();
182                            }
183                    });
184                    lifecycles.add(new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.BUS_ADMIN_SERVICE));
185                    return lifecycles;
186            }
187            
188            
189    
190        @Override
191        public void doAdditonalConfigurerValidations() {
192            for (final ServiceDefinition serviceDef : KSBConfigurer.this.services) {
193                            serviceDef.validate();
194                    }
195        }
196    
197            @Override
198            public void doAdditionalContextStartedLogic() {
199                    ServicePublisher servicePublisher = new ServicePublisher(getServices());
200                    Lifecycle serviceBus = new ServiceDelegatingLifecycle(KsbApiServiceLocator.SERVICE_BUS);
201                    Lifecycle threadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.THREAD_POOL_SERVICE);
202                    Lifecycle scheduledThreadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.SCHEDULED_THREAD_POOL_SERVICE);
203                    
204                    try {
205                            servicePublisher.start();
206                            internalLifecycles.add(servicePublisher);
207                            serviceBus.start();
208                            internalLifecycles.add(serviceBus);
209                            threadPool.start();
210                            internalLifecycles.add(threadPool);
211                            scheduledThreadPool.start();
212                            internalLifecycles.add(scheduledThreadPool);
213                    } catch (Exception e) {
214                            if (e instanceof RuntimeException) {
215                                    throw (RuntimeException)e;
216                            }
217                            throw new RiceRuntimeException("Failed to initialize KSB on context startup");
218                    }
219    
220                    requeueMessages();
221            }
222    
223            @Override
224            protected void doAdditionalModuleStopLogic() throws Exception {
225                    for (int index = internalLifecycles.size() - 1; index >= 0; index--) {
226                            try {
227                                    internalLifecycles.get(index).stop();
228                            } catch (Exception e) {
229                                    LOG.error("Failed to properly execute shutdown logic.", e);
230                            }
231                    }
232            }
233    
234            /**
235         * Used to refresh the service registry after the Application Context is initialized.  This way any services that were exported on startup
236         * will be available in the service registry once startup is complete.
237         */
238        private void requeueMessages() {
239            LOG.info("Refreshing Service Registry to export services to the bus.");
240            KsbApiServiceLocator.getServiceBus().synchronize();
241            
242                    //automatically requeue documents sitting with status of 'R'
243                    MessageFetcher messageFetcher = new MessageFetcher((Integer) null);
244                    KSBServiceLocator.getThreadPool().execute(messageFetcher);
245        }
246        
247        protected boolean isMessagePersistenceEnabled() {
248            return ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.MESSAGE_PERSISTENCE, true);
249        }
250        
251        protected boolean isBamEnabled() {
252            return ConfigContext.getCurrentContextConfig().getBooleanProperty(Config.BAM_ENABLED, false);
253        }
254    
255            protected void configureScheduler() {
256                    if (this.getExceptionMessagingScheduler() != null) {
257                            LOG.info("Configuring injected exception messaging Scheduler");
258                            ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.INJECTED_EXCEPTION_MESSAGE_SCHEDULER_KEY, this.getExceptionMessagingScheduler());
259                    }
260            }
261    
262            protected void configureDataSource() {
263                    if (isMessagePersistenceEnabled()) {
264                            if (getMessageDataSource() != null) {
265                                    ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_DATASOURCE, getMessageDataSource());
266                            }
267                            if (getNonTransactionalMessageDataSource() != null) {
268                        ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_NON_TRANSACTIONAL_DATASOURCE, getNonTransactionalMessageDataSource());
269                            }
270                    }
271            if (getRunMode().equals(RunMode.LOCAL)) {
272                    if (getRegistryDataSource() != null) {
273                    ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_REGISTRY_DATASOURCE, getRegistryDataSource());
274                }
275            }
276            if (isBamEnabled()) {
277                    if (getBamDataSource() != null) {
278                            ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_BAM_DATASOURCE, getBamDataSource());
279                    }
280            }
281        }
282    
283            protected void configurePlatformTransactionManager() {
284                    if (getPlatformTransactionManager() == null) {
285                            return;
286                    }
287                    ConfigContext.getCurrentContextConfig().putObject(RiceConstants.SPRING_TRANSACTION_MANAGER, getPlatformTransactionManager());
288            }
289            
290            protected void configureAlternateEndpoints() {
291                    ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINT_LOCATIONS, getAlternateEndpointLocations());
292                    ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS, getAlternateEndpoints());
293            }
294            
295            @Override
296            public void doAdditionalContextStoppedLogic() {
297                    try {
298                            HttpInvokerConnector.shutdownIdleConnectionTimeout();
299                    } catch (Exception e) {
300                            LOG.error("Failed to shutdown idle connection timeout evictor thread.", e);
301                    }
302                cleanUpConfiguration();
303            }
304            
305            /**
306         * Because our configuration is global, shutting down Rice does not get rid of objects stored there.  For that reason
307         * we need to manually clean these up.  This is most important in the case of the service bus because the configuration
308         * is used to store services to be exported.  If we don't clean this up then a shutdown/startup within the same
309         * class loading context causes the service list to be doubled and results in "multiple endpoint" error messages.
310         *
311         */
312        protected void cleanUpConfiguration() {
313            ConfigContext.getCurrentContextConfig().removeObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS);
314        }
315    
316            public List<ServiceDefinition> getServices() {
317                    return this.services;
318            }
319    
320            public void setServices(List<ServiceDefinition> javaServices) {
321                    this.services = javaServices;
322            }
323    
324            public DataSource getMessageDataSource() {
325                    return this.messageDataSource;
326            }
327    
328            public void setMessageDataSource(DataSource messageDataSource) {
329                    this.messageDataSource = messageDataSource;
330            }
331    
332        public DataSource getNonTransactionalMessageDataSource() {
333            return this.nonTransactionalMessageDataSource;
334        }
335    
336        public void setNonTransactionalMessageDataSource(DataSource nonTransactionalMessageDataSource) {
337            this.nonTransactionalMessageDataSource = nonTransactionalMessageDataSource;
338        }
339    
340        public DataSource getRegistryDataSource() {
341                    return this.registryDataSource;
342            }
343    
344            public void setRegistryDataSource(DataSource registryDataSource) {
345                    this.registryDataSource = registryDataSource;
346            }
347            
348            public DataSource getBamDataSource() {
349                    return this.bamDataSource;
350            }
351    
352            public void setBamDataSource(DataSource bamDataSource) {
353                    this.bamDataSource = bamDataSource;
354            }
355    
356            public Scheduler getExceptionMessagingScheduler() {
357                    return this.exceptionMessagingScheduler;
358            }
359    
360            public void setExceptionMessagingScheduler(Scheduler exceptionMessagingScheduler) {
361                    this.exceptionMessagingScheduler = exceptionMessagingScheduler;
362            }
363    
364            public PlatformTransactionManager getPlatformTransactionManager() {
365                    return platformTransactionManager;
366            }
367    
368            public void setPlatformTransactionManager(PlatformTransactionManager springTransactionManager) {
369                    this.platformTransactionManager = springTransactionManager;
370            }
371    
372        public List<AlternateEndpointLocation> getAlternateEndpointLocations() {
373                return this.alternateEndpointLocations;
374        }
375    
376        public void setAlternateEndpointLocations(List<AlternateEndpointLocation> alternateEndpointLocations) {
377                this.alternateEndpointLocations = alternateEndpointLocations;
378            }
379    
380        public List<AlternateEndpoint> getAlternateEndpoints() {
381            return this.alternateEndpoints;
382        }
383    
384        public void setAlternateEndpoints(List<AlternateEndpoint> alternateEndpoints) {
385            this.alternateEndpoints = alternateEndpoints;
386        }
387        
388        private final class ServicePublisher extends BaseLifecycle {
389    
390            private final List<ServiceDefinition> serviceDefinitions;
391            
392            ServicePublisher(List<ServiceDefinition> serviceDefinitions) {
393                    this.serviceDefinitions = serviceDefinitions;
394            }
395            
396                    @Override
397                    public void start() throws Exception {
398                            if (serviceDefinitions != null && !serviceDefinitions.isEmpty()) {
399                                    LOG.debug("Configuring " + serviceDefinitions.size() + " services for application id " + CoreConfigHelper.getApplicationId() + " using config for classloader " + ClassLoaderUtils.getDefaultClassLoader());
400                                    KsbApiServiceLocator.getServiceBus().publishServices(serviceDefinitions, true);
401                                    super.start();
402                            }
403                    }
404            
405        }
406        
407    }