1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.job

File AbstractJob.java

 

Coverage histogram

../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

26
72
12
1
368
213
29
0.4
6
12
2.42

Classes

Class Line # Actions
AbstractJob 58 72 0% 29 17
0.845454684.5%
 

Contributing tests

This file is covered by 147 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.job;
21   
22    import java.util.Date;
23    import java.util.concurrent.TimeUnit;
24    import java.util.concurrent.locks.Condition;
25    import java.util.concurrent.locks.ReentrantLock;
26   
27    import javax.inject.Inject;
28    import javax.inject.Provider;
29   
30    import org.slf4j.Logger;
31    import org.xwiki.component.annotation.InstantiationStrategy;
32    import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
33    import org.xwiki.component.manager.ComponentManager;
34    import org.xwiki.context.Execution;
35    import org.xwiki.context.ExecutionContext;
36    import org.xwiki.context.ExecutionContextException;
37    import org.xwiki.context.ExecutionContextManager;
38    import org.xwiki.job.event.JobFinishedEvent;
39    import org.xwiki.job.event.JobFinishingEvent;
40    import org.xwiki.job.event.JobStartedEvent;
41    import org.xwiki.job.event.status.JobProgressManager;
42    import org.xwiki.job.event.status.JobStatus;
43    import org.xwiki.job.event.status.JobStatus.State;
44    import org.xwiki.logging.LoggerManager;
45    import org.xwiki.logging.marker.BeginTranslationMarker;
46    import org.xwiki.logging.marker.EndTranslationMarker;
47    import org.xwiki.logging.marker.TranslationMarker;
48    import org.xwiki.observation.ObservationManager;
49   
50    /**
51    * Base class for {@link Job} implementations.
52    *
53    * @param <R> the request type associated to the job
54    * @version $Id: 232e309d3f3e7a5dc3402859a535b803e1b67466 $
55    * @since 7.4M1
56    */
57    @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
 
