View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import java.io.File;
4   import java.io.FilenameFilter;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.TreeMap;
12  
13  import com.atlassian.plugin.PluginException;
14  import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
15  import com.atlassian.plugin.loaders.classloading.Scanner;
16  
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  import static com.google.common.base.Preconditions.checkNotNull;
21  
22  /**
23   * Scans the filesystem for changed or added plugin files and stores a map of the currently known ones.  Files beginning
24   * with "." are ignored.
25   *
26   * @since 2.1.0
27   */
28  public class DirectoryScanner implements Scanner
29  {
30      private static Logger log = LoggerFactory.getLogger(DirectoryScanner.class);
31  
32      /**
33       * Tracks the classloading
34       */
35      private final File pluginsDirectory;
36  
37      /**
38       * A Map of {@link String} absolute file paths to {@link DeploymentUnit}s.
39       */
40      private final Map<String,DeploymentUnit> scannedDeploymentUnits = new TreeMap<String,DeploymentUnit>();
41  
42  
43      /**
44       * Constructor for scanner.
45       *
46       * @param pluginsDirectory the directory that the scanner should monitor for plugins
47       */
48      public DirectoryScanner(final File pluginsDirectory)
49      {
50          this.pluginsDirectory = checkNotNull(pluginsDirectory);
51      }
52  
53      private DeploymentUnit createAndStoreDeploymentUnit(final File file)
54      {
55          if (isScanned(file))
56              return null;
57  
58          final DeploymentUnit unit = new DeploymentUnit(file);
59          scannedDeploymentUnits.put(file.getAbsolutePath(), unit);
60  
61          return unit;
62      }
63  
64      /**
65       * Given a file, finds the deployment unit for it if one has already been scanned.
66       * @param file a jar file.
67       * @return the stored deploymentUnit matching the file or null if none exists.
68       */
69      public DeploymentUnit locateDeploymentUnit(final File file)
70      {
71          return scannedDeploymentUnits.get(file.getAbsolutePath());
72      }
73  
74      /**
75       * Finds whether the given file has been scanned already.
76       */
77      private boolean isScanned(final File file)
78      {
79          return locateDeploymentUnit(file) != null;
80      }
81  
82      /**
83       * Tells the Scanner to forget about a file it has loaded so that it will reload it
84       * next time it scans.
85       *
86       * @param file a file that may have already been scanned.
87       */
88      public void clear(final File file)
89      {
90          scannedDeploymentUnits.remove(file.getAbsolutePath());
91      }
92  
93      /**
94       * Scans for all files and directories that have been added or modified since the
95       * last call to scan. This will ignore all files or directories starting with
96       * the '.' character.
97       *
98       * @return Collection of {@link DeploymentUnit}s that describe newly added files or directories.
99       */
100     public Collection<DeploymentUnit> scan()
101     {
102         // Checks to see if we have deleted any of the deployment units.
103         final List<File> removedFiles = new ArrayList<File>();
104         for (final DeploymentUnit unit : scannedDeploymentUnits.values())
105         {
106             if (!unit.getPath().exists() || !unit.getPath().canRead())
107             {
108                 removedFiles.add(unit.getPath());
109             }
110         }
111         clear(removedFiles);
112 
113         // Checks for new files that don't start in '.'
114         final Collection<DeploymentUnit> result = new ArrayList<DeploymentUnit>();
115         final File files[] = pluginsDirectory.listFiles(new FilenameFilter()
116         {
117 
118             public boolean accept(final File dir, final String name)
119             {
120                 return !name.startsWith(".");
121             }
122         });
123 
124         if (files == null)
125         {
126             log.error("listFiles returned null for directory " + pluginsDirectory.getAbsolutePath());
127             return result;
128         }
129 
130         Arrays.sort(files); // sorts by filename for deterministic load order
131         for (final File file : files)
132         {
133             if (isScanned(file) && isModified(file))
134             {
135                 clear(file);
136                 final DeploymentUnit unit = createAndStoreDeploymentUnit(file);
137                 if (unit != null)
138                     result.add(unit);
139             }
140             else if (!isScanned(file))
141             {
142                 final DeploymentUnit unit = createAndStoreDeploymentUnit(file);
143                 if (unit != null)
144                     result.add(unit);
145             }
146         }
147         return result;
148     }
149 
150     private boolean isModified(final File file)
151     {
152         final DeploymentUnit unit = locateDeploymentUnit(file);
153         return file.lastModified() > unit.lastModified();
154     }
155 
156     private void clear(final List<File> toUndeploy)
157     {
158         for (final File aToUndeploy : toUndeploy)
159         {
160             clear( aToUndeploy);
161         }
162     }
163 
164     /**
165      * Retrieve all the {@link DeploymentUnit}s currently stored.
166      *
167      * @return the complete unmodifiable list of scanned {@link DeploymentUnit}s.
168      */
169     public Collection<DeploymentUnit> getDeploymentUnits()
170     {
171         return Collections.unmodifiableCollection(scannedDeploymentUnits.values());
172     }
173 
174     /**
175      * Clears the list of scanned deployment units.
176      */
177     public void reset()
178     {
179         scannedDeploymentUnits.clear();
180     }
181 
182     public void remove(final DeploymentUnit unit) throws PluginException
183     {
184         if (unit.getPath().exists())
185         {
186             if (unit.getPath().getParentFile().canWrite())
187             {
188                 if (!unit.getPath().delete())
189                 {
190                     throw new PluginException("Unable to delete file: " + unit.getPath());
191                 }
192             }
193             else
194             {
195                 log.info("Plugin file <{}> exists but we do not have permission to remove it. Ignoring.", unit.getPath().getPath());
196             }
197         }
198         else
199         {
200             log.debug("Plugin file <{}> doesn't exist to delete.  Ignoring.", unit.getPath().getPath());
201         }
202 
203         clear(unit.getPath());
204     }
205 }