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.jar.Manifest; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.common.Environment; 029import org.nuxeo.common.utils.FileUtils; 030import org.nuxeo.common.utils.JarUtils; 031import org.nuxeo.common.utils.ZipUtils; 032import org.nuxeo.runtime.RuntimeService; 033import org.nuxeo.runtime.RuntimeServiceException; 034import org.nuxeo.runtime.api.Framework; 035import org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor; 036import org.nuxeo.runtime.model.ComponentContext; 037import org.nuxeo.runtime.model.DefaultComponent; 038import org.nuxeo.runtime.services.event.Event; 039import org.nuxeo.runtime.services.event.EventService; 040import org.osgi.framework.Bundle; 041import org.osgi.framework.BundleContext; 042import org.osgi.framework.BundleException; 043import org.osgi.framework.ServiceReference; 044import org.osgi.service.packageadmin.PackageAdmin; 045 046/** 047 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 048 */ 049public class ReloadComponent extends DefaultComponent implements ReloadService { 050 051 private static final Log log = LogFactory.getLog(ReloadComponent.class); 052 053 protected static Bundle bundle; 054 055 protected Long lastFlushed; 056 057 public static BundleContext getBundleContext() { 058 return bundle.getBundleContext(); 059 } 060 061 public static Bundle getBundle() { 062 return bundle; 063 } 064 065 @Override 066 public void activate(ComponentContext context) { 067 super.activate(context); 068 bundle = context.getRuntimeContext().getBundle(); 069 } 070 071 @Override 072 public void deactivate(ComponentContext context) { 073 super.deactivate(context); 074 bundle = null; 075 } 076 077 @Override 078 public void reload() { 079 if (log.isDebugEnabled()) { 080 log.debug("Starting reload"); 081 } 082 try { 083 reloadProperties(); 084 } catch (IOException e) { 085 throw new RuntimeServiceException(e); 086 } 087 EventService eventService = Framework.getLocalService(EventService.class); 088 eventService.sendEvent(new Event(RELOAD_TOPIC, RELOAD_EVENT_ID, this, null)); 089 if (log.isDebugEnabled()) { 090 log.debug("Reload done"); 091 } 092 } 093 094 @Override 095 public void reloadProperties() throws IOException { 096 log.info("Reload runtime properties"); 097 Framework.getRuntime().reloadProperties(); 098 } 099 100 @Override 101 public void reloadRepository() { 102 log.info("Reload repository"); 103 Framework.getLocalService(EventService.class).sendEvent( 104 new Event(RELOAD_TOPIC, RELOAD_REPOSITORIES_ID, this, null)); 105 } 106 107 @Override 108 public void reloadSeamComponents() { 109 log.info("Reload Seam components"); 110 Framework.getLocalService(EventService.class).sendEvent( 111 new Event(RELOAD_TOPIC, RELOAD_SEAM_EVENT_ID, this, null)); 112 } 113 114 @Override 115 public void flush() { 116 if (log.isDebugEnabled()) { 117 log.debug("Starting flush"); 118 } 119 flushJaasCache(); 120 EventService eventService = Framework.getLocalService(EventService.class); 121 eventService.sendEvent(new Event(RELOAD_TOPIC, FLUSH_EVENT_ID, this, null)); 122 setFlushedNow(); 123 if (log.isDebugEnabled()) { 124 log.debug("Flush done"); 125 } 126 } 127 128 @Override 129 public void flushJaasCache() { 130 log.info("Flush the JAAS cache"); 131 EventService eventService = Framework.getLocalService(EventService.class); 132 eventService.sendEvent(new Event("usermanager", "user_changed", this, "Deployer")); // the data argument is 133 // optional 134 setFlushedNow(); 135 } 136 137 @Override 138 public void flushSeamComponents() { 139 log.info("Flush Seam components"); 140 Framework.getLocalService(EventService.class).sendEvent( 141 new Event(RELOAD_TOPIC, FLUSH_SEAM_EVENT_ID, this, null)); 142 setFlushedNow(); 143 } 144 145 @Override 146 public String deployBundle(File file) throws BundleException { 147 return deployBundle(file, false); 148 } 149 150 @Override 151 public String deployBundle(File file, boolean reloadResourceClasspath) throws BundleException { 152 String name = getOSGIBundleName(file); 153 if (name == null) { 154 log.error(String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", file.getAbsolutePath())); 155 return null; 156 } 157 158 String path = file.getAbsolutePath(); 159 160 log.info(String.format("Before deploy bundle for file at '%s'\n" + "%s", path, getRuntimeStatus())); 161 162 if (reloadResourceClasspath) { 163 URL url; 164 try { 165 url = new File(path).toURI().toURL(); 166 } catch (MalformedURLException e) { 167 throw new RuntimeException(e); 168 } 169 Framework.reloadResourceLoader(Arrays.asList(url), null); 170 } 171 172 // check if this is a bundle first 173 Bundle newBundle = getBundleContext().installBundle(path); 174 if (newBundle == null) { 175 throw new IllegalArgumentException("Could not find a valid bundle at path: " + path); 176 } 177 newBundle.start(); 178 179 log.info(String.format("Deploy done for bundle with name '%s'.\n" + "%s", newBundle.getSymbolicName(), 180 getRuntimeStatus())); 181 182 return newBundle.getSymbolicName(); 183 } 184 185 @Override 186 public void undeployBundle(File file, boolean reloadResources) throws BundleException { 187 String name = getOSGIBundleName(file); 188 String path = file.getAbsolutePath(); 189 if (name == null) { 190 log.error(String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", path)); 191 return; 192 } 193 194 undeployBundle(name); 195 196 if (reloadResources) { 197 URL url; 198 try { 199 url = new File(path).toURI().toURL(); 200 } catch (MalformedURLException e) { 201 throw new RuntimeException(e); 202 } 203 Framework.reloadResourceLoader(null, Arrays.asList(url)); 204 } 205 } 206 207 @Override 208 public void undeployBundle(String bundleName) throws BundleException { 209 if (bundleName == null) { 210 // ignore 211 return; 212 } 213 log.info(String.format("Before undeploy bundle with name '%s'.\n" + "%s", bundleName, getRuntimeStatus())); 214 BundleContext ctx = getBundleContext(); 215 ServiceReference ref = ctx.getServiceReference(PackageAdmin.class.getName()); 216 PackageAdmin srv = (PackageAdmin) ctx.getService(ref); 217 try { 218 for (Bundle b : srv.getBundles(bundleName, null)) { 219 if (b != null && b.getState() == Bundle.ACTIVE) { 220 b.stop(); 221 b.uninstall(); 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 @Deprecated 248 public void installWebResources(File file) throws IOException { 249 log.info("Install web resources"); 250 if (file.isDirectory()) { 251 File war = new File(file, "web"); 252 war = new File(war, "nuxeo.war"); 253 if (war.isDirectory()) { 254 FileUtils.copyTree(war, getAppDir()); 255 } else { 256 // compatibility mode with studio 1.5 - see NXP-6186 257 war = new File(file, "nuxeo.war"); 258 if (war.isDirectory()) { 259 FileUtils.copyTree(war, getAppDir()); 260 } 261 } 262 } else if (file.isFile()) { // a jar 263 File war = getWarDir(); 264 ZipUtils.unzip("web/nuxeo.war", file, war); 265 // compatibility mode with studio 1.5 - see NXP-6186 266 ZipUtils.unzip("nuxeo.war", file, war); 267 } 268 } 269 270 public void runDeploymentPreprocessor() throws IOException { 271 if (log.isDebugEnabled()) { 272 log.debug("Start running deployment preprocessor"); 273 } 274 String rootPath = Environment.getDefault().getHome().getAbsolutePath(); 275 File root = new File(rootPath); 276 DeploymentPreprocessor processor = new DeploymentPreprocessor(root); 277 // initialize 278 processor.init(); 279 // and predeploy 280 processor.predeploy(); 281 if (log.isDebugEnabled()) { 282 log.debug("Deployment preprocessing done"); 283 } 284 } 285 286 protected static File getAppDir() { 287 return Environment.getDefault().getConfig().getParentFile(); 288 } 289 290 protected static File getWarDir() { 291 return new File(getAppDir(), "nuxeo.war"); 292 } 293 294 @Override 295 public String getOSGIBundleName(File file) { 296 Manifest mf = JarUtils.getManifest(file); 297 if (mf == null) { 298 return null; 299 } 300 String bundleName = mf.getMainAttributes().getValue("Bundle-SymbolicName"); 301 if (bundleName == null) { 302 return null; 303 } 304 int index = bundleName.indexOf(';'); 305 if (index > -1) { 306 bundleName = bundleName.substring(0, index); 307 } 308 return bundleName; 309 } 310 311 protected String getRuntimeStatus() { 312 StringBuilder msg = new StringBuilder(); 313 RuntimeService runtime = Framework.getRuntime(); 314 runtime.getStatusMessage(msg); 315 return msg.toString(); 316 } 317 318}