View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.ArrayList;
6   import java.util.Collection;
7   import java.util.Collections;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  
12  import com.atlassian.plugin.PluginException;
13  import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
14  import com.atlassian.plugin.loaders.classloading.Scanner;
15  
16  import org.apache.commons.io.FileUtils;
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  import static com.google.common.base.Preconditions.checkArgument;
21  import static com.google.common.base.Preconditions.checkNotNull;
22  
23  /**
24   * A Scanner which polls a file containing a list of file names to define its content.
25   *
26   * @since 3.0.24
27   */
28  public class RosterFileScanner implements Scanner
29  {
30      private static final Logger log = LoggerFactory.getLogger(RosterFileScanner.class);
31  
32      final private File rosterFile;
33      private Map<String, DeploymentUnit> deploymentUnits;
34      private long lastModified;
35  
36      /**
37       * Create a roster file scanner which loads the given roster file to locate plugins.
38       * <p/>
39       * The provided roster file format is determined by its file suffix. Currently supported suffixes are
40       * - {@link #getListSuffix()}: a list of file paths, one per line
41       * If the format is not recognized, an {@link IllegalArgumentException} is thrown. This can be checked before construction
42       * using {@link #isKnownRosterFileFormat(File)}.
43       *
44       * @param rosterFile the file to load the plugin roster from.
45       * @throws IllegalArgumentException if the suffix of rosterFile is not a supported file format
46       */
47      public RosterFileScanner(final File rosterFile)
48      {
49          checkNotNull(rosterFile);
50          checkArgument(isKnownRosterFileFormat(rosterFile), "Roster file '%s' does not end with '%s'", rosterFile, getListSuffix());
51          this.rosterFile = rosterFile;
52          this.deploymentUnits = Collections.emptyMap();
53      }
54  
55      @Override
56      public Collection<DeploymentUnit> scan()
57      {
58          try
59          {
60              final List<DeploymentUnit> scanned = new ArrayList<DeploymentUnit>();
61              final long updatedLastModified = rosterFile.lastModified();
62              if ((updatedLastModified != 0) && (updatedLastModified != lastModified))
63              {
64                  // File exists and has changed, reload to implement the scan
65                  // These semantics are by analogy with DirectoryScanner, that we forget plugins that drop out of the list
66                  // FileUtils is not generic safe
67                  //noinspection unchecked
68                  final List<String> filePaths = (List<String>) FileUtils.readLines(rosterFile);
69                  final Map<String, DeploymentUnit> updatedDeploymentUnits = new HashMap<String, DeploymentUnit>(filePaths.size());
70                  for(final String filePath : filePaths)
71                  {
72                      final DeploymentUnit priorUnit = deploymentUnits.get(filePath);
73                      if (null == priorUnit)
74                      {
75                          // It's new, make a new DeploymentUnit and include it in the scan.
76                          final File file = new File(filePath);
77                          final File absoluteFile = file.isAbsolute() ? file : new File(rosterFile.getParentFile(), filePath);
78                          final DeploymentUnit deploymentUnit = new DeploymentUnit(absoluteFile);
79                          updatedDeploymentUnits.put(filePath, deploymentUnit);
80                          scanned.add(deploymentUnit);
81                      }
82                      else
83                      {
84                          // Keep the deployment unit from the previous run
85                          updatedDeploymentUnits.put(filePath, priorUnit);
86                      }
87                  }
88                  deploymentUnits = updatedDeploymentUnits;
89                  lastModified = updatedLastModified;
90                  return scanned;
91              }
92          }
93          catch (final IOException eio)
94          {
95              log.warn("Cannot read roster file '{}': {}", rosterFile.getAbsolutePath(), eio.getMessage());
96          }
97          // unreadable, unchanged or error - report nothing new
98          return Collections.emptyList();
99      }
100 
101     @Override
102     public Collection<DeploymentUnit> getDeploymentUnits()
103     {
104         return Collections.unmodifiableCollection(deploymentUnits.values());
105     }
106 
107     @Override
108     public void reset()
109     {
110         deploymentUnits = Collections.emptyMap();
111         lastModified = 0;
112     }
113 
114     @Override
115     public void remove(final DeploymentUnit deploymentUnit) throws PluginException
116     {
117         // Remove is called during the plugin upgrade process, but since we don't own the files, we don't actually remove them.
118         // In fact, they're in our software tree and hence read-only anyway, so we can't remove them.
119         // We just ignore remove - while we could in principle track what we'd seen, that's a chunk of logic for not much gain.
120     }
121 
122     /**
123      * The suffix for a roster file containing a simple plugin list.
124      * <p/>
125      * Roster files with this suffix have one literal path per line. Relative paths are resolved relative to the directory
126      * containing the list file. Entries are considered distinct or not based on the path used in the file - no canonicalization
127      * is forced.
128      *
129      * @return the suffix used for roster files containing just a list of plugin jar files.
130      */
131     public static String getListSuffix()
132     {
133         return ".list";
134     }
135 
136     /**
137      * Check whether a proposed roster file is in a known format.
138      *
139      * @param rosterFile the roster file to check.
140      * @return true if the format of the roster file is known.
141      * @see RosterFileScanner
142      */
143     public static boolean isKnownRosterFileFormat(final File rosterFile)
144     {
145         return rosterFile.getName().endsWith(getListSuffix());
146     }
147 }