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

File DefaultJobStatusStore.java

 

Coverage histogram

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

Code metrics

34
89
19
2
396
246
47
0.53
4.68
9.5
2.47

Classes

Class Line # Actions
DefaultJobStatusStore 62 87 0% 45 18
0.869565287%
DefaultJobStatusStore.JobStatusSerializerRunnable 117 2 0% 2 0
1.0100%
 

Contributing tests

This file is covered by 113 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.internal;
21   
22    import java.io.File;
23    import java.io.IOException;
24    import java.io.UnsupportedEncodingException;
25    import java.net.URLEncoder;
26    import java.util.List;
27    import java.util.concurrent.ExecutorService;
28    import java.util.concurrent.SynchronousQueue;
29    import java.util.concurrent.ThreadPoolExecutor;
30    import java.util.concurrent.TimeUnit;
31   
32    import javax.inject.Inject;
33    import javax.inject.Singleton;
34   
35    import org.apache.commons.configuration.ConfigurationException;
36    import org.apache.commons.configuration.PropertiesConfiguration;
37    import org.apache.commons.io.FileUtils;
38    import org.apache.commons.lang3.StringUtils;
39    import org.apache.commons.lang3.concurrent.BasicThreadFactory;
40    import org.slf4j.Logger;
41    import org.xwiki.cache.Cache;
42    import org.xwiki.cache.CacheException;
43    import org.xwiki.cache.CacheManager;
44    import org.xwiki.cache.config.LRUCacheConfiguration;
45    import org.xwiki.component.annotation.Component;
46    import org.xwiki.component.phase.Initializable;
47    import org.xwiki.component.phase.InitializationException;
48    import org.xwiki.job.DefaultJobStatus;
49    import org.xwiki.job.JobManagerConfiguration;
50    import org.xwiki.job.JobStatusStore;
51    import org.xwiki.job.annotation.Serializable;
52    import org.xwiki.job.event.status.JobStatus;
53   
54    /**
55    * Default implementation of {@link JobStatusStorage}.
56    *
57    * @version $Id: bd58ed5666d8cbaf6968a472c52389403bf72998 $
58    * @since 6.1M2
59    */
60    @Component
61    @Singleton
 
