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.quartz.JobDetail;
033    import org.quartz.JobExecutionContext;
034    import org.quartz.JobExecutionException;
035    import org.quartz.SchedulerException;
036    import org.quartz.Trigger;
037    import org.slf4j.Logger;
038    import org.slf4j.LoggerFactory;
039    
040    /**
041     * A <a href="http://activemq.apache.org/quartz.html">Quartz Endpoint</a>
042     *
043     * @version 
044     */
045    public class QuartzEndpoint extends DefaultEndpoint implements ShutdownableService {
046        private static final transient Logger LOG = LoggerFactory.getLogger(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            LOG.debug("Firing Quartz Job with context: {}", jobExecutionContext);
110            Exchange exchange = createExchange(jobExecutionContext);
111            try {
112                balancer.process(exchange);
113    
114                if (exchange.getException() != null) {
115                    // propagate the exception back to Quartz
116                    throw new JobExecutionException(exchange.getException());
117                }
118            } catch (Exception e) {
119                // log the error
120                LOG.error(ExchangeHelper.createExceptionMessage("Error processing exchange", exchange, e));
121    
122                // and rethrow to let quartz handle it
123                if (e instanceof JobExecutionException) {
124                    throw (JobExecutionException) e;
125                }
126                throw new JobExecutionException(e);
127            }
128        }
129    
130        public Exchange createExchange(final JobExecutionContext jobExecutionContext) {
131            Exchange exchange = createExchange();
132            exchange.setIn(new QuartzMessage(exchange, jobExecutionContext));
133            return exchange;
134        }
135    
136        public Producer createProducer() throws Exception {
137            throw new UnsupportedOperationException("You cannot send messages to this endpoint");
138        }
139    
140        public QuartzConsumer createConsumer(Processor processor) throws Exception {
141            return new QuartzConsumer(this, processor);
142        }
143    
144        @Override
145        protected String createEndpointUri() {
146            return "quartz://" + getTrigger().getGroup() + "/" + getTrigger().getName();
147        }
148    
149        protected String getJobName() {
150            return getJobDetail().getName();
151        }
152    
153        // Properties
154        // -------------------------------------------------------------------------
155    
156        @Override
157        public QuartzComponent getComponent() {
158            return (QuartzComponent) super.getComponent();
159        }
160    
161        public boolean isSingleton() {
162            return true;
163        }
164    
165        public LoadBalancer getLoadBalancer() {
166            if (loadBalancer == null) {
167                loadBalancer = createLoadBalancer();
168            }
169            return loadBalancer;
170        }
171    
172        public void setLoadBalancer(final LoadBalancer loadBalancer) {
173            this.loadBalancer = loadBalancer;
174        }
175    
176        public JobDetail getJobDetail() {
177            if (jobDetail == null) {
178                jobDetail = createJobDetail();
179            }
180            return jobDetail;
181        }
182    
183        public void setJobDetail(final JobDetail jobDetail) {
184            this.jobDetail = jobDetail;
185        }
186    
187        public Trigger getTrigger() {
188            return trigger;
189        }
190    
191        public void setTrigger(final Trigger trigger) {
192            this.trigger = trigger;
193        }
194    
195        public boolean isStateful() {
196            return this.stateful;
197        }
198    
199        public void setStateful(final boolean stateful) {
200            this.stateful = stateful;
201        }
202    
203        // Implementation methods
204        // -------------------------------------------------------------------------
205    
206        public synchronized void consumerStarted(final QuartzConsumer consumer) throws SchedulerException {
207            ObjectHelper.notNull(trigger, "trigger");
208            LOG.debug("Adding consumer {}", consumer.getProcessor());
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                pauseTrigger(getTrigger());
222                started = false;
223            }
224    
225            LOG.debug("Removing consumer {}", consumer.getProcessor());
226            getLoadBalancer().removeProcessor(consumer.getProcessor());
227        }
228    
229        protected LoadBalancer createLoadBalancer() {
230            return new RoundRobinLoadBalancer();
231        }
232    
233        protected JobDetail createJobDetail() {
234            return new JobDetail();
235        }
236    
237        @Override
238        protected void doStart() throws Exception {
239            ObjectHelper.notNull(getComponent(), "QuartzComponent", this);
240            ServiceHelper.startService(loadBalancer);
241        }
242    
243        @Override
244        protected void doStop() throws Exception {
245            ServiceHelper.stopService(loadBalancer);
246        }
247    
248        @Override
249        protected void doShutdown() throws Exception {
250            ObjectHelper.notNull(trigger, "trigger");
251            deleteTrigger(getTrigger());
252        }
253    
254    }