001/* 002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu 018 * Kevin Leturc <[email protected]> 019 */ 020package org.nuxeo.runtime.reload; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.lang.reflect.Field; 026import java.net.MalformedURLException; 027import java.net.URL; 028import java.nio.file.Files; 029import java.nio.file.Path; 030import java.nio.file.StandardCopyOption; 031import java.util.ArrayList; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Map.Entry; 036import java.util.Optional; 037import java.util.concurrent.TimeUnit; 038import java.util.jar.JarFile; 039import java.util.jar.Manifest; 040import java.util.stream.Collectors; 041import java.util.stream.Stream; 042 043import javax.transaction.Transaction; 044 045import org.apache.commons.io.FileUtils; 046import org.apache.logging.log4j.LogManager; 047import org.apache.logging.log4j.Logger; 048import org.nuxeo.common.Environment; 049import org.nuxeo.common.utils.JarUtils; 050import org.nuxeo.common.utils.ZipUtils; 051import org.nuxeo.osgi.application.DevMutableClassLoader; 052import org.nuxeo.runtime.RuntimeServiceException; 053import org.nuxeo.runtime.api.Framework; 054import org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor; 055import org.nuxeo.runtime.model.ComponentContext; 056import org.nuxeo.runtime.model.ComponentManager; 057import org.nuxeo.runtime.model.DefaultComponent; 058import org.nuxeo.runtime.services.event.Event; 059import org.nuxeo.runtime.services.event.EventService; 060import org.nuxeo.runtime.transaction.TransactionHelper; 061import org.nuxeo.runtime.util.Watch; 062import org.osgi.framework.Bundle; 063import org.osgi.framework.BundleContext; 064import org.osgi.framework.BundleException; 065import org.osgi.framework.ServiceReference; 066import org.osgi.service.packageadmin.PackageAdmin; 067 068/** 069 * @author <a href="mailto:[email protected]">Bogdan Stefanescu</a> 070 */ 071public class ReloadComponent extends DefaultComponent implements ReloadService { 072 073 /** 074 * The reload strategy to adopt for hot reload. Default value is {@link #RELOAD_STRATEGY_VALUE_DEFAULT}. 075 * 076 * @since 9.3 077 */ 078 public static final String RELOAD_STRATEGY_PARAMETER = "org.nuxeo.runtime.reload_strategy"; 079 080 public static final String RELOAD_STRATEGY_VALUE_UNSTASH = "unstash"; 081 082 public static final String RELOAD_STRATEGY_VALUE_STANDBY = "standby"; 083 084 public static final String RELOAD_STRATEGY_VALUE_RESTART = "restart"; 085 086 public static final String RELOAD_STRATEGY_VALUE_DEFAULT = RELOAD_STRATEGY_VALUE_STANDBY; 087 088 private static final Logger log = LogManager.getLogger(ReloadComponent.class); 089 090 protected static Bundle bundle; 091 092 protected Long lastFlushed; 093 094 public static BundleContext getBundleContext() { 095 return bundle.getBundleContext(); 096 } 097 098 public static Bundle getBundle() { 099 return bundle; 100 } 101 102 @Override 103 public void activate(ComponentContext context) { 104 super.activate(context); 105 bundle = context.getRuntimeContext().getBundle(); 106 } 107 108 @Override 109 public void deactivate(ComponentContext context) { 110 super.deactivate(context); 111 bundle = null; 112 } 113 114 /** 115 * @deprecated since 9.3, this method is only used in deployBundles and undeployBundles which are deprecated. Keep 116 * it for backward compatibility. 117 */ 118 @Deprecated 119 protected void refreshComponents() { 120 String reloadStrategy = Framework.getProperty(RELOAD_STRATEGY_PARAMETER, RELOAD_STRATEGY_VALUE_DEFAULT); 121 log.info("Refresh components. Strategy={}", reloadStrategy); 122 // reload components / contributions 123 ComponentManager mgr = Framework.getRuntime().getComponentManager(); 124 switch (reloadStrategy) { 125 case RELOAD_STRATEGY_VALUE_UNSTASH: 126 // compat mode 127 mgr.unstash(); 128 break; 129 case RELOAD_STRATEGY_VALUE_STANDBY: 130 // standby / resume 131 mgr.standby(); 132 mgr.unstash(); 133 mgr.resume(); 134 break; 135 case RELOAD_STRATEGY_VALUE_RESTART: 136 default: 137 // restart mode 138 mgr.refresh(false); 139 break; 140 } 141 } 142 143 @Override 144 public void reload() throws InterruptedException { 145 log.debug("Starting reload"); 146 147 try { 148 reloadProperties(); 149 } catch (IOException e) { 150 throw new RuntimeServiceException(e); 151 } 152 153 triggerReloadWithNewTransaction(RELOAD_EVENT_ID); 154 } 155 156 @Override 157 public void reloadProperties() throws IOException { 158 log.info("Before reload runtime properties"); 159 Framework.getRuntime().reloadProperties(); 160 log.info("After reload runtime properties"); 161 } 162 163 @Override 164 public void reloadSeamComponents() { 165 log.info("Reload Seam components"); 166 Framework.getService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, RELOAD_SEAM_EVENT_ID, this, null)); 167 } 168 169 @Override 170 public void flush() { 171 log.info("Before flush caches"); 172 Framework.getService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, FLUSH_EVENT_ID, this, null)); 173 flushJaasCache(); 174 setFlushedNow(); 175 log.info("After flush caches"); 176 } 177 178 @Override 179 public void flushJaasCache() { 180 log.info("Before flush the JAAS cache"); 181 Framework.getService(EventService.class).sendEvent(new Event("usermanager", "user_changed", this, "Deployer")); 182 setFlushedNow(); 183 log.info("After flush the JAAS cache"); 184 } 185 186 @Override 187 public void flushSeamComponents() { 188 log.info("Flush Seam components"); 189 Framework.getService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, FLUSH_SEAM_EVENT_ID, this, null)); 190 setFlushedNow(); 191 } 192 193 /** 194 * @deprecated since 9.3 use {@link #reloadBundles(ReloadContext)} instead. 195 */ 196 @Override 197 @Deprecated 198 public void deployBundles(List<File> files, boolean reloadResources) throws BundleException { 199 long begin = System.currentTimeMillis(); 200 List<String> missingNames = files.stream() 201 .filter(file -> getOSGIBundleName(file) == null) 202 .map(File::getAbsolutePath) 203 .collect(Collectors.toList()); 204 if (!missingNames.isEmpty()) { 205 missingNames.forEach(name -> log.error("No Bundle-SymbolicName found in MANIFEST for jar at '{}'", name)); 206 // TODO investigate why we need to exit here, getBundleContext().installBundle(path) will throw an exception 207 // unless, maybe tests ? 208 return; 209 } 210 211 log.info(() -> { 212 StringBuilder builder = new StringBuilder("Before deploy bundles\n"); 213 Framework.getRuntime().getStatusMessage(builder); 214 return builder.toString(); 215 }); 216 217 // Reload resources 218 if (reloadResources) { 219 List<URL> urls = files.stream().map(this::toURL).collect(Collectors.toList()); 220 Framework.reloadResourceLoader(urls, null); 221 } 222 223 // Deploy bundles 224 Transaction tx = TransactionHelper.suspendTransaction(); 225 try { 226 _deployBundles(files); 227 refreshComponents(); 228 } finally { 229 TransactionHelper.resumeTransaction(tx); 230 } 231 232 log.info(() -> { 233 StringBuilder builder = new StringBuilder("After deploy bundles.\n"); 234 Framework.getRuntime().getStatusMessage(builder); 235 return builder.toString(); 236 }); 237 log.info("Hot deploy was done in {} ms.", System.currentTimeMillis() - begin); 238 } 239 240 /** 241 * @deprecated since 9.3 use {@link #reloadBundles(ReloadContext)} instead. 242 */ 243 @Override 244 @Deprecated 245 public void undeployBundles(List<String> bundleNames, boolean reloadResources) throws BundleException { 246 long begin = System.currentTimeMillis(); 247 log.info(() -> { 248 StringBuilder builder = new StringBuilder("Before undeploy bundles\n"); 249 Framework.getRuntime().getStatusMessage(builder); 250 return builder.toString(); 251 }); 252 253 // Undeploy bundles 254 Transaction tx = TransactionHelper.suspendTransaction(); 255 ReloadResult result = new ReloadResult(); 256 try { 257 result.merge(_undeployBundles(bundleNames)); 258 refreshComponents(); 259 } finally { 260 TransactionHelper.resumeTransaction(tx); 261 } 262 263 // Reload resources 264 if (reloadResources) { 265 List<URL> undeployedBundleURLs = result.undeployedBundles.stream().map(this::toURL).collect( 266 Collectors.toList()); 267 Framework.reloadResourceLoader(null, undeployedBundleURLs); 268 } 269 270 log.info(() -> { 271 StringBuilder builder = new StringBuilder("After undeploy bundles.\n"); 272 Framework.getRuntime().getStatusMessage(builder); 273 return builder.toString(); 274 }); 275 log.info("Hot undeploy was done in {} ms.", System.currentTimeMillis() - begin); 276 } 277 278 @Override 279 public synchronized ReloadResult reloadBundles(ReloadContext context) throws BundleException { 280 ReloadResult result = new ReloadResult(); 281 List<String> bundlesNamesToUndeploy = context.bundlesNamesToUndeploy; 282 283 Watch watch = new Watch(new LinkedHashMap<>()).start(); 284 log.info(() -> { 285 StringBuilder builder = new StringBuilder("Before updating Nuxeo server\n"); 286 Framework.getRuntime().getStatusMessage(builder); 287 return builder.toString(); 288 }); 289 // get class loader 290 Optional<DevMutableClassLoader> classLoader = Optional.of(getClass().getClassLoader()) 291 .filter(DevMutableClassLoader.class::isInstance) 292 .map(DevMutableClassLoader.class::cast); 293 294 watch.start("flush"); 295 flush(); 296 watch.stop("flush"); 297 298 // Suspend current transaction 299 Transaction tx = TransactionHelper.suspendTransaction(); 300 301 try { 302 // Stop or Standby the component manager 303 ComponentManager componentManager = Framework.getRuntime().getComponentManager(); 304 String reloadStrategy = Framework.getProperty(RELOAD_STRATEGY_PARAMETER, RELOAD_STRATEGY_VALUE_DEFAULT); 305 log.info("Component reload strategy={}", reloadStrategy); 306 307 watch.start("stop/standby"); 308 log.info("Before stop/standby component manager"); 309 if (RELOAD_STRATEGY_VALUE_RESTART.equals(reloadStrategy)) { 310 componentManager.stop(); 311 } else { 312 // standby strategy by default 313 componentManager.standby(); 314 } 315 log.info("After stop/standby component manager"); 316 watch.stop("stop/standby"); 317 318 // Undeploy bundles 319 if (!bundlesNamesToUndeploy.isEmpty()) { 320 watch.start("undeploy-bundles"); 321 log.info("Before undeploy bundles"); 322 logComponentManagerStatus(); 323 324 result.merge(_undeployBundles(bundlesNamesToUndeploy)); 325 clearJarFileFactoryCache(result); 326 componentManager.unstash(); 327 328 // Clear the class loader 329 classLoader.ifPresent(DevMutableClassLoader::clearPreviousClassLoader); 330 // TODO shall we do a GC here ? see DevFrameworkBootstrap#clearClassLoader 331 332 log.info("After undeploy bundles"); 333 logComponentManagerStatus(); 334 watch.stop("undeploy-bundles"); 335 } 336 337 watch.start("delete-copy"); 338 // Delete old bundles 339 log.info("Before delete-copy"); 340 List<URL> urlsToRemove = result.undeployedBundles.stream() 341 .map(Bundle::getLocation) 342 .map(File::new) 343 .peek(File::delete) 344 .map(this::toURL) 345 .collect(Collectors.toList()); 346 // Then copy new ones 347 List<File> bundlesToDeploy = copyBundlesToDeploy(context); 348 List<URL> urlsToAdd = bundlesToDeploy.stream().map(this::toURL).collect(Collectors.toList()); 349 log.info("After delete-copy"); 350 watch.stop("delete-copy"); 351 352 // Reload resources 353 watch.start("reload-resources"); 354 Framework.reloadResourceLoader(urlsToAdd, urlsToRemove); 355 watch.stop("reload-resources"); 356 357 // Deploy bundles 358 if (!bundlesToDeploy.isEmpty()) { 359 watch.start("deploy-bundles"); 360 log.info("Before deploy bundles"); 361 logComponentManagerStatus(); 362 363 // Fill the class loader 364 classLoader.ifPresent(cl -> cl.addClassLoader(urlsToAdd.toArray(new URL[0]))); 365 366 result.merge(_deployBundles(bundlesToDeploy)); 367 componentManager.unstash(); 368 369 log.info("After deploy bundles"); 370 logComponentManagerStatus(); 371 watch.stop("deploy-bundles"); 372 } 373 374 // Start or Resume the component manager 375 watch.start("start/resume"); 376 log.info("Before start/resume component manager"); 377 if (RELOAD_STRATEGY_VALUE_RESTART.equals(reloadStrategy)) { 378 componentManager.start(); 379 } else { 380 // standby strategy by default 381 componentManager.resume(); 382 } 383 log.info("After start/resume component manager"); 384 watch.stop("start/resume"); 385 386 try { 387 // run deployment preprocessor 388 watch.start("deployment-preprocessor"); 389 runDeploymentPreprocessor(); 390 watch.stop("deployment-preprocessor"); 391 } catch (IOException e) { 392 throw new BundleException("Unable to run deployment preprocessor", e); 393 } 394 395 try { 396 // reload 397 watch.start("reload-properties"); 398 reloadProperties(); 399 watch.stop("reload-properties"); 400 } catch (IOException e) { 401 throw new BundleException("Unable to reload properties", e); 402 } 403 } finally { 404 TransactionHelper.resumeTransaction(tx); 405 } 406 407 log.info(() -> { 408 StringBuilder builder = new StringBuilder("After updating Nuxeo server\n"); 409 Framework.getRuntime().getStatusMessage(builder); 410 return builder.toString(); 411 }); 412 413 watch.stop(); 414 log.info("Hot reload was done in {} ms, detailed steps:\n{}", 415 () -> watch.getTotal().elapsed(TimeUnit.MILLISECONDS), 416 () -> Stream.of(watch.getIntervals()) 417 .map(i -> "- " + i.getName() + ": " + i.elapsed(TimeUnit.MILLISECONDS) + " ms") 418 .collect(Collectors.joining("\n"))); 419 return result; 420 } 421 422 protected List<File> copyBundlesToDeploy(ReloadContext context) throws BundleException { 423 List<File> bundlesToDeploy = new ArrayList<>(); 424 Path homePath = Framework.getRuntime().getHome().toPath(); 425 Path destinationPath = homePath.resolve(context.bundlesDestination); 426 try { 427 Files.createDirectories(destinationPath); 428 for (File bundle : context.bundlesToDeploy) { 429 Path bundlePath = bundle.toPath(); 430 // check if the bundle is located under the desired destination 431 // if not copy it to the desired destination 432 if (!bundlePath.startsWith(destinationPath)) { 433 if (Files.isDirectory(bundlePath)) { 434 // If it's a directory, assume that it's an exploded jar 435 bundlePath = JarUtils.zipDirectory(bundlePath, 436 destinationPath.resolve("hotreload-bundle-" + System.currentTimeMillis() + ".jar"), 437 StandardCopyOption.REPLACE_EXISTING); 438 } else { 439 bundlePath = destinationPath.resolve(bundle.getName()); 440 // JDK nio Files will replace the existing file (if destination already exists) which is an 441 // an issue on Windows cause you can't replace a file used by the JVM 442 // so use commons-io instead because it will override the content by using a FileInputStream 443 // instead of replacing the file 444 FileUtils.copyFile(bundle, bundlePath.toFile(), false); 445 } 446 } 447 bundlesToDeploy.add(bundlePath.toFile()); 448 } 449 return bundlesToDeploy; 450 } catch (IOException e) { 451 throw new BundleException("Unable to copy bundles to " + destinationPath, e); 452 } 453 } 454 455 /* 456 * TODO Change this method name when deployBundles will be removed. 457 */ 458 protected ReloadResult _deployBundles(List<File> bundlesToDeploy) throws BundleException { 459 ReloadResult result = new ReloadResult(); 460 BundleContext bundleContext = getBundleContext(); 461 for (File file : bundlesToDeploy) { 462 String path = file.getAbsolutePath(); 463 log.info("Before deploy bundle for file at '{}'", path); 464 Bundle bundle = bundleContext.installBundle(path); 465 if (bundle == null) { 466 // TODO check why this is necessary, our implementation always return sth 467 throw new IllegalArgumentException("Could not find a valid bundle at path: " + path); 468 } 469 bundle.start(); 470 result.deployedBundles.add(bundle); 471 log.info("Deploy done for bundle with name '{}'", bundle.getSymbolicName()); 472 } 473 return result; 474 } 475 476 /* 477 * TODO Change this method name when undeployBundles will be removed. 478 */ 479 protected ReloadResult _undeployBundles(List<String> bundleNames) throws BundleException { 480 ReloadResult result = new ReloadResult(); 481 BundleContext ctx = getBundleContext(); 482 ServiceReference ref = ctx.getServiceReference(PackageAdmin.class.getName()); 483 PackageAdmin srv = (PackageAdmin) ctx.getService(ref); 484 try { 485 for (String bundleName : bundleNames) { 486 for (Bundle bundle : srv.getBundles(bundleName, null)) { 487 if (bundle != null && bundle.getState() == Bundle.ACTIVE) { 488 log.info("Before undeploy bundle with name '{}'.", bundleName); 489 bundle.stop(); 490 bundle.uninstall(); 491 result.undeployedBundles.add(bundle); 492 log.info("After undeploy bundle with name '{}'.", bundleName); 493 } 494 } 495 } 496 } finally { 497 ctx.ungetService(ref); 498 } 499 return result; 500 } 501 502 /** 503 * Gets the un-deployed bundle from given {@link ReloadResult result} and try to remove them from 504 * {@link sun.net.www.protocol.jar.JarFileFactory} otherwise we'll have resource conflict when opening 505 * {@link InputStream stream} from {@link URL url}. 506 */ 507 @SuppressWarnings({ "unchecked", "SynchronizationOnLocalVariableOrMethodParameter" }) 508 protected void clearJarFileFactoryCache(ReloadResult result) { 509 try { 510 List<String> jarLocations = result.undeployedBundlesAsStream().map(Bundle::getLocation).collect( 511 Collectors.toList()); 512 log.debug("Clear JarFileFactory caches for jars={}", jarLocations); 513 Class jarFileFactory = Class.forName("sun.net.www.protocol.jar.JarFileFactory"); 514 515 Field factoryInstanceField = jarFileFactory.getDeclaredField("instance"); 516 factoryInstanceField.setAccessible(true); 517 Object factoryInstance = factoryInstanceField.get(null); 518 519 Field fileCacheField = jarFileFactory.getDeclaredField("fileCache"); 520 fileCacheField.setAccessible(true); 521 Map<String, JarFile> fileCache = (Map<String, JarFile>) fileCacheField.get(null); 522 523 Field urlCacheField = jarFileFactory.getDeclaredField("urlCache"); 524 urlCacheField.setAccessible(true); 525 Map<JarFile, URL> urlCache = (Map<JarFile, URL>) urlCacheField.get(null); 526 527 synchronized (factoryInstance) { 528 // collect keys of cache 529 List<JarFile> urlCacheRemoveKeys = new ArrayList<>(); 530 for (Entry<JarFile, URL> entry : urlCache.entrySet()) { 531 JarFile jarFile = entry.getKey(); 532 if (jarLocations.stream().anyMatch(jar -> jar.startsWith(jarFile.getName()))) { 533 urlCacheRemoveKeys.add(jarFile); 534 } 535 } 536 537 List<String> fileCacheRemoveKeys = new ArrayList<>(); 538 for (Entry<String, JarFile> entry : fileCache.entrySet()) { 539 if (urlCacheRemoveKeys.contains(entry.getValue())) { 540 fileCacheRemoveKeys.add(entry.getKey()); 541 } 542 } 543 544 // now remove from factory 545 for (String fileCacheRemoveKey : fileCacheRemoveKeys) { 546 JarFile remove = fileCache.remove(fileCacheRemoveKey); 547 if (remove != null) { 548 log.trace("Removed item from fileCache={}", remove); 549 } 550 } 551 552 for (JarFile urlCacheRemoveKey : urlCacheRemoveKeys) { 553 URL remove = urlCache.remove(urlCacheRemoveKey); 554 try { 555 urlCacheRemoveKey.close(); 556 } catch (IOException e) { 557 log.info("Unable to close JarFile={}", urlCacheRemoveKey, e); 558 } 559 if (remove != null) { 560 log.trace("Removed item from urlCache={}", remove); 561 } 562 } 563 } 564 } catch (ReflectiveOperationException | ClassCastException e) { 565 log.error("Unable to clear JarFileFactory, you might need to restart Nuxeo", e); 566 } 567 } 568 569 /** 570 * This method needs to be called before bundle uninstallation, otherwise {@link Bundle#getLocation()} throw a NPE. 571 */ 572 protected URL toURL(Bundle bundle) { 573 String location = bundle.getLocation(); 574 File file = new File(location); 575 return toURL(file); 576 } 577 578 protected URL toURL(File file) { 579 try { 580 return file.toURI().toURL(); 581 } catch (MalformedURLException e) { 582 throw new RuntimeServiceException(e); 583 } 584 } 585 586 /** 587 * Logs the {@link ComponentManager} status. 588 */ 589 protected void logComponentManagerStatus() { 590 log.debug(() -> { 591 StringBuilder builder = new StringBuilder("ComponentManager status:\n"); 592 Framework.getRuntime().getStatusMessage(builder); 593 return builder.toString(); 594 }); 595 } 596 597 @Override 598 public Long lastFlushed() { 599 return lastFlushed; 600 } 601 602 /** 603 * Sets the last date date to current date timestamp 604 * 605 * @since 5.6 606 */ 607 protected void setFlushedNow() { 608 lastFlushed = Long.valueOf(System.currentTimeMillis()); 609 } 610 611 /** 612 * @deprecated since 5.6, use {@link #runDeploymentPreprocessor()} instead. Keep it as compatibility code until 613 * NXP-9642 is done. 614 */ 615 @Override 616 @Deprecated 617 public void installWebResources(File file) throws IOException { 618 log.info("Install web resources"); 619 if (file.isDirectory()) { 620 File war = new File(file, "web"); 621 war = new File(war, "nuxeo.war"); 622 if (war.isDirectory()) { 623 org.nuxeo.common.utils.FileUtils.copyTree(war, getAppDir()); 624 } else { 625 // compatibility mode with studio 1.5 - see NXP-6186 626 war = new File(file, "nuxeo.war"); 627 if (war.isDirectory()) { 628 org.nuxeo.common.utils.FileUtils.copyTree(war, getAppDir()); 629 } 630 } 631 } else if (file.isFile()) { // a jar 632 File war = getWarDir(); 633 ZipUtils.unzip("web/nuxeo.war", file, war); 634 // compatibility mode with studio 1.5 - see NXP-6186 635 ZipUtils.unzip("nuxeo.war", file, war); 636 } 637 } 638 639 @Override 640 public void runDeploymentPreprocessor() throws IOException { 641 log.info("Start running deployment preprocessor"); 642 String rootPath = Environment.getDefault().getRuntimeHome().getAbsolutePath(); 643 File root = new File(rootPath); 644 DeploymentPreprocessor processor = new DeploymentPreprocessor(root); 645 // initialize 646 processor.init(); 647 // and predeploy 648 processor.predeploy(); 649 log.info("Deployment preprocessing done"); 650 } 651 652 protected static File getAppDir() { 653 return Environment.getDefault().getConfig().getParentFile(); 654 } 655 656 protected static File getWarDir() { 657 return new File(getAppDir(), "nuxeo.war"); 658 } 659 660 @Override 661 public String getOSGIBundleName(File file) { 662 Manifest mf = JarUtils.getManifest(file); 663 if (mf == null) { 664 return null; 665 } 666 String bundleName = mf.getMainAttributes().getValue("Bundle-SymbolicName"); 667 if (bundleName == null) { 668 return null; 669 } 670 int index = bundleName.indexOf(';'); 671 if (index > -1) { 672 bundleName = bundleName.substring(0, index); 673 } 674 return bundleName; 675 } 676 677 /** 678 * @deprecated since 9.3 should not be needed anymore 679 */ 680 @Deprecated 681 protected void triggerReloadWithNewTransaction(String eventId) { 682 if (TransactionHelper.isTransactionMarkedRollback()) { 683 throw new AssertionError("The calling transaction is marked rollback"); 684 } 685 // we need to commit or rollback transaction because suspending it leads to a lock/errors when acquiring a new 686 // connection during the datasource reload 687 boolean hasTransaction = TransactionHelper.isTransactionActiveOrMarkedRollback(); 688 if (hasTransaction) { 689 TransactionHelper.commitOrRollbackTransaction(); 690 } 691 try { 692 TransactionHelper.runInTransaction(() -> triggerReload(eventId)); 693 } finally { 694 // start a new transaction only if one already existed 695 // this is because there's no user transaction when coming from SDK 696 if (hasTransaction) { 697 TransactionHelper.startTransaction(); 698 } 699 } 700 } 701 702 /** 703 * @deprecated since 9.3 should not be needed anymore 704 */ 705 @Deprecated 706 protected void triggerReload(String eventId) { 707 log.info("About to send reload event for id: {}", eventId); 708 Framework.getService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, BEFORE_RELOAD_EVENT_ID, this, null)); 709 try { 710 Framework.getService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, eventId, this, null)); 711 } finally { 712 Framework.getService(EventService.class) 713 .sendEvent(new Event(RELOAD_TOPIC, AFTER_RELOAD_EVENT_ID, this, null)); 714 log.info("Returning from reload for event id: {}", eventId); 715 } 716 } 717}