001/* 002 * (C) Copyright 2006-2015 Nuxeo SA (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 * 019 */ 020 021package org.nuxeo.runtime; 022 023import java.io.File; 024import java.net.URL; 025import java.time.Duration; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.List; 030import java.util.Map; 031import java.util.Map.Entry; 032import java.util.Set; 033import java.util.logging.Level; 034import java.util.stream.Collectors; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.common.codec.CryptoProperties; 039import org.nuxeo.common.logging.JavaUtilLoggingHelper; 040import org.nuxeo.common.utils.TextTemplate; 041import org.nuxeo.runtime.api.Framework; 042import org.nuxeo.runtime.api.ServicePassivator; 043import org.nuxeo.runtime.model.ComponentInstance; 044import org.nuxeo.runtime.model.ComponentManager; 045import org.nuxeo.runtime.model.ComponentName; 046import org.nuxeo.runtime.model.Extension; 047import org.nuxeo.runtime.model.RuntimeContext; 048import org.nuxeo.runtime.model.impl.ComponentManagerImpl; 049import org.nuxeo.runtime.model.impl.DefaultRuntimeContext; 050import org.osgi.framework.Bundle; 051 052/** 053 * Abstract implementation of the Runtime Service. 054 * <p> 055 * Implementors are encouraged to extend this class instead of directly implementing the {@link RuntimeService} 056 * interface. 057 * 058 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 059 */ 060public abstract class AbstractRuntimeService implements RuntimeService { 061 062 /** 063 * Property that controls whether or not to redirect JUL to JCL. By default is true (JUL will be redirected) 064 */ 065 public static final String REDIRECT_JUL = "org.nuxeo.runtime.redirectJUL"; 066 067 public static final String REDIRECT_JUL_THRESHOLD = "org.nuxeo.runtime.redirectJUL.threshold"; 068 069 private static final Log log = LogFactory.getLog(RuntimeService.class); 070 071 protected boolean isStarted = false; 072 073 protected boolean isShuttingDown = false; 074 075 protected File workingDir; 076 077 protected CryptoProperties properties = new CryptoProperties(System.getProperties()); 078 079 protected ComponentManager manager; 080 081 protected final RuntimeContext context; 082 083 protected final List<RuntimeExtension> extensions = new ArrayList<>(); 084 085 protected AbstractRuntimeService(DefaultRuntimeContext context) { 086 this(context, null); 087 } 088 089 // warnings during the deployment. Here are collected all errors occurred 090 // during the startup 091 protected final List<String> warnings = new ArrayList<>(); 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 warnings.addAll(Arrays.asList(errs.split("\n"))); 103 System.clearProperty("org.nuxeo.runtime.deployment.errors"); 104 } 105 } 106 107 @Override 108 public List<String> getWarnings() { 109 return warnings; 110 } 111 112 protected ComponentManager createComponentManager() { 113 return new ComponentManagerImpl(this); 114 } 115 116 protected static URL getBuiltinFeatureURL() { 117 return Thread.currentThread().getContextClassLoader().getResource("org/nuxeo/runtime/nx-feature.xml"); 118 } 119 120 @Override 121 public synchronized void start() { 122 if (isStarted) { 123 return; 124 } 125 if (Boolean.parseBoolean(getProperty(REDIRECT_JUL, "false"))) { 126 Level threshold = Level.parse(getProperty(REDIRECT_JUL_THRESHOLD, "INFO").toUpperCase()); 127 JavaUtilLoggingHelper.redirectToApacheCommons(threshold); 128 } 129 log.info("Starting Nuxeo Runtime service " + getName() + "; version: " + getVersion()); 130 // NXRuntime.setInstance(this); 131 manager = createComponentManager(); 132 Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_START, this)); 133 ServicePassivator.passivate() 134 .withQuietDelay(Duration.ofSeconds(0)) 135 .monitor() 136 .withTimeout(Duration.ofSeconds(0)) 137 .withEnforceMode(false) 138 .await() 139 .proceed(() -> { 140 try { 141 doStart(); 142 startExtensions(); 143 } finally { 144 Framework.sendEvent( 145 new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STARTED, this)); 146 isStarted = true; 147 } 148 }); 149 } 150 151 @Override 152 public synchronized void stop() { 153 if (!isStarted) { 154 return; 155 } 156 isShuttingDown = true; 157 try { 158 log.info("Stopping Nuxeo Runtime service " + getName() + "; version: " + getVersion()); 159 Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP, this)); 160 ServicePassivator.passivate() 161 .withQuietDelay(Duration.ofSeconds(0)) 162 .monitor() 163 .withTimeout(Duration.ofSeconds(0)) 164 .withEnforceMode(false) 165 .await() 166 .proceed(() -> { 167 try { 168 stopExtensions(); 169 doStop(); 170 manager.shutdown(); 171 } finally { 172 isStarted = false; 173 Framework.sendEvent( 174 new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STOPPED, this)); 175 manager = null; 176 } 177 }); 178 } finally { 179 JavaUtilLoggingHelper.reset(); 180 isShuttingDown = false; 181 } 182 } 183 184 @Override 185 public boolean isStarted() { 186 return isStarted; 187 } 188 189 @Override 190 public boolean isShuttingDown() { 191 return isShuttingDown; 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 StringBuilder sb = new StringBuilder(); 244 return sb.append(getName()).append(" version ").append(getVersion().toString()).toString(); 245 } 246 247 @Override 248 public Object getComponent(String name) { 249 ComponentInstance co = getComponentInstance(name); 250 return co != null ? co.getInstance() : null; 251 } 252 253 @Override 254 public Object getComponent(ComponentName name) { 255 ComponentInstance co = getComponentInstance(name); 256 return co != null ? co.getInstance() : null; 257 } 258 259 @Override 260 public ComponentInstance getComponentInstance(String name) { 261 return manager.getComponent(new ComponentName(name)); 262 } 263 264 @Override 265 public ComponentInstance getComponentInstance(ComponentName name) { 266 return manager.getComponent(name); 267 } 268 269 @Override 270 public ComponentManager getComponentManager() { 271 return manager; 272 } 273 274 @Override 275 public RuntimeContext getContext() { 276 return context; 277 } 278 279 protected void startExtensions() { 280 for (RuntimeExtension ext : extensions) { 281 ext.start(); 282 } 283 } 284 285 protected void stopExtensions() { 286 for (RuntimeExtension ext : extensions) { 287 ext.stop(); 288 } 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 Errors:\n"); 321 for (String warning : warnings) { 322 msg.append(" * ").append(warning).append('\n'); 323 } 324 } 325 Map<ComponentName, Set<ComponentName>> pendingRegistrations = manager.getPendingRegistrations(); 326 Map<ComponentName, Set<Extension>> missingRegistrations = manager.getMissingRegistrations(); 327 Collection<ComponentName> unstartedRegistrations = manager.getActivatingRegistrations(); 328 unstartedRegistrations.addAll(manager.getStartFailureRegistrations()); 329 msg.append(hr) 330 .append("\n= Component Loading Status: Pending: ") 331 .append(pendingRegistrations.size()) 332 .append(" / Missing: ") 333 .append(missingRegistrations.size()) 334 .append(" / Unstarted: ") 335 .append(unstartedRegistrations.size()) 336 .append(" / Total: ") 337 .append(manager.getRegistrations().size()) 338 .append('\n'); 339 for (Entry<ComponentName, Set<ComponentName>> e : pendingRegistrations.entrySet()) { 340 msg.append(" * ").append(e.getKey()).append(" requires ").append(e.getValue()).append('\n'); 341 } 342 for (Entry<ComponentName, Set<Extension>> e : missingRegistrations.entrySet()) { 343 msg.append(" * ") 344 .append(e.getKey()) 345 .append(" references missing ") 346 .append(e.getValue() 347 .stream() 348 .map(ext -> "target=" + ext.getTargetComponent().getName() + ";point=" 349 + ext.getExtensionPoint()) 350 .collect(Collectors.toList())) 351 .append('\n'); 352 } 353 for (ComponentName componentName : unstartedRegistrations) { 354 msg.append(" - ").append(componentName).append('\n'); 355 } 356 msg.append(hr); 357 return (warnings.isEmpty() && pendingRegistrations.isEmpty() && missingRegistrations.isEmpty() 358 && unstartedRegistrations.isEmpty()); 359 } 360 361}