View Javadoc

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