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.net.URL; 023import java.time.Duration; 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.utils.TextTemplate; 039import org.nuxeo.runtime.api.Framework; 040import org.nuxeo.runtime.api.ServicePassivator; 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 private 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 final List<RuntimeExtension> extensions = new ArrayList<>(); 082 083 protected AbstractRuntimeService(DefaultRuntimeContext context) { 084 this(context, null); 085 } 086 087 /** 088 * Warnings during the deployment. These messages don't block startup, even in strict mode. 089 */ 090 protected final List<String> warnings = new ArrayList<>(); 091 092 /** 093 * Errors during the deployment. Here are collected all errors occurred during the startup. These messages block 094 * startup in strict mode. 095 * 096 * @since 9.1 097 */ 098 protected final List<String> errors = new ArrayList<>(); 099 100 protected AbstractRuntimeService(DefaultRuntimeContext context, Map<String, String> properties) { 101 this.context = context; 102 context.setRuntime(this); 103 if (properties != null) { 104 this.properties.putAll(properties); 105 } 106 // get errors set by NuxeoDeployer 107 String errs = System.getProperty("org.nuxeo.runtime.deployment.errors"); 108 if (errs != null) { 109 errors.addAll(Arrays.asList(errs.split("\n"))); 110 System.clearProperty("org.nuxeo.runtime.deployment.errors"); 111 } 112 } 113 114 @Override 115 public List<String> getWarnings() { 116 return warnings; 117 } 118 119 /** 120 * @since 9.1 121 */ 122 @Override 123 public List<String> getErrors() { 124 return errors; 125 } 126 127 protected ComponentManager createComponentManager() { 128 return new ComponentManagerImpl(this); 129 } 130 131 protected static URL getBuiltinFeatureURL() { 132 return Thread.currentThread().getContextClassLoader().getResource("org/nuxeo/runtime/nx-feature.xml"); 133 } 134 135 @Override 136 public synchronized void start() { 137 if (isStarted) { 138 return; 139 } 140 if (Boolean.parseBoolean(getProperty(REDIRECT_JUL, "true"))) { 141 Level threshold = Level.parse(getProperty(REDIRECT_JUL_THRESHOLD, "INFO").toUpperCase()); 142 JavaUtilLoggingHelper.redirectToApacheCommons(threshold); 143 } 144 log.info("Starting Nuxeo Runtime service " + getName() + "; version: " + getVersion()); 145 // NXRuntime.setInstance(this); 146 manager = createComponentManager(); 147 Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_START, this)); 148 ServicePassivator.passivate() 149 .withQuietDelay(Duration.ofSeconds(0)) 150 .monitor() 151 .withTimeout(Duration.ofSeconds(0)) 152 .withEnforceMode(false) 153 .await() 154 .proceed(() -> { 155 try { 156 doStart(); 157 startExtensions(); 158 } finally { 159 Framework.sendEvent( 160 new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STARTED, this)); 161 isStarted = true; 162 } 163 }); 164 } 165 166 @Override 167 public synchronized void stop() { 168 if (!isStarted) { 169 return; 170 } 171 isShuttingDown = true; 172 try { 173 log.info("Stopping Nuxeo Runtime service " + getName() + "; version: " + getVersion()); 174 Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP, this)); 175 ServicePassivator.passivate() 176 .withQuietDelay(Duration.ofSeconds(0)) 177 .monitor() 178 .withTimeout(Duration.ofSeconds(0)) 179 .withEnforceMode(false) 180 .await() 181 .proceed(() -> { 182 try { 183 stopExtensions(); 184 doStop(); 185 manager.shutdown(); 186 } finally { 187 isStarted = false; 188 Framework.sendEvent( 189 new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STOPPED, this)); 190 manager = null; 191 } 192 }); 193 } finally { 194 JavaUtilLoggingHelper.reset(); 195 isShuttingDown = false; 196 } 197 } 198 199 @Override 200 public boolean isStarted() { 201 return isStarted; 202 } 203 204 @Override 205 public boolean isShuttingDown() { 206 return isShuttingDown; 207 } 208 209 protected void doStart() { 210 } 211 212 protected void doStop() { 213 } 214 215 @Override 216 public File getHome() { 217 return workingDir; 218 } 219 220 public void setHome(File home) { 221 workingDir = home; 222 } 223 224 @Override 225 public String getDescription() { 226 return toString(); 227 } 228 229 @Override 230 public CryptoProperties getProperties() { 231 // do not unreference properties: some methods rely on this to set 232 // variables here... 233 return properties; 234 } 235 236 @Override 237 public String getProperty(String name) { 238 return getProperty(name, null); 239 } 240 241 @Override 242 public String getProperty(String name, String defValue) { 243 String value = properties.getProperty(name, defValue); 244 if (value == null || ("${" + name + "}").equals(value)) { 245 // avoid loop, don't expand 246 return value; 247 } 248 return expandVars(value); 249 } 250 251 @Override 252 public void setProperty(String name, Object value) { 253 properties.setProperty(name, value.toString()); 254 } 255 256 @Override 257 public String toString() { 258 return getName() + " version " + getVersion(); 259 } 260 261 @Override 262 public Object getComponent(String name) { 263 ComponentInstance co = getComponentInstance(name); 264 return co != null ? co.getInstance() : null; 265 } 266 267 @Override 268 public Object getComponent(ComponentName name) { 269 ComponentInstance co = getComponentInstance(name); 270 return co != null ? co.getInstance() : null; 271 } 272 273 @Override 274 public ComponentInstance getComponentInstance(String name) { 275 return manager.getComponent(new ComponentName(name)); 276 } 277 278 @Override 279 public ComponentInstance getComponentInstance(ComponentName name) { 280 return manager.getComponent(name); 281 } 282 283 @Override 284 public ComponentManager getComponentManager() { 285 return manager; 286 } 287 288 @Override 289 public RuntimeContext getContext() { 290 return context; 291 } 292 293 protected void startExtensions() { 294 for (RuntimeExtension ext : extensions) { 295 ext.start(); 296 } 297 } 298 299 protected void stopExtensions() { 300 for (RuntimeExtension ext : extensions) { 301 ext.stop(); 302 } 303 } 304 305 @Override 306 public <T> T getService(Class<T> serviceClass) { 307 return manager.getService(serviceClass); 308 } 309 310 @Override 311 public String expandVars(String expression) { 312 return new TextTemplate(properties).processText(expression); 313 } 314 315 @Override 316 public File getBundleFile(Bundle bundle) { 317 return null; 318 } 319 320 @Override 321 public Bundle getBundle(String symbolicName) { 322 throw new UnsupportedOperationException("Not implemented"); 323 } 324 325 /** 326 * @since 5.5 327 * @param msg summary message about all components loading status 328 * @return true if there was no detected error, else return false 329 */ 330 @Override 331 public boolean getStatusMessage(StringBuilder msg) { 332 String hr = "======================================================================"; 333 if (!warnings.isEmpty()) { 334 msg.append(hr).append("\n= Component Loading Warnings:\n"); 335 for (String warning : warnings) { 336 msg.append(" * ").append(warning).append('\n'); 337 } 338 } 339 if (!errors.isEmpty()) { 340 msg.append(hr).append("\n= Component Loading Errors:\n"); 341 for (String error : errors) { 342 msg.append(" * ").append(error).append('\n'); 343 } 344 } 345 Map<ComponentName, Set<ComponentName>> pendingRegistrations = manager.getPendingRegistrations(); 346 Map<ComponentName, Set<Extension>> missingRegistrations = manager.getMissingRegistrations(); 347 Collection<ComponentName> unstartedRegistrations = manager.getActivatingRegistrations(); 348 unstartedRegistrations.addAll(manager.getStartFailureRegistrations()); 349 msg.append(hr) 350 .append("\n= Component Loading Status: Pending: ") 351 .append(pendingRegistrations.size()) 352 .append(" / Missing: ") 353 .append(missingRegistrations.size()) 354 .append(" / Unstarted: ") 355 .append(unstartedRegistrations.size()) 356 .append(" / Total: ") 357 .append(manager.getRegistrations().size()) 358 .append('\n'); 359 for (Entry<ComponentName, Set<ComponentName>> e : pendingRegistrations.entrySet()) { 360 msg.append(" * ").append(e.getKey()).append(" requires ").append(e.getValue()).append('\n'); 361 } 362 for (Entry<ComponentName, Set<Extension>> e : missingRegistrations.entrySet()) { 363 msg.append(" * ") 364 .append(e.getKey()) 365 .append(" references missing ") 366 .append(e.getValue() 367 .stream() 368 .map(ext -> "target=" + ext.getTargetComponent().getName() + ";point=" 369 + ext.getExtensionPoint()) 370 .collect(Collectors.toList())) 371 .append('\n'); 372 } 373 for (ComponentName componentName : unstartedRegistrations) { 374 msg.append(" - ").append(componentName).append('\n'); 375 } 376 msg.append(hr); 377 return (errors.isEmpty() && pendingRegistrations.isEmpty() && missingRegistrations.isEmpty() 378 && unstartedRegistrations.isEmpty()); 379 } 380 381}