001/* 002 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu 016 */ 017package org.nuxeo.runtime.reload; 018 019import java.io.File; 020import java.io.IOException; 021import java.net.MalformedURLException; 022import java.net.URL; 023import java.util.Arrays; 024import java.util.concurrent.CountDownLatch; 025import java.util.jar.Manifest; 026 027import javax.transaction.Transaction; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.common.Environment; 032import org.nuxeo.common.utils.FileUtils; 033import org.nuxeo.common.utils.JarUtils; 034import org.nuxeo.common.utils.ZipUtils; 035import org.nuxeo.runtime.RuntimeService; 036import org.nuxeo.runtime.RuntimeServiceException; 037import org.nuxeo.runtime.api.DefaultServiceProvider; 038import org.nuxeo.runtime.api.Framework; 039import org.nuxeo.runtime.api.ServiceProvider; 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).sendEvent( 124 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().getHome().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 final CountDownLatch reloadAchieved = new CountDownLatch(1); 322 final Thread ownerThread = Thread.currentThread(); 323 try { 324 ServiceProvider next = DefaultServiceProvider.getProvider(); 325 DefaultServiceProvider.setProvider(new ServiceProvider() { 326 327 @Override 328 public <T> T getService(Class<T> serviceClass) { 329 if (Thread.currentThread() != ownerThread) { 330 try { 331 reloadAchieved.await(); 332 } catch (InterruptedException cause) { 333 Thread.currentThread().interrupt(); 334 throw new AssertionError(serviceClass + "was interruped while waiting for reloading", 335 cause); 336 } 337 } 338 if (next != null) { 339 return next.getService(serviceClass); 340 } 341 return Framework.getRuntime().getService(serviceClass); 342 } 343 }); 344 try { 345 if (log.isDebugEnabled()) { 346 log.debug("triggering reload("+id+")"); 347 } 348 Framework.getLocalService(EventService.class).sendEvent(new Event(RELOAD_TOPIC, id, this, null)); 349 if (id.startsWith(FLUSH_EVENT_ID) || FLUSH_SEAM_EVENT_ID.equals(id)) { 350 setFlushedNow(); 351 } 352 } finally { 353 DefaultServiceProvider.setProvider(next); 354 } 355 } finally { 356 reloadAchieved.countDown(); 357 } 358 } 359 360 protected void triggerReloadWithNewTransaction(String id) { 361 if (TransactionHelper.isTransactionMarkedRollback()) { 362 throw new AssertionError("The calling transaction is marked rollback="); 363 } else if (TransactionHelper.isTransactionActive()) { // should flush the calling transaction 364 TransactionHelper.commitOrRollbackTransaction(); 365 TransactionHelper.startTransaction(); 366 } 367 try { 368 try { 369 triggerReload(id); 370 } catch (RuntimeException cause) { 371 TransactionHelper.setTransactionRollbackOnly(); 372 throw cause; 373 } 374 } finally { 375 if (TransactionHelper.isTransactionActiveOrMarkedRollback()) { 376 boolean wasRollbacked = TransactionHelper.isTransactionMarkedRollback(); 377 TransactionHelper.commitOrRollbackTransaction(); 378 TransactionHelper.startTransaction(); 379 if (wasRollbacked) { 380 TransactionHelper.setTransactionRollbackOnly(); 381 } 382 } 383 } 384 } 385}