1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.job

File AbstractJob.java

 

Coverage histogram

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

Code metrics

36
88
13
1
419
249
37
0.42
6.77
13
2.85

Classes

Class Line # Actions
AbstractJob 63 88 0% 37 14
0.897810289.8%
 

Contributing tests

This file is covered by 179 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.io.Serializable;
23    import java.util.Date;
24    import java.util.Map;
25    import java.util.concurrent.TimeUnit;
26    import java.util.concurrent.locks.Condition;
27    import java.util.concurrent.locks.ReentrantLock;
28   
29    import javax.inject.Inject;
30    import javax.inject.Provider;
31   
32    import org.apache.commons.collections4.MapUtils;
33    import org.slf4j.Logger;
34    import org.xwiki.component.annotation.InstantiationStrategy;
35    import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
36    import org.xwiki.component.manager.ComponentLookupException;
37    import org.xwiki.component.manager.ComponentManager;
38    import org.xwiki.context.Execution;
39    import org.xwiki.context.ExecutionContext;
40    import org.xwiki.context.ExecutionContextException;
41    import org.xwiki.context.ExecutionContextManager;
42    import org.xwiki.context.concurrent.ContextStoreManager;
43    import org.xwiki.job.event.JobFinishedEvent;
44    import org.xwiki.job.event.JobFinishingEvent;
45    import org.xwiki.job.event.JobStartedEvent;
46    import org.xwiki.job.event.status.JobProgressManager;
47    import org.xwiki.job.event.status.JobStatus;
48    import org.xwiki.job.event.status.JobStatus.State;
49    import org.xwiki.logging.LoggerManager;
50    import org.xwiki.logging.marker.BeginTranslationMarker;
51    import org.xwiki.logging.marker.EndTranslationMarker;
52    import org.xwiki.logging.marker.TranslationMarker;
53    import org.xwiki.observation.ObservationManager;
54   
55    /**
56    * Base class for {@link Job} implementations.
57    *
58    * @param <R> the request type associated to the job
59    * @version $Id: 70e6432c5f674d47676055a549db46a1026421fc $
60    * @since 7.4M1
61    */
62    @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
 
