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 * Nuxeo - initial API and implementation 018 */ 019package org.nuxeo.runtime; 020 021import java.io.File; 022import java.io.IOException; 023import java.net.URL; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Set; 031import java.util.logging.Level; 032import java.util.stream.Collectors; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.common.codec.CryptoProperties; 037import org.nuxeo.common.logging.JavaUtilLoggingHelper; 038import org.nuxeo.common.logging.Log4JHelper; 039import org.nuxeo.common.logging.Log4jWatchdogHandle; 040import org.nuxeo.common.utils.TextTemplate; 041import org.nuxeo.runtime.api.Framework; 042import org.nuxeo.runtime.model.ComponentInstance; 043import org.nuxeo.runtime.model.ComponentManager; 044import org.nuxeo.runtime.model.ComponentName; 045import org.nuxeo.runtime.model.Extension; 046import org.nuxeo.runtime.model.RuntimeContext; 047import org.nuxeo.runtime.model.impl.ComponentManagerImpl; 048import org.nuxeo.runtime.model.impl.DefaultRuntimeContext; 049import org.osgi.framework.Bundle; 050 051/** 052 * Abstract implementation of the Runtime Service. 053 * <p> 054 * Implementors are encouraged to extend this class instead of directly implementing the {@link RuntimeService} 055 * interface. 056 * 057 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 058 */ 059public abstract class AbstractRuntimeService implements RuntimeService { 060 061 /** 062 * Property that controls whether or not to redirect JUL to JCL. By default is true (JUL will be redirected) 063 */ 064 public static final String REDIRECT_JUL = "org.nuxeo.runtime.redirectJUL"; 065 066 public static final String REDIRECT_JUL_THRESHOLD = "org.nuxeo.runtime.redirectJUL.threshold"; 067 068 public static final String LOG4J_WATCH_DISABLED = "org.nuxeo.runtime.log4jwatch.disabled"; 069 070 public static final String LOG4J_WATCH_DELAY = "org.nuxeo.runtime.log4jwatch.delay"; 071 072 public static final long LOG4J_WATCH_DELAY_DEFAULT = 10; 073 074 // package-private for subclass access without synthetic accessor 075 static final Log log = LogFactory.getLog(RuntimeService.class); 076 077 protected boolean isStarted = false; 078 079 protected boolean isShuttingDown = false; 080 081 protected File workingDir; 082 083 protected CryptoProperties properties = new CryptoProperties(System.getProperties()); 084 085 protected ComponentManager manager; 086 087 protected final RuntimeContext context; 088 089 protected AbstractRuntimeService(DefaultRuntimeContext context) { 090 this(context, null); 091 } 092 093 /** 094 * Warnings during the deployment. These messages don't block startup, even in strict mode. 095 */ 096 protected final List<String> warnings = new ArrayList<>(); 097 098 protected LogConfig logConfig = new LogConfig(); 099 100 /** 101 * Errors during the deployment. Here are collected all errors occurred during the startup. These messages block 102 * startup in strict mode. 103 * 104 * @since 9.1 105 */ 106 protected final List<String> errors = new ArrayList<>(); 107 108 protected AbstractRuntimeService(DefaultRuntimeContext context, Map<String, String> properties) { 109 this.context = context; 110 context.setRuntime(this); 111 if (properties != null) { 112 this.properties.putAll(properties); 113 } 114 // get errors set by NuxeoDeployer 115 String errs = System.getProperty("org.nuxeo.runtime.deployment.errors"); 116 if (errs != null) { 117 errors.addAll(Arrays.asList(errs.split("\n"))); 118 System.clearProperty("org.nuxeo.runtime.deployment.errors"); 119 } 120 } 121 122 @Override 123 public List<String> getWarnings() { 124 return warnings; 125 } 126 127 /** 128 * @since 9.1 129 */ 130 @Override 131 public List<String> getErrors() { 132 return errors; 133 } 134 135 protected ComponentManager createComponentManager() { 136 return new ComponentManagerImpl(this); 137 } 138 139 protected static URL getBuiltinFeatureURL() { 140 return Thread.currentThread().getContextClassLoader().getResource("org/nuxeo/runtime/nx-feature.xml"); 141 } 142 143 @Override 144 public synchronized void start() { 145 if (isStarted) { 146 return; 147 } 148 149 manager = createComponentManager(); 150 try { 151 loadConfig(); 152 } catch (IOException e) { 153 throw new RuntimeServiceException(e); 154 } 155 156 logConfig.configure(); 157 158 log.info("Starting Nuxeo Runtime service " + getName() + "; version: " + getVersion()); 159 160 Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_START, this)); 161 try { 162 doStart(); 163 } finally { 164 Framework.sendEvent( 165 new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STARTED, this)); 166 isStarted = true; 167 } 168 } 169 170 @Override 171 public synchronized void stop() { 172 if (!isStarted) { 173 return; 174 } 175 isShuttingDown = true; 176 try { 177 log.info("Stopping Nuxeo Runtime service " + getName() + "; version: " + getVersion()); 178 Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP, this)); 179 try { 180 manager.shutdown(); 181 doStop(); 182 } finally { 183 isStarted = false; 184 Framework.sendEvent( 185 new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STOPPED, this)); 186 manager = null; 187 } 188 } finally { 189 logConfig.cleanup(); 190 isShuttingDown = false; 191 } 192 } 193 194 @Override 195 public boolean isStarted() { 196 return isStarted; 197 } 198 199 @Override 200 public boolean isShuttingDown() { 201 return isShuttingDown; 202 } 203 204 protected void loadConfig() throws IOException { 205 } 206 207 protected void doStart() { 208 } 209 210 protected void doStop() { 211 } 212 213 @Override 214 public File getHome() { 215 return workingDir; 216 } 217 218 public void setHome(File home) { 219 workingDir = home; 220 } 221 222 @Override 223 public String getDescription() { 224 return toString(); 225 } 226 227 @Override 228 public CryptoProperties getProperties() { 229 // do not unreference properties: some methods rely on this to set 230 // variables here... 231 return properties; 232 } 233 234 @Override 235 public String getProperty(String name) { 236 return getProperty(name, null); 237 } 238 239 @Override 240 public String getProperty(String name, String defValue) { 241 String value = properties.getProperty(name, defValue); 242 if (value == null || ("${" + name + "}").equals(value)) { 243 // avoid loop, don't expand 244 return value; 245 } 246 return expandVars(value); 247 } 248 249 @Override 250 public void setProperty(String name, Object value) { 251 properties.setProperty(name, value.toString()); 252 } 253 254 @Override 255 public String toString() { 256 return getName() + " version " + getVersion(); 257 } 258 259 @Override 260 public Object getComponent(String name) { 261 ComponentInstance co = getComponentInstance(name); 262 return co != null ? co.getInstance() : null; 263 } 264 265 @Override 266 public Object getComponent(ComponentName name) { 267 ComponentInstance co = getComponentInstance(name); 268 return co != null ? co.getInstance() : null; 269 } 270 271 @Override 272 public ComponentInstance getComponentInstance(String name) { 273 return manager.getComponent(new ComponentName(name)); 274 } 275 276 @Override 277 public ComponentInstance getComponentInstance(ComponentName name) { 278 return manager.getComponent(name); 279 } 280 281 @Override 282 public ComponentManager getComponentManager() { 283 return manager; 284 } 285 286 @Override 287 public RuntimeContext getContext() { 288 return context; 289 } 290 291 @Override 292 public <T> T getService(Class<T> serviceClass) { 293 return manager.getService(serviceClass); 294 } 295 296 @Override 297 public String expandVars(String expression) { 298 return new TextTemplate(properties).processText(expression); 299 } 300 301 @Override 302 public File getBundleFile(Bundle bundle) { 303 return null; 304 } 305 306 @Override 307 public Bundle getBundle(String symbolicName) { 308 throw new UnsupportedOperationException("Not implemented"); 309 } 310 311 /** 312 * @since 5.5 313 * @param msg summary message about all components loading status 314 * @return true if there was no detected error, else return false 315 */ 316 @Override 317 public boolean getStatusMessage(StringBuilder msg) { 318 String hr = "======================================================================"; 319 if (!warnings.isEmpty()) { 320 msg.append(hr).append("\n= Component Loading Warnings:\n"); 321 for (String warning : warnings) { 322 msg.append(" * ").append(warning).append('\n'); 323 } 324 } 325 if (!errors.isEmpty()) { 326 msg.append(hr).append("\n= Component Loading Errors:\n"); 327 for (String error : errors) { 328 msg.append(" * ").append(error).append('\n'); 329 } 330 } 331 Map<ComponentName, Set<ComponentName>> pendingRegistrations = manager.getPendingRegistrations(); 332 Map<ComponentName, Set<Extension>> missingRegistrations = manager.getMissingRegistrations(); 333 Collection<ComponentName> unstartedRegistrations = manager.getActivatingRegistrations(); 334 unstartedRegistrations.addAll(manager.getStartFailureRegistrations()); 335 msg.append(hr) 336 .append("\n= Component Loading Status: Pending: ") 337 .append(pendingRegistrations.size()) 338 .append(" / Missing: ") 339 .append(missingRegistrations.size()) 340 .append(" / Unstarted: ") 341 .append(unstartedRegistrations.size()) 342 .append(" / Total: ") 343 .append(manager.getRegistrations().size()) 344 .append('\n'); 345 for (Entry<ComponentName, Set<ComponentName>> e : pendingRegistrations.entrySet()) { 346 msg.append(" * ").append(e.getKey()).append(" requires ").append(e.getValue()).append('\n'); 347 } 348 for (Entry<ComponentName, Set<Extension>> e : missingRegistrations.entrySet()) { 349 msg.append(" * ") 350 .append(e.getKey()) 351 .append(" references missing ") 352 .append(e.getValue() 353 .stream() 354 .map(ext -> "target=" + ext.getTargetComponent().getName() + ";point=" 355 + ext.getExtensionPoint()) 356 .collect(Collectors.toList())) 357 .append('\n'); 358 } 359 for (ComponentName componentName : unstartedRegistrations) { 360 msg.append(" - ").append(componentName).append('\n'); 361 } 362 msg.append(hr); 363 return (errors.isEmpty() && pendingRegistrations.isEmpty() && missingRegistrations.isEmpty() 364 && unstartedRegistrations.isEmpty()); 365 } 366 367 /** 368 * Error logger which does its logging from a separate thread, for thread isolation. 369 * 370 * @param message the message to log 371 * @return a thread that can be started to do the logging 372 * @since 9.2, 8.10-HF05 373 */ 374 public static Thread getErrorLoggerThread(String message) { 375 return new Thread() { 376 @Override 377 public void run() { 378 log.error(message); 379 } 380 }; 381 } 382 383 /** 384 * Configure the logging system (log4j) at runtime startup and do any cleanup is needed when the runtime is stopped 385 */ 386 protected class LogConfig { 387 388 Log4jWatchdogHandle wdog; 389 390 public void configure(){ 391 if (Boolean.parseBoolean(getProperty(REDIRECT_JUL, "true"))) { 392 Level threshold = Level.parse(getProperty(REDIRECT_JUL_THRESHOLD, "INFO").toUpperCase()); 393 JavaUtilLoggingHelper.redirectToApacheCommons(threshold); 394 } 395 if (Boolean.parseBoolean(getProperty(LOG4J_WATCH_DISABLED, "false"))) { 396 log.info("Disabled log4j.xml change detection"); 397 } else { 398 long delay; 399 try { 400 delay = Long.parseLong(getProperty(LOG4J_WATCH_DELAY, Long.toString(LOG4J_WATCH_DELAY_DEFAULT))); 401 } catch (NumberFormatException e) { 402 delay = LOG4J_WATCH_DELAY_DEFAULT; 403 } 404 wdog = Log4JHelper.configureAndWatch(delay); 405 if (wdog == null) { 406 log.warn("Failed to configure log4j.xml change detection"); 407 } else { 408 log.info("Configured log4j.xml change detection with a delay of " + delay + "s"); 409 } 410 } 411 } 412 413 public void cleanup() { 414 try { 415 if (wdog != null) { 416 wdog.cancel(); 417 } 418 } finally { 419 wdog = null; 420 JavaUtilLoggingHelper.reset(); 421 } 422 } 423 } 424 425}