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 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 @Override
244 protected void doStart() throws Exception {
245 ObjectHelper.notNull(getComponent(), "QuartzComponent", this);
246 ServiceHelper.startService(loadBalancer);
247 }
248
249 @Override
250 protected void doStop() throws Exception {
251 ServiceHelper.stopService(loadBalancer);
252 }
253
254 @Override
255 protected void doShutdown() throws Exception {
256 ObjectHelper.notNull(trigger, "trigger");
257 deleteTrigger(getTrigger());
258 }
259
260 }