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