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