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