001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * docs/licenses/cddl.txt
011     * or http://www.opensource.org/licenses/cddl1.php.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * docs/licenses/cddl.txt.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2011-2013 UnboundID Corp.
026     */
027    package com.unboundid.directory.sdk.common.types;
028    
029    
030    import java.io.File;
031    import java.io.FileFilter;
032    import java.io.FileNotFoundException;
033    import java.io.IOException;
034    import java.util.jar.JarFile;
035    
036    /**
037     * This class represents the physical state of an UnboundID Server
038     * extension bundle.  All the operations are dependent upon the root directory
039     * that is specified in the constructor.
040     */
041    public class ExtensionBundle
042    {
043      /**
044       * The name of the jar file manifest attribute that provides the extension
045       * bundle name.
046       */
047      public static final String MANIFEST_ATTR_TITLE =
048           "Implementation-Title";
049    
050    
051    
052      /**
053       * The name of the jar file manifest attribute that provides the extension
054       * bundle version.
055       */
056      public static final String MANIFEST_ATTR_VERSION =
057           "Implementation-Version";
058    
059    
060    
061      /**
062       * The name of the jar file manifest attribute that provides the extension
063       * bundle vendor.
064       */
065      public static final String MANIFEST_ATTR_VENDOR =
066           "Implementation-Vendor";
067    
068    
069    
070      /**
071       * The name of the jar file manifest attribute that provides the extension
072       * bundle vendor ID.
073       */
074      public static final String MANIFEST_ATTR_VENDOR_ID =
075           "Implementation-Vendor-Id";
076    
077    
078    
079      /**
080       * The name of the jar file manifest attribute that provides the extension
081       * bundle URL.
082       */
083      public static final String MANIFEST_ATTR_URL =
084           "Implementation-URL";
085    
086    
087    
088      /**
089       * The name of the jar file manifest attribute that provides the extension
090       * bundle support contact.
091       */
092      public static final String MANIFEST_ATTR_SUPPORT_CONTACT =
093           "Extension-Support-Contact";
094    
095    
096    
097      /**
098       * The name of the jar file manifest attribute that provides the server SDK
099       * version number.
100       */
101      public static final String MANIFEST_ATTR_SERVER_SDK_VERSION =
102           "UnboundID-Server-SDK-Version";
103    
104      /**
105       * The relative path where the config files are.
106       */
107      public static final String CONFIG_PATH_RELATIVE = "config";
108    
109      /**
110       * The relative path where all the libraries (jar files) are.
111       */
112      public static final String LIBRARIES_PATH_RELATIVE = "lib";
113    
114      /**
115       * The relative path where all the documentation files are.
116       */
117      public static final String DOCUMENTATION_PATH_RELATIVE = "docs";
118    
119      /**
120       * The relative path where the config files are.
121       */
122      public static final String HISTORY_PATH_RELATIVE = "history";
123    
124      /**
125       * Path to the config/update directory where update base files
126       * are stored.
127       */
128      public static final String UPDATE_PATH = "update";
129    
130      /**
131       * File written to history/[timestamp]-[version] to document whatever
132       * modifications were made to the file system during an update.
133       */
134      public static final String UPDATE_LOG_NAME = "update.log";
135    
136      // A FileFilter for JAR files.
137      private static final FileFilter JAR_FILTER = new FileFilter()
138      {
139    
140        /**
141         * Must be a Jar file.
142         */
143        public boolean accept(final File pathname)
144        {
145          if (!pathname.isFile())
146          {
147            return false;
148          }
149    
150          String name = pathname.getName();
151          return name.endsWith(".jar");
152        }
153    
154      };
155    
156      private final File extensionBundleDir;
157      private final File extensionJarFile;
158      private final java.util.jar.Manifest manifest;
159      private final String vendorId;
160      private final String title;
161      private final String version;
162      private final String bundleId;
163      private final String sdkVersion;
164    
165      /**
166       * Creates a new instance from a root directory specified as a File.
167       *
168       * @param extensionBundleDir root directory of this extension bundle.
169       * @throws IOException if a error occurs while opening the bundle.
170       * @throws IllegalArgumentException if a error occurs while loading the
171       *         extension JAR.
172       */
173      public ExtensionBundle(final File extensionBundleDir) throws IOException,
174          IllegalArgumentException {
175        File jarFile = null;
176        java.util.jar.Manifest jarManifest = null;
177        String extensionVendorId = null;
178        String extensionTitle = null;
179        String extensionVersion = null;
180        String extensionSdkVersion = null;
181        boolean isValid = false;
182        for(File file : extensionBundleDir.listFiles(JAR_FILTER))
183        {
184          jarFile = file;
185          JarFile jar = new JarFile(file);
186          jarManifest = jar.getManifest();
187          extensionVendorId = jarManifest.getMainAttributes().getValue(
188            MANIFEST_ATTR_VENDOR_ID);
189          extensionTitle = jarManifest.getMainAttributes().getValue(
190            MANIFEST_ATTR_TITLE);
191          extensionVersion = jarManifest.getMainAttributes().getValue(
192            MANIFEST_ATTR_VERSION);
193          extensionSdkVersion = jarManifest.getMainAttributes().getValue(
194              MANIFEST_ATTR_SERVER_SDK_VERSION);
195          if(extensionVendorId == null || extensionVendorId.isEmpty())
196          {
197            continue;
198          }
199          if(extensionTitle == null || extensionTitle.isEmpty())
200          {
201            continue;
202          }
203          if(extensionVersion == null || extensionVersion.isEmpty())
204          {
205            continue;
206          }
207          if(extensionSdkVersion == null || extensionSdkVersion.isEmpty())
208          {
209            continue;
210          }
211          isValid = true;
212          break;
213        }
214        if(!isValid)
215        {
216          throw new FileNotFoundException("Cannot find a valid extension jar in " +
217              "bundle");
218        }
219    
220        checkCompatibility(extensionSdkVersion);
221    
222        this.extensionBundleDir = extensionBundleDir;
223        this.extensionJarFile = jarFile;
224        this.manifest = jarManifest;
225        this.vendorId = extensionVendorId;
226        this.title = extensionTitle;
227        this.version = extensionVersion;
228        this.sdkVersion = extensionSdkVersion;
229        this.bundleId = extensionVendorId + "." + extensionTitle;
230      }
231    
232      /**
233       * Retrieves the title of this extension bundle.
234       *
235       * @return The title of this extension bundle.
236       */
237      public String getTitle()
238      {
239        return title;
240      }
241    
242      /**
243       * Retrieves the vendor name of this extension bundle.
244       *
245       * @return The vendor name of this extension bundle.
246       */
247      public String getVendor()
248      {
249        return manifest.getMainAttributes().getValue(MANIFEST_ATTR_VENDOR);
250      }
251    
252      /**
253       * Retrieves the vendor ID of this extension bundle.
254       *
255       * @return The vendor ID of this extension bundle.
256       */
257      public String getVendorId()
258      {
259        return vendorId;
260      }
261    
262      /**
263       * Retrieves the version string of this extension bundle.
264       *
265       * @return The version string of this extension bundle.
266       */
267      public String getVersion()
268      {
269        return version;
270      }
271    
272      /**
273       * Get the server SDK version number the extensions where built against.
274       *
275       * @return The server SDK version number the extensions where built against.
276       */
277      public String getServerSDKVersion()
278      {
279        return sdkVersion;
280      }
281    
282    
283      /**
284       * Get the URL of the extension bundle.
285       *
286       * @return The URL of the extension bundle.
287       */
288      public String getUrl()
289      {
290        return manifest.getMainAttributes().getValue(MANIFEST_ATTR_URL);
291      }
292    
293      /**
294       * Get the support contact for this extension bundle.
295       *
296       * @return The support contact for this extension bundle.
297       */
298      public String getSupportContact()
299      {
300        return manifest.getMainAttributes().getValue(MANIFEST_ATTR_SUPPORT_CONTACT);
301      }
302    
303      /**
304       * Get the ID string that could be used to uniquely identify this bundle.
305       *
306       * @return The ID string that could be used to uniquely identify this bundle.
307       */
308      public String getBundleId()
309      {
310        return bundleId;
311      }
312    
313      /**
314       * Get the root directory of this extension bundle.
315       *
316       * @return The root directory of this extension bundle.
317       */
318      public File getExtensionBundleDir()
319      {
320        return extensionBundleDir;
321      }
322    
323      /**
324       * Get the extension jar file in the bundle.
325       *
326       * @return The extension jar file in the bundle.
327       */
328      public File getExtensionJarFile()
329      {
330        return extensionJarFile;
331      }
332    
333      /**
334       * Get the configuration directory for this extension bundle.
335       *
336       * @return The configuration directory for this extension bundle.
337       */
338      public File getConfigDir()
339      {
340        return new File(extensionBundleDir, CONFIG_PATH_RELATIVE);
341      }
342    
343      /**
344       * Get the update directory for this extension bundle.
345       *
346       * @return The update directory for this extension bundle.
347       */
348      public File getConfigUpdateDir()
349      {
350        return new File(getConfigDir(), UPDATE_PATH);
351      }
352    
353      /**
354       * Get the lib directory for this extension bundle.
355       *
356       * @return The lib directory for this extension bundle.
357       */
358      public File getLibrariesDir()
359      {
360        return new File(extensionBundleDir, LIBRARIES_PATH_RELATIVE);
361      }
362    
363      /**
364       * Get the documentation directory for this extension bundle.
365       *
366       * @return The documentation directory for this extension bundle.
367       */
368      public File getDocumentationDir()
369      {
370        return new File(extensionBundleDir, DOCUMENTATION_PATH_RELATIVE);
371      }
372    
373      /**
374       * Get the history directory for this extension bundle.
375       *
376       * @return The history directory for this extension bundle.
377       */
378      public File getHistoryDir()
379      {
380        return new File(extensionBundleDir, HISTORY_PATH_RELATIVE);
381      }
382    
383      /**
384       * Checks to make sure the SDK version specified in the extension JAR file
385       * is compatible with this server instance.
386       *
387       * @param serverSDKVersionString The server SDK version string.
388       * @throws IllegalArgumentException if the version string is invalid or
389       * not compatible.
390       */
391      public static void checkCompatibility(final String serverSDKVersionString)
392          throws IllegalArgumentException
393      {
394        final int jarSDKMajor;
395        final int jarSDKMinor;
396        final int jarSDKPoint;
397        final int jarSDKPatch;
398        try
399        {
400          final String[] versionComps = serverSDKVersionString.split("\\.");
401          jarSDKMajor = Integer.parseInt(versionComps[0]);
402          jarSDKMinor = Integer.parseInt(versionComps[1]);
403          jarSDKPoint = Integer.parseInt(versionComps[2]);
404          jarSDKPatch = Integer.parseInt(versionComps[3]);
405        }
406        catch (final Exception e)
407        {
408          throw new IllegalArgumentException("Value '" + serverSDKVersionString +
409              "' for manifest attribute '" + MANIFEST_ATTR_SERVER_SDK_VERSION +
410              "' is malformed", e);
411        }
412    
413        boolean acceptable = true;
414        if (jarSDKMajor > Version.MAJOR_VERSION)
415        {
416          acceptable = false;
417        }
418        else if (jarSDKMajor == Version.MAJOR_VERSION)
419        {
420          if (jarSDKMinor > Version.MINOR_VERSION)
421          {
422            acceptable = false;
423          }
424          else if (jarSDKMinor == Version.MINOR_VERSION)
425          {
426            if (jarSDKPoint > Version.POINT_VERSION)
427            {
428              acceptable = false;
429            }
430            else if (jarSDKPoint == Version.POINT_VERSION)
431            {
432              acceptable = jarSDKPatch <= Version.PATCH_VERSION;
433            }
434          }
435        }
436    
437        if (! acceptable)
438        {
439          final String maxSupportedVersion =
440              Version.MAJOR_VERSION + "." + Version.MINOR_VERSION + '.' +
441                  Version.POINT_VERSION + '.' + Version.PATCH_VERSION;
442          throw new IllegalArgumentException("Extensions built using " +
443              Version.PRODUCT_NAME + " version " + serverSDKVersionString + ", " +
444              "which is newer than the maximum server SDK version of " +
445              maxSupportedVersion);
446        }
447      }
448    }