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-2016 UnboundID Corp.
026 */
027package com.unboundid.directory.sdk.common.types;
028
029
030import java.io.File;
031import java.io.FileFilter;
032import java.io.FileNotFoundException;
033import java.io.IOException;
034import 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 */
041public 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}