63    public abstract class AbstractJob<R extends Request, S extends JobStatus> implements Job
64    {
65    private static final BeginTranslationMarker LOG_BEGIN = new BeginTranslationMarker("job.log.begin");
66   
67    private static final BeginTranslationMarker LOG_BEGIN_ID = new BeginTranslationMarker("job.log.beginWithId");
68   
69    private static final EndTranslationMarker LOG_END = new EndTranslationMarker("job.log.end");
70   
71    private static final EndTranslationMarker LOG_END_ID = new EndTranslationMarker("job.log.endWithId");
72   
73    private static final TranslationMarker LOG_EXCEPTION = new TranslationMarker("job.log.exception");
74   
75    private static final TranslationMarker LOG_STATUS_STORE_FAILED =
76    new TranslationMarker("job.log.status.store.failed");
77   
78    /**
79    * Component manager.
80    */
81    @Inject
82    protected ComponentManager componentManager;
83   
84    /**
85    * Used to send extensions installation and upgrade related events.
86    */
87    @Inject
88    protected ObservationManager observationManager;
89   
90    /**
91    * Used to isolate job related log.
92    */
93    @Inject
94    protected LoggerManager loggerManager;
95   
96    @Inject
97    protected ContextStoreManager contextStore;
98   
99    /**
100    * Used to store the results of the jobs execution.
101    */
102    @Inject
103    protected JobStatusStore store;
104   
105    /**
106    * The logger to log.
107    */
108    @Inject
109    protected Logger logger;
110   
111    /**
112    * Used to set the current context.
113    */
114    @Inject
115    protected JobContext jobContext;
116   
117    @Inject
118    protected JobProgressManager progressManager;
119   
120    /**
121    * The job request.
122    */
123    protected R request;
124   
125    /**
126    * @see #getStatus()
127    */
128    protected S status;
129   
130    /**
131    * Main lock guarding all access.
132    */
133    protected final ReentrantLock lock = new ReentrantLock();
134   
135    /**
136    * Condition to wait for finished state.
137    */
138    protected final Condition finishedCondition = this.lock.newCondition();
139   
140    protected boolean initExecutionContext = true;
141   
142    /**
143    * Used to get the Execution Context.
144    */
145    @Inject
146    private Execution jobExecution;
147   
148    /**
149    * Used to create a new Execution Context from scratch.
150    */
151    @Inject
152    private Provider<ExecutionContextManager> executionContextManagerProvider;
153   
 
154  305379 toggle @Override
155    public R getRequest()
156    {
157  305444 return this.request;
158    }
159   
 
160  165697 toggle @Override
161    public S getStatus()
162    {
163  165697 return this.status;
164    }
165   
 
166  21990 toggle @Override
167    public void initialize(Request request)
168    {
169  21990 this.request = castRequest(request);
170  21990 this.status = createNewStatus(this.request);
171    }
172   
 
173  21972 toggle @Override
174    public void run()
175    {
176  21973 if (this.initExecutionContext) {
177  21973 ExecutionContext previousContext = null;
178   
179    // Get context to restore
180  21972 Map<String, Serializable> storedContext = getRequest().getContext();
181   
182    // Initialize a new context only if there is not already one
183  21975 ExecutionContext context;
184  21975 if (storedContext != null || this.jobExecution.getContext() == null) {
185    // Create a clean Execution Context
186  21747 context = new ExecutionContext();
187    } else {
188  226 context = null;
189    }
190   
191  21973 try {
192  21973 if (context != null) {
193    // Remember previous context
194  21747 previousContext = this.jobExecution.getContext();
195   
196  21749 try {
197  21748 this.executionContextManagerProvider.get().initialize(context);
198    } catch (ExecutionContextException e) {
199  0 throw new RuntimeException("Failed to initialize Job [" + this + "] execution context", e);
200    }
201   
202    // Restore stored context
203  21749 if (MapUtils.isNotEmpty(storedContext)) {
204  21186 try {
205  21186 this.contextStore.restore(storedContext);
206    } catch (ComponentLookupException e) {
207  0 throw new RuntimeException("Failed to restore context requested for the job [" + this + "]",
208    e);
209    }
210    }
211    }
212   
213  21969 runInContext();
214    } finally {
215  21974 if (context != null) {
216    // Get rid of job context
217  21749 this.jobExecution.removeContext();
218   
219    // Restore previous context
220  21749 if (previousContext != null) {
221  0 this.jobExecution.setContext(previousContext);
222    }
223    }
224    }
225    } else {
226  0 runInContext();
227    }
228    }
229   
 
230  21966 toggle protected void runInContext()
231    {
232  21974 Throwable error = null;
233  21971 try {
234  21969 jobStarting();
235   
236  21973 runInternal();
237    } catch (Throwable t) {
238  41 this.logger.error(LOG_EXCEPTION, "Exception thrown during job execution", t);
239  41 error = t;
240    } finally {
241  21974 jobFinished(error);
242    }
243    }
244   
245    /**
246    * Called when the job is starting.
247    */
 
248  21968 toggle protected void jobStarting()
249    {
250  21975 this.jobContext.pushCurrentJob(this);
251   
252  21972 this.observationManager.notify(new JobStartedEvent(getRequest().getId(), getType(), this.request), this);
253   
254  21969 if (this.status instanceof AbstractJobStatus) {
255  21967 ((AbstractJobStatus<R>) this.status).setStartDate(new Date());
256  21964 ((AbstractJobStatus<R>) this.status).setState(JobStatus.State.RUNNING);
257   
258  21968 ((AbstractJobStatus) this.status).startListening();
259    }
260   
261  21975 if (getRequest().isVerbose()) {
262  576 if (getStatus().getRequest().getId() != null) {
263  328 this.logger.info(LOG_BEGIN_ID, "Starting job of type [{}] with identifier [{}]", getType(),
264    getStatus().getRequest().getId());
265    } else {
266  248 this.logger.info(LOG_BEGIN, "Starting job of type [{}]", getType());
267    }
268    }
269    }
270   
271    /**
272    * Called when the job is done.
273    *
274    * @param error the exception throw during execution of the job
275    */
 
276  21974 toggle protected void jobFinished(Throwable error)
277    {
278  21974 this.lock.lock();
279   
280  21969 try {
281  21973 if (this.status instanceof AbstractJobStatus) {
282    // Store error
283  21972 ((AbstractJobStatus) this.status).setError(error);
284    }
285   
286    // Give a chance to any listener to do custom action associated to the job
287  21973 this.observationManager.notify(new JobFinishingEvent(getRequest().getId(), getType(), this.request), this,
288    error);
289   
290  21974 if (getRequest().isVerbose()) {
291  575 if (getStatus().getRequest().getId() != null) {
292  327 this.logger.info(LOG_END_ID, "Finished job of type [{}] with identifier [{}]", getType(),
293    getStatus().getRequest().getId());
294    } else {
295  248 this.logger.info(LOG_END, "Finished job of type [{}]", getType());
296    }
297    }
298   
299  21974 if (this.status instanceof AbstractJobStatus) {
300    // Indicate when the job ended
301  21973 ((AbstractJobStatus) this.status).setEndDate(new Date());
302   
303    // Stop updating job status (progress, log, etc.)
304  21972 ((AbstractJobStatus) this.status).stopListening();
305   
306    // Update job state
307  21974 ((AbstractJobStatus) this.status).setState(JobStatus.State.FINISHED);
308    }
309   
310    // Release threads waiting for job being done
311  21973 this.finishedCondition.signalAll();
312   
313    // Remove the job from the current jobs context
314  21972 this.jobContext.popCurrentJob();
315   
316    // Store the job status
317  21974 try {
318  21974 if (this.request.getId() != null) {
319  21516 this.store.storeAsync(this.status);
320    }
321    } catch (Throwable t) {
322  0 this.logger.warn(LOG_STATUS_STORE_FAILED, "Failed to store job status [{}]", this.status, t);
323    }
324    } finally {
325  21974 this.lock.unlock();
326   
327    // Notify listener that job is fully finished
328  21974 this.observationManager.notify(new JobFinishedEvent(getRequest().getId(), getType(), this.request), this,
329    error);
330    }
331    }
332   
333    /**
334    * Should be overridden if R is not Request.
335    *
336    * @param request the request
337    * @return the request in the proper extended type
338    */
 
339  228 toggle @SuppressWarnings("unchecked")
340    protected R castRequest(Request request)
341    {
342  228 return (R) request;
343    }
344   
345    /**
346    * Create a new standard {@link JobStatus}. Jobs requiring a custom {@link JobStatus} should overwrite this method.
347    *
348    * @param request contains information related to the job to execute
349    * @return the status of the job
350    */
 
351  232 toggle protected S createNewStatus(R request)
352    {
353  232 Job currentJob = this.jobContext.getCurrentJob();
354  232 JobStatus currentJobStatus = currentJob != null ? currentJob.getStatus() : null;
355  232 return (S) new DefaultJobStatus<R>(getType(), request, currentJobStatus, this.observationManager,
356    this.loggerManager);
357    }
358   
359    /**
360    * Should be implemented by {@link Job} implementations.
361    *
362    * @throws Exception errors during job execution
363    */
364    protected abstract void runInternal() throws Exception;
365   
 
366  428 toggle @Override
367    public void join() throws InterruptedException
368    {
369  428 this.lock.lockInterruptibly();
370   
371  428 try {
372  428 if (getStatus() == null || getStatus().getState() != State.FINISHED) {
373  428 this.finishedCondition.await();
374    }
375    } finally {
376  428 this.lock.unlock();
377    }
378    }
379   
 
380  4 toggle @Override
381    public boolean join(long time, TimeUnit unit) throws InterruptedException
382    {
383  4 this.lock.lockInterruptibly();
384   
385  4 try {
386  4 if (getStatus().getState() != State.FINISHED) {
387  4 return this.finishedCondition.await(time, unit);
388    }
389    } finally {
390  4 this.lock.unlock();
391    }
392   
393  0 return true;
394    }
395   
396    // Deprecated
397   
 
398  2 toggle @Override
399    @Deprecated
400    public void start(Request request)
401    {
402  2 initialize(request);
403  2 run();
404    }
405   
 
406  22091 toggle @Override
407    public String toString()
408    {
409  22091 StringBuilder builder = new StringBuilder(super.toString());
410   
411  22092 if (getRequest() != null) {
412  22092 builder.append('(');
413  22092 builder.append(getRequest().getId());
414  22092 builder.append(')');
415    }
416   
417  22092 return builder.toString();
418    }
419    }