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