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