001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.quartz;
018    
019    import java.util.Date;
020    
021    import org.apache.camel.Exchange;
022    import org.apache.camel.Processor;
023    import org.apache.camel.Producer;
024    import org.apache.camel.ShutdownableService;
025    import org.apache.camel.impl.DefaultEndpoint;
026    import org.apache.camel.impl.ServiceSupport;
027    import org.apache.camel.processor.loadbalancer.LoadBalancer;
028    import org.apache.camel.processor.loadbalancer.RoundRobinLoadBalancer;
029    import org.apache.camel.util.ExchangeHelper;
030    import org.apache.camel.util.ObjectHelper;
031    import org.apache.camel.util.ServiceHelper;
032    import org.apache.commons.logging.Log;
033    import org.apache.commons.logging.LogFactory;
034    import org.quartz.JobDetail;
035    import org.quartz.JobExecutionContext;
036    import org.quartz.JobExecutionException;
037    import org.quartz.SchedulerException;
038    import org.quartz.Trigger;
039    
040    /**
041     * A <a href="http://activemq.apache.org/quartz.html">Quartz Endpoint</a>
042     *
043     * @version $Revision:520964 $
044     */
045    public class QuartzEndpoint extends DefaultEndpoint implements ShutdownableService {
046        private static final transient Log LOG = LogFactory.getLog(QuartzEndpoint.class);
047    
048        private LoadBalancer loadBalancer;
049        private Trigger trigger;
050        private JobDetail jobDetail;
051        private volatile boolean started;
052        private volatile boolean stateful;
053    
054        public QuartzEndpoint(final String endpointUri, final QuartzComponent component) {
055            super(endpointUri, component);
056            getJobDetail().setName("quartz-" + getId());
057        }
058    
059        public void addTrigger(final Trigger trigger, final JobDetail detail) throws SchedulerException {
060            // lets default the trigger name to the job name
061            if (trigger.getName() == null) {
062                trigger.setName(detail.getName());
063            }
064            // lets default the trigger group to the job group
065            if (trigger.getGroup() == null) {
066                trigger.setGroup(detail.getGroup());
067            }
068            // default start time to now if not specified
069            if (trigger.getStartTime() == null) {
070                trigger.setStartTime(new Date());
071            }
072            detail.getJobDataMap().put(QuartzConstants.QUARTZ_ENDPOINT_URI, getEndpointUri());
073            detail.getJobDataMap().put(QuartzConstants.QUARTZ_CAMEL_CONTEXT_NAME, getCamelContext().getName());
074            if (detail.getJobClass() == null) {
075                detail.setJobClass(isStateful() ? StatefulCamelJob.class : CamelJob.class);
076            }
077            if (detail.getName() == null) {
078                detail.setName(getJobName());
079            }
080            getComponent().addJob(detail, trigger);
081        }
082    
083        public void pauseTrigger(final Trigger trigger) throws SchedulerException {
084            getComponent().pauseJob(trigger);
085        }
086    
087        public void deleteTrigger(final Trigger trigger) throws SchedulerException {
088            getComponent().deleteJob(trigger.getName(), trigger.getGroup());
089        }
090    
091        /**
092         * This method is invoked when a Quartz job is fired.
093         *
094         * @param jobExecutionContext the Quartz Job context
095         */
096        public void onJobExecute(final JobExecutionContext jobExecutionContext) throws JobExecutionException {
097            boolean run = true;
098            LoadBalancer balancer = getLoadBalancer();
099            if (balancer instanceof ServiceSupport) {
100                run = ((ServiceSupport) balancer).isRunAllowed();
101            }
102    
103            if (!run) {
104                // quartz scheduler could potential trigger during a route has been shutdown
105                LOG.warn("Cannot execute Quartz Job with context: " + jobExecutionContext + " because processor is not started: " + balancer);
106                return;
107            }
108    
109            if (LOG.isDebugEnabled()) {
110                LOG.debug("Firing Quartz Job with context: " + jobExecutionContext);
111            }
112            Exchange exchange = createExchange(jobExecutionContext);
113            try {
114                balancer.process(exchange);
115    
116                if (exchange.getException() != null) {
117                    // propagate the exception back to Quartz
118                    throw new JobExecutionException(exchange.getException());
119                }
120            } catch (Exception e) {
121                // log the error
122                LOG.error(ExchangeHelper.createExceptionMessage("Error processing exchange", exchange, e));
123    
124                // and rethrow to let quartz handle it
125                if (e instanceof JobExecutionException) {
126                    throw (JobExecutionException) e;
127                }
128                throw new JobExecutionException(e);
129            }
130        }
131    
132        public Exchange createExchange(final JobExecutionContext jobExecutionContext) {
133            Exchange exchange = createExchange();
134            exchange.setIn(new QuartzMessage(exchange, jobExecutionContext));
135            return exchange;
136        }
137    
138        public Producer createProducer() throws Exception {
139            throw new UnsupportedOperationException("You cannot send messages to this endpoint");
140        }
141    
142        public QuartzConsumer createConsumer(Processor processor) throws Exception {
143            return new QuartzConsumer(this, processor);
144        }
145    
146        @Override
147        protected String createEndpointUri() {
148            return "quartz://" + getTrigger().getGroup() + "/" + getTrigger().getName();
149        }
150    
151        protected String getJobName() {
152            return getJobDetail().getName();
153        }
154    
155        // Properties
156        // -------------------------------------------------------------------------
157    
158        @Override
159        public QuartzComponent getComponent() {
160            return (QuartzComponent) super.getComponent();
161        }
162    
163        public boolean isSingleton() {
164            return true;
165        }
166    
167        public LoadBalancer getLoadBalancer() {
168            if (loadBalancer == null) {
169                loadBalancer = createLoadBalancer();
170            }
171            return loadBalancer;
172        }
173    
174        public void setLoadBalancer(final LoadBalancer loadBalancer) {
175            this.loadBalancer = loadBalancer;
176        }
177    
178        public JobDetail getJobDetail() {
179            if (jobDetail == null) {
180                jobDetail = createJobDetail();
181            }
182            return jobDetail;
183        }
184    
185        public void setJobDetail(final JobDetail jobDetail) {
186            this.jobDetail = jobDetail;
187        }
188    
189        public Trigger getTrigger() {
190            return trigger;
191        }
192    
193        public void setTrigger(final Trigger trigger) {
194            this.trigger = trigger;
195        }
196    
197        public boolean isStateful() {
198            return this.stateful;
199        }
200    
201        public void setStateful(final boolean stateful) {
202            this.stateful = stateful;
203        }
204    
205        // Implementation methods
206        // -------------------------------------------------------------------------
207    
208        public synchronized void consumerStarted(final QuartzConsumer consumer) throws SchedulerException {
209            ObjectHelper.notNull(trigger, "trigger");
210            if (LOG.isDebugEnabled()) {
211                LOG.debug("Adding consumer " + consumer.getProcessor());
212            }
213            getLoadBalancer().addProcessor(consumer.getProcessor());
214    
215            // if we have not yet added our default trigger, then lets do it
216            if (!started) {
217                addTrigger(getTrigger(), getJobDetail());
218                started = true;
219            }
220        }
221    
222        public synchronized void consumerStopped(final QuartzConsumer consumer) throws SchedulerException {
223            ObjectHelper.notNull(trigger, "trigger");
224            if (started) {
225                pauseTrigger(getTrigger());
226                started = false;
227            }
228    
229            if (LOG.isDebugEnabled()) {
230                LOG.debug("Removing consumer " + consumer.getProcessor());
231            }
232            getLoadBalancer().removeProcessor(consumer.getProcessor());
233        }
234    
235        protected LoadBalancer createLoadBalancer() {
236            return new RoundRobinLoadBalancer();
237        }
238    
239        protected JobDetail createJobDetail() {
240            return new JobDetail();
241        }
242    
243        public void start() throws Exception {
244            ObjectHelper.notNull(getComponent(), "QuartzComponent", this);
245            ServiceHelper.startService(loadBalancer);
246        }
247    
248        public void stop() throws Exception {
249            ServiceHelper.stopService(loadBalancer);
250        }
251    
252        public void shutdown() throws Exception {
253            ObjectHelper.notNull(trigger, "trigger");
254            deleteTrigger(getTrigger());
255        }
256    }