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

File DefaultJobStatusStore.java

 

Coverage histogram

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

Code metrics

34
93
18
2
403
251
47
0.51
5.17
9
2.61

Classes

Class Line # Actions
DefaultJobStatusStore 63 91 0% 45 15
0.8936170389.4%
DefaultJobStatusStore.JobStatusSerializerRunnable 118 2 0% 2 0
1.0100%
 

Contributing tests

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