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.Service;
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 Service {
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 removeTrigger(final Trigger trigger, final JobDetail detail) throws SchedulerException {
084            getComponent().removeJob(detail, trigger);
085        }
086    
087        /**
088         * This method is invoked when a Quartz job is fired.
089         *
090         * @param jobExecutionContext the Quartz Job context
091         */
092        public void onJobExecute(final JobExecutionContext jobExecutionContext) throws JobExecutionException {
093            boolean run = true;
094            LoadBalancer balancer = getLoadBalancer();
095            if (balancer instanceof ServiceSupport) {
096                run = ((ServiceSupport) balancer).isRunAllowed();
097            }
098    
099            if (!run) {
100                // quartz scheduler could potential trigger during a route has been shutdown
101                LOG.warn("Cannot execute Quartz Job with context: " + jobExecutionContext + " because processor is not started: " + balancer);
102                return;
103            }
104    
105            if (LOG.isDebugEnabled()) {
106                LOG.debug("Firing Quartz Job with context: " + jobExecutionContext);
107            }
108            Exchange exchange = createExchange(jobExecutionContext);
109            try {
110                balancer.process(exchange);
111    
112                if (exchange.getException() != null) {
113                    // propagate the exception back to Quartz
114                    throw new JobExecutionException(exchange.getException());
115                }
116            } catch (Exception e) {
117                // log the error
118                LOG.error(ExchangeHelper.createExceptionMessage("Error processing exchange", exchange, e));
119    
120                // and rethrow to let quartz handle it
121                if (e instanceof JobExecutionException) {
122                    throw (JobExecutionException) e;
123                }
124                throw new JobExecutionException(e);
125            }
126        }
127    
128        public Exchange createExchange(final JobExecutionContext jobExecutionContext) {
129            Exchange exchange = createExchange();
130            exchange.setIn(new QuartzMessage(exchange, jobExecutionContext));
131            return exchange;
132        }
133    
134        public Producer createProducer() throws Exception {
135            throw new UnsupportedOperationException("You cannot send messages to this endpoint");
136        }
137    
138        public QuartzConsumer createConsumer(Processor processor) throws Exception {
139            return new QuartzConsumer(this, processor);
140        }
141    
142        @Override
143        protected String createEndpointUri() {
144            return "quartz://" + getTrigger().getGroup() + "/" + getTrigger().getName();
145        }
146    
147        protected String getJobName() {
148            return getJobDetail().getName();
149        }
150    
151        // Properties
152        // -------------------------------------------------------------------------
153    
154        @Override
155        public QuartzComponent getComponent() {
156            return (QuartzComponent) super.getComponent();
157        }
158    
159        public boolean isSingleton() {
160            return true;
161        }
162    
163        public LoadBalancer getLoadBalancer() {
164            if (loadBalancer == null) {
165                loadBalancer = createLoadBalancer();
166            }
167            return loadBalancer;
168        }
169    
170        public void setLoadBalancer(final LoadBalancer loadBalancer) {
171            this.loadBalancer = loadBalancer;
172        }
173    
174        public JobDetail getJobDetail() {
175            if (jobDetail == null) {
176                jobDetail = createJobDetail();
177            }
178            return jobDetail;
179        }
180    
181        public void setJobDetail(final JobDetail jobDetail) {
182            this.jobDetail = jobDetail;
183        }
184    
185        public Trigger getTrigger() {
186            return trigger;
187        }
188    
189        public void setTrigger(final Trigger trigger) {
190            this.trigger = trigger;
191        }
192    
193        public boolean isStateful() {
194            return this.stateful;
195        }
196    
197        public void setStateful(final boolean stateful) {
198            this.stateful = stateful;
199        }
200    
201        // Implementation methods
202        // -------------------------------------------------------------------------
203    
204        public synchronized void consumerStarted(final QuartzConsumer consumer) throws SchedulerException {
205            ObjectHelper.notNull(trigger, "trigger");
206            if (LOG.isDebugEnabled()) {
207                LOG.debug("Adding consumer " + consumer.getProcessor());
208            }
209            getLoadBalancer().addProcessor(consumer.getProcessor());
210    
211            // if we have not yet added our default trigger, then lets do it
212            if (!started) {
213                addTrigger(getTrigger(), getJobDetail());
214                started = true;
215            }
216        }
217    
218        public synchronized void consumerStopped(final QuartzConsumer consumer) throws SchedulerException {
219            ObjectHelper.notNull(trigger, "trigger");
220            if (started) {
221                removeTrigger(getTrigger(), getJobDetail());
222                started = false;
223            }
224    
225            if (LOG.isDebugEnabled()) {
226                LOG.debug("Removing consumer " + consumer.getProcessor());
227            }
228            getLoadBalancer().removeProcessor(consumer.getProcessor());
229        }
230    
231        protected LoadBalancer createLoadBalancer() {
232            return new RoundRobinLoadBalancer();
233        }
234    
235        protected JobDetail createJobDetail() {
236            return new JobDetail();
237        }
238    
239        public void start() throws Exception {
240            ObjectHelper.notNull(getComponent(), "QuartzComponent", this);
241            ServiceHelper.startService(loadBalancer);
242        }
243    
244        public void stop() throws Exception {
245            ServiceHelper.stopService(loadBalancer);
246        }
247    
248    }