58    public abstract class AbstractJob<R extends Request, S extends JobStatus> implements Job
59    {
60    private static final BeginTranslationMarker LOG_BEGIN = new BeginTranslationMarker("job.log.begin");
61   
62    private static final BeginTranslationMarker LOG_BEGIN_ID = new BeginTranslationMarker("job.log.beginWithId");
63   
64    private static final EndTranslationMarker LOG_END = new EndTranslationMarker("job.log.end");
65   
66    private static final EndTranslationMarker LOG_END_ID = new EndTranslationMarker("job.log.endWithId");
67   
68    private static final TranslationMarker LOG_EXCEPTION = new TranslationMarker("job.log.exception");
69   
70    private static final TranslationMarker LOG_STATUS_STORE_FAILED =
71    new TranslationMarker("job.log.status.store.failed");
72   
73    /**
74    * Component manager.
75    */
76    @Inject
77    protected ComponentManager componentManager;
78   
79    /**
80    * Used to send extensions installation and upgrade related events.
81    */
82    @Inject
83    protected ObservationManager observationManager;
84   
85    /**
86    * Used to isolate job related log.
87    */
88    @Inject
89    protected LoggerManager loggerManager;
90   
91    /**
92    * Used to store the results of the jobs execution.
93    */
94    @Inject
95    protected JobStatusStore store;
96   
97    /**
98    * The logger to log.
99    */
100    @Inject
101    protected Logger logger;
102   
103    /**
104    * Used to set the current context.
105    */
106    @Inject
107    protected JobContext jobContext;
108   
109    @Inject
110    protected JobProgressManager progressManager;
111   
112    /**
113    * The job request.
114    */
115    protected R request;
116   
117    /**
118    * @see #getStatus()
119    */
120    protected S status;
121   
122    /**
123    * Main lock guarding all access.
124    */
125    protected final ReentrantLock lock = new ReentrantLock();
126   
127    /**
128    * Condition to wait for finished state.
129    */
130    protected final Condition finishedCondition = this.lock.newCondition();
131   
132    protected boolean initExecutionContext = true;
133   
134    /**
135    * Used to get the Execution Context.
136    */
137    @Inject
138    private Provider<Execution> executionProvider;
139   
140    /**
141    * Used to create a new Execution Context from scratch.
142    */
143    @Inject
144    private Provider<ExecutionContextManager> executionContextManagerProvider;
145   
 
146  8398 toggle @Override
147    public R getRequest()
148    {
149  8398 return this.request;
150    }
151   
 
152  3591 toggle @Override
153    public S getStatus()
154    {
155  3591 return this.status;
156    }
157   
 
158  587 toggle @Override
159    public void initialize(Request request)
160    {
161  587 this.request = castRequest(request);
162  587 this.status = createNewStatus(this.request);
163    }
164   
 
165  579 toggle @Override
166    public void run()
167    {
168  579 if (this.initExecutionContext) {
169  579 Execution execution = this.executionProvider.get();
170   
171    // Initialize a new context only if there is not already one
172  579 ExecutionContext context;
173  579 if (execution.getContext() == null) {
174    // Create a clean Execution Context
175  384 context = new ExecutionContext();
176    } else {
177  195 context = null;
178    }
179   
180  579 try {
181  579 if (context != null) {
182  384 try {
183  384 this.executionContextManagerProvider.get().initialize(context);
184    } catch (ExecutionContextException e) {
185  0 throw new RuntimeException("Failed to initialize Job [" + this + "] execution context", e);
186    }
187    }
188   
189  579 runInContext();
190    } finally {
191  579 if (context != null) {
192  384 execution.removeContext();
193    }
194    }
195    } else {
196  0 runInContext();
197    }
198    }
199   
 
200  579 toggle protected void runInContext()
201    {
202  579 Throwable error = null;
203  579 try {
204  579 jobStarting();
205   
206  579 runInternal();
207    } catch (Throwable t) {
208  43 this.logger.error(LOG_EXCEPTION, "Exception thrown during job execution", t);
209  43 error = t;
210    } finally {
211  579 jobFinished(error);
212    }
213    }
214   
215    /**
216    * Called when the job is starting.
217    */
 
218  579 toggle protected void jobStarting()
219    {
220  579 this.jobContext.pushCurrentJob(this);
221   
222  579 this.observationManager.notify(new JobStartedEvent(getRequest().getId(), getType(), this.request), this);
223   
224  579 if (this.status instanceof AbstractJobStatus) {
225  579 ((AbstractJobStatus<R>) this.status).setStartDate(new Date());
226  579 ((AbstractJobStatus<R>) this.status).setState(JobStatus.State.RUNNING);
227   
228  579 ((AbstractJobStatus) this.status).startListening();
229    }
230   
231  579 if (getStatus().getRequest().getId() != null) {
232  161 this.logger.info(LOG_BEGIN_ID, "Starting job of type [{}] with identifier [{}]", getType(),
233    getStatus().getRequest().getId());
234    } else {
235  418 this.logger.info(LOG_BEGIN, "Starting job of type [{}]", getType());
236    }
237    }
238   
239    /**
240    * Called when the job is done.
241    *
242    * @param error the exception throw during execution of the job
243    */
 
244  579 toggle protected void jobFinished(Throwable error)
245    {
246  579 this.lock.lock();
247   
248  579 try {
249  579 if (this.status instanceof AbstractJobStatus) {
250    // Store error
251  579 ((AbstractJobStatus) this.status).setError(error);
252    }
253   
254    // Give a chance to any listener to do custom action associated to the job
255  579 this.observationManager.notify(new JobFinishingEvent(getRequest().getId(), getType(), this.request), this,
256    error);
257   
258  579 if (getStatus().getRequest().getId() != null) {
259  161 this.logger.info(LOG_END_ID, "Finished job of type [{}] with identifier [{}]", getType(),
260    getStatus().getRequest().getId());
261    } else {
262  418 this.logger.info(LOG_END, "Finished job of type [{}]", getType());
263    }
264   
265  579 if (this.status instanceof AbstractJobStatus) {
266    // Indicate when the job ended
267  579 ((AbstractJobStatus) this.status).setEndDate(new Date());
268   
269    // Stop updating job status (progress, log, etc.)
270  579 ((AbstractJobStatus) this.status).stopListening();
271   
272    // Update job state
273  579 ((AbstractJobStatus) this.status).setState(JobStatus.State.FINISHED);
274    }
275   
276    // Release threads waiting for job being done
277  579 this.finishedCondition.signalAll();
278   
279    // Remove the job from the current jobs context
280  579 this.jobContext.popCurrentJob();
281   
282    // Store the job status
283  579 try {
284  579 if (this.request.getId() != null) {
285  161 this.store.storeAsync(this.status);
286    }
287    } catch (Throwable t) {
288  0 this.logger.warn(LOG_STATUS_STORE_FAILED, "Failed to store job status [{}]", this.status, t);
289    }
290    } finally {
291  579 this.lock.unlock();
292   
293    // Notify listener that job is fully finished
294  579 this.observationManager.notify(new JobFinishedEvent(getRequest().getId(), getType(), this.request), this,
295    error);
296    }
297    }
298   
299    /**
300    * Should be overridden if R is not Request.
301    *
302    * @param request the request
303    * @return the request in the proper extended type
304    */
 
305  92 toggle @SuppressWarnings("unchecked")
306    protected R castRequest(Request request)
307    {
308  92 return (R) request;
309    }
310   
311    /**
312    * @param request contains information related to the job to execute
313    * @return the status of the job
314    */
 
315  201 toggle protected S createNewStatus(R request)
316    {
317  201 Job currentJob = this.jobContext.getCurrentJob();
318  201 JobStatus currentJobStatus = currentJob != null ? currentJob.getStatus() : null;
319  201 return (S) new DefaultJobStatus<R>(request, currentJobStatus, this.observationManager, this.loggerManager);
320    }
321   
322    /**
323    * Should be implemented by {@link Job} implementations.
324    *
325    * @throws Exception errors during job execution
326    */
327    protected abstract void runInternal() throws Exception;
328   
 
329  299 toggle @Override
330    public void join() throws InterruptedException
331    {
332  299 this.lock.lockInterruptibly();
333   
334  299 try {
335  299 if (getStatus() == null || getStatus().getState() != State.FINISHED) {
336  299 this.finishedCondition.await();
337    }
338    } finally {
339  299 this.lock.unlock();
340    }
341    }
342   
 
343  0 toggle @Override
344    public boolean join(long time, TimeUnit unit) throws InterruptedException
345    {
346  0 this.lock.lockInterruptibly();
347   
348  0 try {
349  0 if (getStatus().getState() != State.FINISHED) {
350  0 return this.finishedCondition.await(time, unit);
351    }
352    } finally {
353  0 this.lock.unlock();
354    }
355   
356  0 return true;
357    }
358   
359    // Deprecated
360   
 
361  2 toggle @Override
362    @Deprecated
363    public void start(Request request)
364    {
365  2 initialize(request);
366  2 run();
367    }
368    }