62    public class DefaultJobStatusStore implements JobStatusStore, Initializable
63    {
64    /**
65    * The current version of the store. Should be upgraded if any change is made.
66    */
67    private static final int VERSION = 1;
68   
69    /**
70    * The name of the file where the job status is stored.
71    */
72    private static final String FILENAME_STATUS = "status.xml";
73   
74    /**
75    * The name of the file where various information about the status store are stored (like the version of the store).
76    */
77    private static final String INDEX_FILE = "store.properties";
78   
79    /**
80    * The name of the property containing the version of the store.
81    */
82    private static final String INDEX_FILE_VERSION = "version";
83   
84    /**
85    * Encoding used for file content and names.
86    */
87    private static final String DEFAULT_ENCODING = "UTF-8";
88   
89    /**
90    * The encoded version of a <code>null</code> value in the id list.
91    */
92    private static final String FOLDER_NULL = "&null";
93   
94    private static final JobStatus NOSTATUS = new DefaultJobStatus<>(null, null, null, null);
95   
96    /**
97    * Used to get the storage directory.
98    */
99    @Inject
100    private JobManagerConfiguration configuration;
101   
102    @Inject
103    private CacheManager cacheManager;
104   
105    /**
106    * The logger to log.
107    */
108    @Inject
109    private Logger logger;
110   
111    private JobStatusSerializer serializer;
112   
113    private ExecutorService executorService;
114   
115    private Cache<JobStatus> cache;
116   
 
117    class JobStatusSerializerRunnable implements Runnable
118    {
119    /**
120    * The status to store.
121    */
122    private final JobStatus status;
123   
 
124  84 toggle JobStatusSerializerRunnable(JobStatus status)
125    {
126  84 this.status = status;
127    }
128   
 
129  84 toggle @Override
130    public void run()
131    {
132  84 saveJobStatus(this.status);
133    }
134    }
135   
 
136  174 toggle @Override
137    public void initialize() throws InitializationException
138    {
139  174 try {
140  174 this.serializer = new JobStatusSerializer();
141   
142    // Check if the store need to be upgraded
143  174 PropertiesConfiguration properties = getStoreProperties();
144  174 int version = properties.getInt(INDEX_FILE_VERSION, 0);
145  174 if (VERSION > version) {
146  146 repair();
147   
148    // Update version
149  146 properties.setProperty(INDEX_FILE_VERSION, VERSION);
150  146 properties.save();
151    }
152    } catch (Exception e) {
153  0 this.logger.error("Failed to load jobs", e);
154    }
155   
156  174 BasicThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("Job status serializer")
157    .daemon(true).priority(Thread.MIN_PRIORITY).build();
158  174 this.executorService =
159    new ThreadPoolExecutor(0, 10, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
160   
161    // Initialize cache
162  174 LRUCacheConfiguration cacheConfiguration =
163    new LRUCacheConfiguration("xwiki.groupservice.usergroups", this.configuration.getJobStatusCacheSize());
164  174 try {
165  174 this.cache = this.cacheManager.createNewCache(cacheConfiguration);
166    } catch (CacheException e) {
167  0 throw new InitializationException("Failed to initialize job status cache", e);
168    }
169    }
170   
 
171  4333 toggle private String toUniqueString(List<String> id)
172    {
173  4333 return StringUtils.join(id, '/');
174    }
175   
176    /**
177    * @param name the file or directory name to encode
178    * @return the encoding name
179    */
 
180  13723 toggle private String encode(String name)
181    {
182  13723 String encoded;
183   
184  13723 if (name != null) {
185  13723 try {
186  13723 encoded = URLEncoder.encode(name, DEFAULT_ENCODING);
187    } catch (UnsupportedEncodingException e) {
188    // Should never happen
189   
190  0 encoded = name;
191    }
192    } else {
193  0 encoded = FOLDER_NULL;
194    }
195   
196  13723 return encoded;
197    }
198   
199    /**
200    * Load jobs from directory.
201    *
202    * @throws IOException when failing to load statuses
203    */
 
204  146 toggle private void repair() throws IOException
205    {
206  146 File folder = this.configuration.getStorage();
207   
208  146 if (folder.exists()) {
209  7 if (!folder.isDirectory()) {
210  0 throw new IOException("Not a directory: " + folder);
211    }
212   
213  7 repairFolder(folder);
214    }
215    }
216   
217    /**
218    * @param folder the folder from where to load the jobs
219    */
 
220  42 toggle private void repairFolder(File folder)
221    {
222  42 for (File file : folder.listFiles()) {
223  63 if (file.isDirectory()) {
224  35 repairFolder(file);
225  28 } else if (file.getName().equals(FILENAME_STATUS)) {
226  28 try {
227  28 JobStatus status = loadStatus(folder);
228   
229  28 if (status != null) {
230  28 File properFolder = getJobFolder(status.getRequest().getId());
231   
232  28 if (!folder.equals(properFolder)) {
233    // Move the status in its right place
234  14 try {
235  14 FileUtils.moveFileToDirectory(file, properFolder, true);
236    } catch (IOException e) {
237  0 this.logger.error("Failed to move job status file", e);
238    }
239    }
240    }
241    } catch (Exception e) {
242  0 this.logger.warn("Failed to load job status in folder [{}]", folder, e);
243    }
244    }
245    }
246    }
247   
 
248  3775 toggle private JobStatus loadStatus(List<String> id)
249    {
250  3775 return loadStatus(getJobFolder(id));
251    }
252   
253    /**
254    * @param folder the folder from where to load the job status
255    */
 
256  3803 toggle private JobStatus loadStatus(File folder)
257    {
258  3803 File statusFile = new File(folder, FILENAME_STATUS);
259  3803 if (statusFile.exists()) {
260  37 return loadJobStatus(statusFile);
261    }
262   
263  3766 return null;
264    }
265   
266    /**
267    * @param statusFile the file containing job status to load
268    * @return the job status
269    * @throws Exception when failing to load the job status from the file
270    */
 
271  37 toggle private JobStatus loadJobStatus(File statusFile)
272    {
273  37 return this.serializer.read(statusFile);
274    }
275   
276    // JobStatusStorage
277   
278    /**
279    * @param id the id of the job
280    * @return the folder where to store the job related informations
281    */
 
282  3984 toggle private File getJobFolder(List<String> id)
283    {
284  3984 File folder = this.configuration.getStorage();
285   
286  3984 if (id != null) {
287  3973 for (String idElement : id) {
288  13723 folder = new File(folder, encode(idElement));
289    }
290    }
291   
292  3984 return folder;
293    }
294   
 
295  174 toggle private PropertiesConfiguration getStoreProperties() throws ConfigurationException
296    {
297  174 File folder = this.configuration.getStorage();
298   
299  174 return new PropertiesConfiguration(new File(folder, INDEX_FILE));
300    }
301   
302    /**
303    * @param status the job status to save
304    * @throws IOException when falling to store the provided status
305    */
 
306  87 toggle private void saveJobStatus(JobStatus status)
307    {
308  87 try {
309  87 File statusFile = getJobFolder(status.getRequest().getId());
310  87 statusFile = new File(statusFile, FILENAME_STATUS);
311   
312  87 this.serializer.write(status, statusFile);
313    } catch (Exception e) {
314  0 this.logger.warn("Failed to save job status [{}]", status, e);
315    }
316    }
317   
 
318  4077 toggle @Override
319    public JobStatus getJobStatus(List<String> id)
320    {
321  4077 String idString = toUniqueString(id);
322   
323  4077 JobStatus status = this.cache.get(idString);
324   
325  4077 if (status == null) {
326  3775 status = maybeLoadStatus(id, idString);
327    }
328   
329  4077 return status == NOSTATUS ? null : status;
330    }
331   
 
332  3775 toggle private synchronized JobStatus maybeLoadStatus(List<String> id, String idString)
333    {
334  3775 JobStatus status = this.cache.get(idString);
335   
336  3775 if (status == null) {
337  3775 try {
338  3775 status = loadStatus(id);
339   
340  3775 this.cache.set(idString, status);
341    } catch (Exception e) {
342  0 this.logger.warn("Failed to load job status for id {}", id, e);
343   
344  0 this.cache.remove(idString);
345    }
346    }
347   
348  3775 return status;
349    }
350   
 
351  3 toggle @Override
352    public void store(JobStatus status)
353    {
354  3 store(status, false);
355    }
356   
 
357  159 toggle @Override
358    public void storeAsync(JobStatus status)
359    {
360  159 store(status, true);
361    }
362   
 
363  162 toggle private void store(JobStatus status, boolean async)
364    {
365  162 if (status != null && status.getRequest() != null && status.getRequest().getId() != null) {
366  162 synchronized (this.cache) {
367  162 this.cache.set(toUniqueString(status.getRequest().getId()), status);
368    }
369   
370    // Only store Serializable job status on file system
371  162 if (status.getClass().isAnnotationPresent(Serializable.class) || status instanceof java.io.Serializable) {
372  87 if (async) {
373  84 this.executorService.execute(new JobStatusSerializerRunnable(status));
374    } else {
375  3 saveJobStatus(status);
376    }
377    }
378    }
379    }
380   
 
381  94 toggle @Override
382    public void remove(List<String> id)
383    {
384  94 File jobFolder = getJobFolder(id);
385   
386  94 if (jobFolder.exists()) {
387  16 try {
388  16 FileUtils.deleteDirectory(jobFolder);
389    } catch (IOException e) {
390  0 this.logger.warn("Failed to delete job folder [{}]", jobFolder, e);
391    }
392    }
393   
394  94 this.cache.remove(toUniqueString(id));
395    }
396    }