001/**
002 * Copyright 2005-2017 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 */
016package org.kuali.rice.ksb.messaging.config;
017
018import org.kuali.rice.core.api.config.CoreConfigHelper;
019import org.kuali.rice.core.api.config.module.RunMode;
020import org.kuali.rice.core.api.config.property.Config;
021import org.kuali.rice.core.api.config.property.ConfigContext;
022import org.kuali.rice.core.api.exception.RiceRuntimeException;
023import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
024import org.kuali.rice.core.api.lifecycle.Lifecycle;
025import org.kuali.rice.core.api.resourceloader.ResourceLoader;
026import org.kuali.rice.core.api.util.ClassLoaderUtils;
027import org.kuali.rice.core.api.util.RiceConstants;
028import org.kuali.rice.core.framework.config.module.ModuleConfigurer;
029import org.kuali.rice.core.framework.config.module.WebModuleConfiguration;
030import org.kuali.rice.core.framework.lifecycle.ServiceDelegatingLifecycle;
031import org.kuali.rice.ksb.api.KsbApiConstants;
032import org.kuali.rice.ksb.api.KsbApiServiceLocator;
033import org.kuali.rice.ksb.api.bus.ServiceDefinition;
034import org.kuali.rice.ksb.messaging.AlternateEndpoint;
035import org.kuali.rice.ksb.messaging.AlternateEndpointLocation;
036import org.kuali.rice.ksb.messaging.MessageFetcher;
037import org.kuali.rice.ksb.messaging.resourceloader.KSBResourceLoaderFactory;
038import org.kuali.rice.ksb.service.KSBServiceLocator;
039import org.kuali.rice.ksb.util.KSBConstants;
040import org.quartz.Scheduler;
041import org.springframework.context.ApplicationEvent;
042import org.springframework.context.event.ContextRefreshedEvent;
043import org.springframework.context.event.ContextStoppedEvent;
044import org.springframework.context.event.SmartApplicationListener;
045import org.springframework.core.Ordered;
046import org.springframework.transaction.PlatformTransactionManager;
047
048import javax.sql.DataSource;
049import javax.xml.ws.WebServiceException;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.Collection;
053import java.util.Collections;
054import java.util.LinkedList;
055import java.util.List;
056
057/**
058 * Used to configure the embedded workflow. This could be used to configure
059 * embedded workflow programmatically but mostly this is a base class by which
060 * to hang specific configuration behavior off of through subclassing
061 *
062 * @author Kuali Rice Team (rice.collab@kuali.org)
063 */
064public class KSBConfigurer extends ModuleConfigurer implements SmartApplicationListener {
065        
066    private static final String SERVICE_BUS_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbServiceBusClientSpringBeans.xml";
067    private static final String MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbMessageClientSpringBeans.xml";
068    private static final String JPA_MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbJpaMessageClientSpringBeans.xml";
069    private static final String BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbBamSpringBeans.xml";
070    private static final String REGISTRY_SERVER_SPRING = "classpath:org/kuali/rice/ksb/config/KsbRegistryServerSpringBeans.xml";
071    private static final String JPA_REGISTRY_SPRING = "classpath:org/kuali/rice/ksb/config/KsbJpaRegistrySpringBeans.xml";
072    private static final String WEB_SPRING = "classpath:org/kuali/rice/ksb/config/KsbWebSpringBeans.xml";
073
074    private List<ServiceDefinition> services = new ArrayList<ServiceDefinition>();
075
076    private List<AlternateEndpointLocation> alternateEndpointLocations = new ArrayList<AlternateEndpointLocation>();
077
078    private List<AlternateEndpoint> alternateEndpoints = new ArrayList<AlternateEndpoint>();
079
080    private DataSource registryDataSource;
081
082    private DataSource messageDataSource;
083
084    private DataSource nonTransactionalMessageDataSource;
085
086    private DataSource bamDataSource;
087
088    private Scheduler exceptionMessagingScheduler;
089
090    private PlatformTransactionManager platformTransactionManager;
091
092    private List<Lifecycle> internalLifecycles;
093
094    public KSBConfigurer() {
095        super(KsbApiConstants.KSB_MODULE_NAME);
096        setValidRunModes(Arrays.asList(RunMode.THIN, RunMode.REMOTE, RunMode.LOCAL));
097        this.internalLifecycles = new ArrayList<Lifecycle>();
098    }
099
100    @Override
101    public void addAdditonalToConfig() {
102        configureDataSource();
103        configureScheduler();
104        configurePlatformTransactionManager();
105        configureAlternateEndpoints();
106    }
107
108    @Override
109    public List<String> getPrimarySpringFiles() {
110        LOG.info("KSBConfigurer:getPrimarySpringFiles: getRunMode => " + getRunMode());
111        final List<String> springFileLocations = new ArrayList<String>();
112
113        springFileLocations.add(SERVICE_BUS_CLIENT_SPRING);
114
115        if (getRunMode() != RunMode.THIN) {
116            // Loading these beans unconditionally now, see:
117            // KULRICE-6574: Some KSB beans not defined unless message persistence turned on
118            //
119            // if (isMessagePersistenceEnabled()) {
120            springFileLocations.add(MESSAGE_CLIENT_SPRING);
121            springFileLocations.add(JPA_MESSAGE_CLIENT_SPRING);
122            // }
123
124            if (isBamEnabled()) {
125                springFileLocations.add(BAM_SPRING);
126            }
127        }
128
129        if (getRunMode().equals(RunMode.LOCAL)) {
130            springFileLocations.add(JPA_REGISTRY_SPRING);
131            springFileLocations.add(REGISTRY_SERVER_SPRING);
132        }
133
134        return springFileLocations;
135    }
136
137    @Override
138    public boolean hasWebInterface() {
139        return true;
140    }
141
142    // See KULRICE-7093: KSB Module UI is not available on client applications
143    @Override
144    public boolean shouldRenderWebInterface() {
145        if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.WEB_FORCE_ENABLE)) {
146            return true;
147        }
148        return super.shouldRenderWebInterface();
149    }
150
151    @Override
152    protected WebModuleConfiguration loadWebModule() {
153        WebModuleConfiguration configuration = super.loadWebModule();
154        configuration.getWebSpringFiles().add(WEB_SPRING);
155        return configuration;
156    }
157
158    @Override
159    public Collection<ResourceLoader> getResourceLoadersToRegister() throws Exception {
160        ResourceLoader ksbRemoteResourceLoader = KSBResourceLoaderFactory.createRootKSBRemoteResourceLoader();
161        ksbRemoteResourceLoader.start();
162        return Collections.singletonList(ksbRemoteResourceLoader);
163    }
164
165    @Override
166    public List<Lifecycle> loadLifecycles() throws Exception {
167        List<Lifecycle> lifecycles = new LinkedList<Lifecycle>();
168
169        // this validation of our service list needs to happen after we've
170        // loaded our configs so it's a lifecycle
171        lifecycles.add(new BaseLifecycle() {
172            @Override
173            public void start() throws Exception {
174                super.start();
175            }
176        });
177
178        return lifecycles;
179    }
180
181    protected void validateServices(List<ServiceDefinition> services) {
182        for (final ServiceDefinition serviceDef : this.services) {
183            serviceDef.validate();
184        }
185    }
186
187    @Override
188    public void onApplicationEvent(ApplicationEvent applicationEvent) {
189        if (applicationEvent instanceof ContextRefreshedEvent) {
190            doAdditionalContextStartedLogic();
191        } else if (applicationEvent instanceof ContextStoppedEvent) {
192            doAdditionalContextStoppedLogic();
193        }
194    }
195
196    protected void doAdditionalContextStartedLogic() {
197        validateServices(getServices());
198        ServicePublisher servicePublisher = new ServicePublisher(getServices());
199        Lifecycle serviceBus = new ServiceDelegatingLifecycle(KsbApiServiceLocator.SERVICE_BUS);
200        Lifecycle threadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.THREAD_POOL_SERVICE);
201        Lifecycle scheduledThreadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.SCHEDULED_THREAD_POOL_SERVICE);
202
203        try {
204            servicePublisher.start();
205            internalLifecycles.add(servicePublisher);
206            serviceBus.start();
207            internalLifecycles.add(serviceBus);
208            threadPool.start();
209            internalLifecycles.add(threadPool);
210            scheduledThreadPool.start();
211            internalLifecycles.add(scheduledThreadPool);
212        } catch (Exception e) {
213            if (e instanceof RuntimeException) {
214                throw (RuntimeException) e;
215            }
216            throw new RiceRuntimeException("Failed to initialize KSB on context startup");
217        }
218
219        // Don't requeue messages if we are in thin client mode
220        if (getRunMode() != RunMode.THIN) {
221            requeueMessages();
222        }
223    }
224
225    protected void doAdditionalContextStoppedLogic() {
226        cleanUpConfiguration();
227    }
228
229    @Override
230    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
231        return true;
232    }
233
234    @Override
235    public boolean supportsSourceType(Class<?> aClass) {
236        return true;
237    }
238
239    @Override
240    public int getOrder() {
241        return Ordered.LOWEST_PRECEDENCE;
242    }
243
244    @Override
245    protected void doAdditionalModuleStartLogic() throws Exception {
246        // this allows us to become aware of remote services, in case the application needs to use any of them during startup
247        /* Wrapped this call in a try/catch block to handle when the web
248                 * service call fails so the KSB module will continue starting even
249                 * if the remote service registry is not available.
250                 */
251        try {
252            // this allows us to become aware of remote services, in case the application needs to use any of them during startup
253            LOG.info("Synchronizing remote services with service bus after KSB startup...");
254            long startTime = System.currentTimeMillis();
255            KsbApiServiceLocator.getServiceBus().synchronizeRemoteServices();
256            long endTime = System.currentTimeMillis();
257            LOG.info("...total time to synchronize remote services with service bus after KSB startup: " + (endTime - startTime));
258        } catch (WebServiceException e) {
259            LOG.error("Caught web service exception synchronizing service bus while configuring KSB", e);
260        }
261    }
262
263    @Override
264    protected void doAdditionalModuleStopLogic() throws Exception {
265        for (int index = internalLifecycles.size() - 1; index >= 0; index--) {
266            try {
267                internalLifecycles.get(index).stop();
268            } catch (Exception e) {
269                LOG.error("Failed to properly execute shutdown logic.", e);
270            }
271        }
272    }
273
274    /**
275     * Used to refresh the service registry after the Application Context is initialized.  This way any services that
276     * were exported on startup
277     * will be available in the service registry once startup is complete.
278     */
279    private void requeueMessages() {
280        LOG.info("Refreshing Service Registry to export services to the bus.");
281        try {
282            KsbApiServiceLocator.getServiceBus().synchronizeLocalServices();
283        }catch (WebServiceException e) {
284            LOG.error("Caught web service exception while starting the KSB Configurer", e);
285        }
286
287        //automatically requeue documents sitting with status of 'R'
288        MessageFetcher messageFetcher = new MessageFetcher((Integer) null);
289        KSBServiceLocator.getThreadPool().execute(messageFetcher);
290    }
291
292    protected boolean isMessagePersistenceEnabled() {
293        return ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.MESSAGE_PERSISTENCE, true);
294    }
295
296    protected boolean isBamEnabled() {
297        return ConfigContext.getCurrentContextConfig().getBooleanProperty(Config.BAM_ENABLED, false);
298    }
299
300    protected void configureScheduler() {
301        if (this.getExceptionMessagingScheduler() != null) {
302            LOG.info("Configuring injected exception messaging Scheduler");
303            ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.INJECTED_EXCEPTION_MESSAGE_SCHEDULER_KEY, this.getExceptionMessagingScheduler());
304        }
305    }
306
307    protected void configureDataSource() {
308        if (isMessagePersistenceEnabled()) {
309            if (getMessageDataSource() != null) {
310                ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_DATASOURCE, getMessageDataSource());
311            }
312            if (getNonTransactionalMessageDataSource() != null) {
313                ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_NON_TRANSACTIONAL_DATASOURCE, getNonTransactionalMessageDataSource());
314            }
315        }
316        if (getRunMode().equals(RunMode.LOCAL)) {
317            if (getRegistryDataSource() != null) {
318                ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_REGISTRY_DATASOURCE, getRegistryDataSource());
319            }
320        }
321        if (isBamEnabled()) {
322            if (getBamDataSource() != null) {
323                ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_BAM_DATASOURCE, getBamDataSource());
324            }
325        }
326    }
327
328    protected void configurePlatformTransactionManager() {
329        if (getPlatformTransactionManager() == null) {
330            return;
331        }
332        ConfigContext.getCurrentContextConfig().putObject(RiceConstants.SPRING_TRANSACTION_MANAGER, getPlatformTransactionManager());
333    }
334
335    protected void configureAlternateEndpoints() {
336        ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINT_LOCATIONS, getAlternateEndpointLocations());
337        ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS, getAlternateEndpoints());
338    }
339
340    /**
341     * Because our configuration is global, shutting down Rice does not get rid of objects stored there.  For that
342     * reason we need to manually clean these up.  This is most important in the case of the service bus because the
343     * configuration is used to store services to be exported.  If we don't clean this up then a shutdown/startup
344     * within the same class loading context causes the service list to be doubled and results in "multiple endpoint"
345     * error messages.
346     */
347    protected void cleanUpConfiguration() {
348        ConfigContext.getCurrentContextConfig().removeObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS);
349    }
350
351    public List<ServiceDefinition> getServices() {
352        return this.services;
353    }
354
355    public void setServices(List<ServiceDefinition> javaServices) {
356        this.services = javaServices;
357    }
358
359    public DataSource getMessageDataSource() {
360        return this.messageDataSource;
361    }
362
363    public void setMessageDataSource(DataSource messageDataSource) {
364        this.messageDataSource = messageDataSource;
365    }
366
367    public DataSource getNonTransactionalMessageDataSource() {
368        return this.nonTransactionalMessageDataSource;
369    }
370
371    public void setNonTransactionalMessageDataSource(DataSource nonTransactionalMessageDataSource) {
372        this.nonTransactionalMessageDataSource = nonTransactionalMessageDataSource;
373    }
374
375    public DataSource getRegistryDataSource() {
376        return this.registryDataSource;
377    }
378
379    public void setRegistryDataSource(DataSource registryDataSource) {
380        this.registryDataSource = registryDataSource;
381    }
382
383    public DataSource getBamDataSource() {
384        return this.bamDataSource;
385    }
386
387    public void setBamDataSource(DataSource bamDataSource) {
388        this.bamDataSource = bamDataSource;
389    }
390
391    public Scheduler getExceptionMessagingScheduler() {
392        return this.exceptionMessagingScheduler;
393    }
394
395    public void setExceptionMessagingScheduler(Scheduler exceptionMessagingScheduler) {
396        this.exceptionMessagingScheduler = exceptionMessagingScheduler;
397    }
398
399    public PlatformTransactionManager getPlatformTransactionManager() {
400        return platformTransactionManager;
401    }
402
403    public void setPlatformTransactionManager(PlatformTransactionManager springTransactionManager) {
404        this.platformTransactionManager = springTransactionManager;
405    }
406
407    public List<AlternateEndpointLocation> getAlternateEndpointLocations() {
408        return this.alternateEndpointLocations;
409    }
410
411    public void setAlternateEndpointLocations(List<AlternateEndpointLocation> alternateEndpointLocations) {
412        this.alternateEndpointLocations = alternateEndpointLocations;
413    }
414
415    public List<AlternateEndpoint> getAlternateEndpoints() {
416        return this.alternateEndpoints;
417    }
418
419    public void setAlternateEndpoints(List<AlternateEndpoint> alternateEndpoints) {
420        this.alternateEndpoints = alternateEndpoints;
421    }
422
423    private final class ServicePublisher extends BaseLifecycle {
424
425        private final List<ServiceDefinition> serviceDefinitions;
426
427        ServicePublisher(List<ServiceDefinition> serviceDefinitions) {
428            this.serviceDefinitions = serviceDefinitions;
429        }
430
431        @Override
432        public void start() throws Exception {
433            if (serviceDefinitions != null && !serviceDefinitions.isEmpty()) {
434                LOG.debug("Configuring " + serviceDefinitions.size() + " services for application id " + CoreConfigHelper.getApplicationId() + " using config for classloader " + ClassLoaderUtils.getDefaultClassLoader());
435                KsbApiServiceLocator.getServiceBus().publishServices(serviceDefinitions, true);
436                super.start();
437            }
438        }
439
440    }
441
442}