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}