001/* 002 * (C) Copyright 2006-2010 Nuxeo SA (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.Arrays; 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.api.ServicePassivator; 040import org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor; 041import org.nuxeo.runtime.model.ComponentContext; 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 @Override 084 public void reload() { 085 if (log.isDebugEnabled()) { 086 log.debug("Starting reload"); 087 } 088 try { 089 reloadProperties(); 090 } catch (IOException e) { 091 throw new RuntimeServiceException(e); 092 } 093 triggerReloadWithNewTransaction(RELOAD_EVENT_ID); 094 } 095 096 @Override 097 public void reloadProperties() throws IOException { 098 log.info("Reload runtime properties"); 099 Framework.getRuntime().reloadProperties(); 100 } 101 102 @Override 103 public void reloadRepository() { 104 log.info("Reload repository"); 105 triggerReloadWithNewTransaction(RELOAD_REPOSITORIES_ID); 106 } 107 108 @Override 109 public void reloadSeamComponents() { 110 log.info("Reload Seam components"); 111 triggerReload(RELOAD_SEAM_EVENT_ID); 112 } 113 114 @Override 115 public void flush() { 116 log.info("Flush caches"); 117 triggerReloadWithNewTransaction(FLUSH_EVENT_ID); 118 } 119 120 @Override 121 public void flushJaasCache() { 122 log.info("Flush the JAAS cache"); 123 Framework.getLocalService(EventService.class) 124 .sendEvent(new Event("usermanager", "user_changed", this, "Deployer")); 125 setFlushedNow(); 126 } 127 128 @Override 129 public void flushSeamComponents() { 130 log.info("Flush Seam components"); 131 triggerReload(FLUSH_SEAM_EVENT_ID); 132 } 133 134 @Override 135 public String deployBundle(File file) throws BundleException { 136 return deployBundle(file, false); 137 } 138 139 @Override 140 public String deployBundle(File file, boolean reloadResourceClasspath) throws BundleException { 141 String name = getOSGIBundleName(file); 142 if (name == null) { 143 log.error( 144 String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", file.getAbsolutePath())); 145 return null; 146 } 147 148 String path = file.getAbsolutePath(); 149 150 log.info(String.format("Before deploy bundle for file at '%s'\n" + "%s", path, getRuntimeStatus())); 151 152 if (reloadResourceClasspath) { 153 URL url; 154 try { 155 url = new File(path).toURI().toURL(); 156 } catch (MalformedURLException e) { 157 throw new RuntimeException(e); 158 } 159 Framework.reloadResourceLoader(Arrays.asList(url), null); 160 } 161 162 // check if this is a bundle first 163 Bundle newBundle = getBundleContext().installBundle(path); 164 if (newBundle == null) { 165 throw new IllegalArgumentException("Could not find a valid bundle at path: " + path); 166 } 167 Transaction tx = TransactionHelper.suspendTransaction(); 168 try { 169 newBundle.start(); 170 } finally { 171 TransactionHelper.resumeTransaction(tx); 172 } 173 174 log.info(String.format("Deploy done for bundle with name '%s'.\n" + "%s", newBundle.getSymbolicName(), 175 getRuntimeStatus())); 176 177 return newBundle.getSymbolicName(); 178 } 179 180 @Override 181 public void undeployBundle(File file, boolean reloadResources) throws BundleException { 182 String name = getOSGIBundleName(file); 183 String path = file.getAbsolutePath(); 184 if (name == null) { 185 log.error(String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", path)); 186 return; 187 } 188 189 undeployBundle(name); 190 191 if (reloadResources) { 192 URL url; 193 try { 194 url = new File(path).toURI().toURL(); 195 } catch (MalformedURLException e) { 196 throw new RuntimeException(e); 197 } 198 Framework.reloadResourceLoader(null, Arrays.asList(url)); 199 } 200 } 201 202 @Override 203 public void undeployBundle(String bundleName) throws BundleException { 204 if (bundleName == null) { 205 // ignore 206 return; 207 } 208 log.info(String.format("Before undeploy bundle with name '%s'.\n" + "%s", bundleName, getRuntimeStatus())); 209 BundleContext ctx = getBundleContext(); 210 ServiceReference ref = ctx.getServiceReference(PackageAdmin.class.getName()); 211 PackageAdmin srv = (PackageAdmin) ctx.getService(ref); 212 try { 213 for (Bundle b : srv.getBundles(bundleName, null)) { 214 if (b != null && b.getState() == Bundle.ACTIVE) { 215 Transaction tx = TransactionHelper.suspendTransaction(); 216 try { 217 b.stop(); 218 b.uninstall(); 219 } finally { 220 TransactionHelper.resumeTransaction(tx); 221 } 222 } 223 } 224 } finally { 225 ctx.ungetService(ref); 226 } 227 log.info(String.format("Undeploy done.\n" + "%s", getRuntimeStatus())); 228 } 229 230 @Override 231 public Long lastFlushed() { 232 return lastFlushed; 233 } 234 235 /** 236 * Sets the last date date to current date timestamp 237 * 238 * @since 5.6 239 */ 240 protected void setFlushedNow() { 241 lastFlushed = Long.valueOf(System.currentTimeMillis()); 242 } 243 244 /** 245 * @deprecated since 5.6, use {@link #runDeploymentPreprocessor()} instead 246 */ 247 @Override 248 @Deprecated 249 public void installWebResources(File file) throws IOException { 250 log.info("Install web resources"); 251 if (file.isDirectory()) { 252 File war = new File(file, "web"); 253 war = new File(war, "nuxeo.war"); 254 if (war.isDirectory()) { 255 FileUtils.copyTree(war, getAppDir()); 256 } else { 257 // compatibility mode with studio 1.5 - see NXP-6186 258 war = new File(file, "nuxeo.war"); 259 if (war.isDirectory()) { 260 FileUtils.copyTree(war, getAppDir()); 261 } 262 } 263 } else if (file.isFile()) { // a jar 264 File war = getWarDir(); 265 ZipUtils.unzip("web/nuxeo.war", file, war); 266 // compatibility mode with studio 1.5 - see NXP-6186 267 ZipUtils.unzip("nuxeo.war", file, war); 268 } 269 } 270 271 @Override 272 public void runDeploymentPreprocessor() throws IOException { 273 if (log.isDebugEnabled()) { 274 log.debug("Start running deployment preprocessor"); 275 } 276 String rootPath = Environment.getDefault().getRuntimeHome().getAbsolutePath(); 277 File root = new File(rootPath); 278 DeploymentPreprocessor processor = new DeploymentPreprocessor(root); 279 // initialize 280 processor.init(); 281 // and predeploy 282 processor.predeploy(); 283 if (log.isDebugEnabled()) { 284 log.debug("Deployment preprocessing done"); 285 } 286 } 287 288 protected static File getAppDir() { 289 return Environment.getDefault().getConfig().getParentFile(); 290 } 291 292 protected static File getWarDir() { 293 return new File(getAppDir(), "nuxeo.war"); 294 } 295 296 @Override 297 public String getOSGIBundleName(File file) { 298 Manifest mf = JarUtils.getManifest(file); 299 if (mf == null) { 300 return null; 301 } 302 String bundleName = mf.getMainAttributes().getValue("Bundle-SymbolicName"); 303 if (bundleName == null) { 304 return null; 305 } 306 int index = bundleName.indexOf(';'); 307 if (index > -1) { 308 bundleName = bundleName.substring(0, index); 309 } 310 return bundleName; 311 } 312 313 protected String getRuntimeStatus() { 314 StringBuilder msg = new StringBuilder(); 315 RuntimeService runtime = Framework.getRuntime(); 316 runtime.getStatusMessage(msg); 317 return msg.toString(); 318 } 319 320 protected void triggerReload(String id) { 321 if (id.equals(RELOAD_SEAM_EVENT_ID)) { 322 log.info("about to send " + id); 323 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, id, this, null)); 324 return; 325 } 326 log.info("about to passivate for " + id); 327 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, "before-reload", this, null)); 328 try { 329 ServicePassivator 330 .proceed(() -> { 331 log.info("about to send " + id); 332 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, id, this, null)); 333 if (id.startsWith(FLUSH_EVENT_ID)) { 334 setFlushedNow(); 335 } 336 }).onFailure(snapshot -> { 337 throw new UnsupportedOperationException("Detected access, should initiate a reboot " + snapshot.toString()); 338 }); 339 } finally { 340 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, "after-reload", this, null)); 341 log.info("returning from " + id); 342 } 343 } 344 345 protected void triggerReloadWithNewTransaction(String id) { 346 if (TransactionHelper.isTransactionMarkedRollback()) { 347 throw new AssertionError("The calling transaction is marked rollback"); 348 } else if (TransactionHelper.isTransactionActive()) { // should flush 349 // the calling 350 // transaction 351 TransactionHelper.commitOrRollbackTransaction(); 352 TransactionHelper.startTransaction(); 353 } 354 try { 355 try { 356 triggerReload(id); 357 } catch (RuntimeException cause) { 358 TransactionHelper.setTransactionRollbackOnly(); 359 throw cause; 360 } 361 } finally { 362 if (TransactionHelper.isTransactionActiveOrMarkedRollback()) { 363 boolean wasRollbacked = TransactionHelper.isTransactionMarkedRollback(); 364 TransactionHelper.commitOrRollbackTransaction(); 365 TransactionHelper.startTransaction(); 366 if (wasRollbacked) { 367 TransactionHelper.setTransactionRollbackOnly(); 368 } 369 } 370 } 371 } 372}