001/* 002 * (C) Copyright 2006-2017 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 */ 019package org.nuxeo.runtime.reload; 020 021import java.io.File; 022import java.io.IOException; 023import java.net.MalformedURLException; 024import java.net.URL; 025import java.util.Collections; 026import java.util.jar.Manifest; 027 028import javax.transaction.Transaction; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.common.Environment; 033import org.nuxeo.common.utils.FileUtils; 034import org.nuxeo.common.utils.JarUtils; 035import org.nuxeo.common.utils.ZipUtils; 036import org.nuxeo.runtime.RuntimeService; 037import org.nuxeo.runtime.RuntimeServiceException; 038import org.nuxeo.runtime.api.Framework; 039import org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor; 040import org.nuxeo.runtime.model.ComponentContext; 041import org.nuxeo.runtime.model.ComponentManager; 042import org.nuxeo.runtime.model.DefaultComponent; 043import org.nuxeo.runtime.services.event.Event; 044import org.nuxeo.runtime.services.event.EventService; 045import org.nuxeo.runtime.transaction.TransactionHelper; 046import org.osgi.framework.Bundle; 047import org.osgi.framework.BundleContext; 048import org.osgi.framework.BundleException; 049import org.osgi.framework.ServiceReference; 050import org.osgi.service.packageadmin.PackageAdmin; 051 052/** 053 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 054 */ 055public class ReloadComponent extends DefaultComponent implements ReloadService { 056 057 private static final Log log = LogFactory.getLog(ReloadComponent.class); 058 059 protected static Bundle bundle; 060 061 protected Long lastFlushed; 062 063 public static BundleContext getBundleContext() { 064 return bundle.getBundleContext(); 065 } 066 067 public static Bundle getBundle() { 068 return bundle; 069 } 070 071 @Override 072 public void activate(ComponentContext context) { 073 super.activate(context); 074 bundle = context.getRuntimeContext().getBundle(); 075 } 076 077 @Override 078 public void deactivate(ComponentContext context) { 079 super.deactivate(context); 080 bundle = null; 081 } 082 083 protected void refreshComponents() { 084 String reloadStrategy = Framework.getProperty("org.nuxeo.runtime.reload_strategy", "restart"); 085 log.info("Refresh components. Strategy: " + reloadStrategy); 086 // reload components / contributions 087 ComponentManager mgr = Framework.getRuntime().getComponentManager(); 088 if ("unstash".equals(reloadStrategy)) { 089 // compat mode 090 mgr.unstash(); 091 } else if ("standby".equals(reloadStrategy)) { // standby / resume 092 mgr.standby(); 093 mgr.unstash(); 094 mgr.resume(); 095 } else { // restart mode 096 mgr.refresh(false); 097 } 098 } 099 100 @Override 101 public void reload() throws InterruptedException { 102 if (log.isDebugEnabled()) { 103 log.debug("Starting reload"); 104 } 105 106 try { 107 reloadProperties(); 108 } catch (IOException e) { 109 throw new RuntimeServiceException(e); 110 } 111 112 triggerReloadWithNewTransaction(RELOAD_EVENT_ID); 113 } 114 115 @Override 116 public void reloadProperties() throws IOException { 117 log.info("Reload runtime properties"); 118 Framework.getRuntime().reloadProperties(); 119 } 120 121 @Override 122 public void reloadRepository() throws InterruptedException { 123 log.info("Reload repository"); 124 triggerReloadWithNewTransaction(RELOAD_REPOSITORIES_ID); 125 } 126 127 @Override 128 public void reloadSeamComponents() { 129 log.info("Reload Seam components"); 130 Framework.getLocalService(EventService.class) 131 .sendEvent(new Event(RELOAD_TOPIC, RELOAD_SEAM_EVENT_ID, this, null)); 132 } 133 134 @Override 135 public void flush() { 136 log.info("Flush caches"); 137 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, FLUSH_EVENT_ID, this, null)); 138 flushJaasCache(); 139 setFlushedNow(); 140 } 141 142 @Override 143 public void flushJaasCache() { 144 log.info("Flush the JAAS cache"); 145 Framework.getLocalService(EventService.class) 146 .sendEvent(new Event("usermanager", "user_changed", this, "Deployer")); 147 setFlushedNow(); 148 } 149 150 @Override 151 public void flushSeamComponents() { 152 log.info("Flush Seam components"); 153 Framework.getLocalService(EventService.class) 154 .sendEvent(new Event(RELOAD_TOPIC, FLUSH_SEAM_EVENT_ID, this, null)); 155 setFlushedNow(); 156 } 157 158 @Override 159 public String deployBundle(File file) throws BundleException { 160 return deployBundle(file, false); 161 } 162 163 @Override 164 public String deployBundle(File file, boolean reloadResourceClasspath) throws BundleException { 165 String name = getOSGIBundleName(file); 166 if (name == null) { 167 log.error( 168 String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", file.getAbsolutePath())); 169 return null; 170 } 171 172 String path = file.getAbsolutePath(); 173 174 log.info(String.format("Before deploy bundle for file at '%s'\n" + "%s", path, getRuntimeStatus())); 175 176 if (reloadResourceClasspath) { 177 URL url; 178 try { 179 url = new File(path).toURI().toURL(); 180 } catch (MalformedURLException e) { 181 throw new RuntimeException(e); 182 } 183 Framework.reloadResourceLoader(Collections.singletonList(url), null); 184 } 185 186 // check if this is a bundle first 187 Bundle newBundle = getBundleContext().installBundle(path); 188 if (newBundle == null) { 189 throw new IllegalArgumentException("Could not find a valid bundle at path: " + path); 190 } 191 Transaction tx = TransactionHelper.suspendTransaction(); 192 try { 193 newBundle.start(); 194 refreshComponents(); 195 } finally { 196 TransactionHelper.resumeTransaction(tx); 197 } 198 199 log.info(String.format("Deploy done for bundle with name '%s'.\n" + "%s", newBundle.getSymbolicName(), 200 getRuntimeStatus())); 201 202 return newBundle.getSymbolicName(); 203 } 204 205 @Override 206 public void undeployBundle(File file, boolean reloadResources) throws BundleException { 207 String name = getOSGIBundleName(file); 208 String path = file.getAbsolutePath(); 209 if (name == null) { 210 log.error(String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", path)); 211 return; 212 } 213 214 undeployBundle(name); 215 216 if (reloadResources) { 217 URL url; 218 try { 219 url = new File(path).toURI().toURL(); 220 } catch (MalformedURLException e) { 221 throw new RuntimeException(e); 222 } 223 Framework.reloadResourceLoader(null, Collections.singletonList(url)); 224 } 225 } 226 227 @Override 228 public void undeployBundle(String bundleName) throws BundleException { 229 if (bundleName == null) { 230 // ignore 231 return; 232 } 233 log.info(String.format("Before undeploy bundle with name '%s'.\n" + "%s", bundleName, getRuntimeStatus())); 234 BundleContext ctx = getBundleContext(); 235 ServiceReference ref = ctx.getServiceReference(PackageAdmin.class.getName()); 236 PackageAdmin srv = (PackageAdmin) ctx.getService(ref); 237 try { 238 for (Bundle b : srv.getBundles(bundleName, null)) { 239 if (b != null && b.getState() == Bundle.ACTIVE) { 240 Transaction tx = TransactionHelper.suspendTransaction(); 241 try { 242 b.stop(); 243 b.uninstall(); 244 refreshComponents(); 245 } finally { 246 TransactionHelper.resumeTransaction(tx); 247 } 248 } 249 } 250 } finally { 251 ctx.ungetService(ref); 252 } 253 log.info(String.format("Undeploy done.\n" + "%s", getRuntimeStatus())); 254 } 255 256 @Override 257 public Long lastFlushed() { 258 return lastFlushed; 259 } 260 261 /** 262 * Sets the last date date to current date timestamp 263 * 264 * @since 5.6 265 */ 266 protected void setFlushedNow() { 267 lastFlushed = System.currentTimeMillis(); 268 } 269 270 /** 271 * @deprecated since 5.6, use {@link #runDeploymentPreprocessor()} instead. 272 * Keep it as compatibility code until NXP-9642 is done. 273 */ 274 @Override 275 @Deprecated 276 public void installWebResources(File file) throws IOException { 277 log.info("Install web resources"); 278 if (file.isDirectory()) { 279 File war = new File(file, "web"); 280 war = new File(war, "nuxeo.war"); 281 if (war.isDirectory()) { 282 FileUtils.copyTree(war, getAppDir()); 283 } else { 284 // compatibility mode with studio 1.5 - see NXP-6186 285 war = new File(file, "nuxeo.war"); 286 if (war.isDirectory()) { 287 FileUtils.copyTree(war, getAppDir()); 288 } 289 } 290 } else if (file.isFile()) { // a jar 291 File war = getWarDir(); 292 ZipUtils.unzip("web/nuxeo.war", file, war); 293 // compatibility mode with studio 1.5 - see NXP-6186 294 ZipUtils.unzip("nuxeo.war", file, war); 295 } 296 } 297 298 @Override 299 public void runDeploymentPreprocessor() throws IOException { 300 if (log.isDebugEnabled()) { 301 log.debug("Start running deployment preprocessor"); 302 } 303 String rootPath = Environment.getDefault().getRuntimeHome().getAbsolutePath(); 304 File root = new File(rootPath); 305 DeploymentPreprocessor processor = new DeploymentPreprocessor(root); 306 // initialize 307 processor.init(); 308 // and predeploy 309 processor.predeploy(); 310 if (log.isDebugEnabled()) { 311 log.debug("Deployment preprocessing done"); 312 } 313 } 314 315 protected static File getAppDir() { 316 return Environment.getDefault().getConfig().getParentFile(); 317 } 318 319 protected static File getWarDir() { 320 return new File(getAppDir(), "nuxeo.war"); 321 } 322 323 @Override 324 public String getOSGIBundleName(File file) { 325 Manifest mf = JarUtils.getManifest(file); 326 if (mf == null) { 327 return null; 328 } 329 String bundleName = mf.getMainAttributes().getValue("Bundle-SymbolicName"); 330 if (bundleName == null) { 331 return null; 332 } 333 int index = bundleName.indexOf(';'); 334 if (index > -1) { 335 bundleName = bundleName.substring(0, index); 336 } 337 return bundleName; 338 } 339 340 protected String getRuntimeStatus() { 341 StringBuilder msg = new StringBuilder(); 342 RuntimeService runtime = Framework.getRuntime(); 343 runtime.getStatusMessage(msg); 344 return msg.toString(); 345 } 346 347 protected void triggerReloadWithNewTransaction(String id) throws InterruptedException { 348 if (TransactionHelper.isTransactionMarkedRollback()) { 349 throw new AssertionError("The calling transaction is marked rollback"); 350 } 351 // we need to commit or rollback transaction because suspending it leads to a lock/errors when acquiring a new 352 // connection during the datasource reload 353 TransactionHelper.commitOrRollbackTransaction(); 354 TransactionHelper.startTransaction(); 355 try { 356 try { 357 triggerReload(id); 358 } catch (RuntimeException cause) { 359 TransactionHelper.setTransactionRollbackOnly(); 360 throw cause; 361 } finally { 362 TransactionHelper.commitOrRollbackTransaction(); 363 } 364 } finally { 365 TransactionHelper.startTransaction(); 366 } 367 } 368 369 protected void triggerReload(String id) throws InterruptedException { 370 log.info("about to reload for " + id); 371 Framework.getLocalService(EventService.class) 372 .sendEvent(new Event(RELOAD_TOPIC, BEFORE_RELOAD_EVENT_ID, this, null)); 373 try { 374 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, id, this, null)); 375 } finally { 376 Framework.getLocalService(EventService.class) 377 .sendEvent(new Event(RELOAD_TOPIC, AFTER_RELOAD_EVENT_ID, this, null)); 378 log.info("returning from " + id); 379 } 380 } 381}