/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.sling.launchpad.base.impl; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Dictionary; import java.util.HashSet; import java.util.Hashtable; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.felix.framework.Logger; import org.apache.sling.launchpad.api.StartupHandler; import org.apache.sling.launchpad.api.StartupListener; import org.apache.sling.launchpad.api.StartupMode; import org.apache.sling.launchpad.api.StartupService; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleListener; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.framework.ServiceReference; import org.osgi.service.startlevel.StartLevel; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; /** * The installation listener is listening for * - framework events * - events from other services * * It notifies listeners about the finish of the startup and * about an approximate progress. * * @see StartupHandler * @see StartupListener * @since 2.4.0 */ @SuppressWarnings("deprecation") public class DefaultStartupHandler implements StartupHandler, BundleListener, FrameworkListener, Runnable { /** Logger. */ private final Logger logger; /** Marker for finished. */ private final AtomicBoolean finished = new AtomicBoolean(false); /** Marker if startup should wait */ private final AtomicInteger startupShouldWait = new AtomicInteger(0); /** The queue for increasing the start level. */ private final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>(); /** The start level service. */ private final StartLevel startLevelService; /** The target start level. */ private final long targetStartLevel; /** The startup mode. */ private final StartupMode startupMode; /** Service tracker for startup listeners. */ private final ServiceTracker<StartupListener, StartupListener> listenerTracker; /** Expected bundle counts. */ private final int expectedBundlesCount; /** Active bundle set. */ private final Set<String> activeBundles = new HashSet<String>(); /** Bundle Context. */ private final BundleContext bundleContext; /** Use incremental start level handling. */ private final boolean useIncremental; /** MBean startup listener. */ private final StartupListener mbeanStartupListener; /** The started time. */ private final long startedAt; private volatile Object[] logService; /** * Constructor. * @param context Bundle context * @param logger Logger * @param manager The startup manager */ public DefaultStartupHandler(final BundleContext context, final Logger logger, final StartupManager manager, final long startedAt) { this.logger = logger; this.bundleContext = context; this.startedAt = startedAt; this.startupMode = manager.getMode(); this.targetStartLevel = manager.getTargetStartLevel(); StartupListener listener = null; try { listener = new MBeanStartupListener(); } catch ( final Exception ignore ) { // ignore } this.mbeanStartupListener = listener; this.listenerTracker = new ServiceTracker<StartupListener, StartupListener>(context, StartupListener.class, new ServiceTrackerCustomizer<StartupListener, StartupListener>() { @Override public void removedService(final ServiceReference<StartupListener> reference, final StartupListener service) { context.ungetService(reference); } @Override public void modifiedService(final ServiceReference<StartupListener> reference, final StartupListener service) { // nothing to do } @Override public StartupListener addingService(final ServiceReference<StartupListener> reference) { final StartupListener listener = context.getService(reference); if (listener != null) { try { listener.inform(startupMode, finished.get()); } catch (final Throwable t) { logger.log(Logger.LOG_ERROR, "Error calling StartupListener " + listener, t); } } return listener; } }); this.listenerTracker.open(); this.startLevelService = (StartLevel)context.getService(context.getServiceReference(StartLevel.class.getName())); context.addFrameworkListener(this); this.useIncremental = this.startupMode != StartupMode.RESTART && manager.isIncrementalStartupEnabled(); if ( !this.useIncremental ) { final Bundle[] bundles = context.getBundles(); this.expectedBundlesCount = (bundles != null && bundles.length > 0 ? bundles.length : 10); context.addBundleListener(this); } else { this.expectedBundlesCount = 10; } this.bundleContext.registerService(StartupHandler.class.getName(), this, null); this.log(Logger.LOG_INFO, "Started startup handler with target start level=" + String.valueOf(this.targetStartLevel) + ", and expected bundle count=" + String.valueOf(this.expectedBundlesCount)); final Thread t = new Thread(this); t.start(); } /** * @see org.apache.sling.launchpad.api.StartupHandler#getMode() */ @Override public StartupMode getMode() { return this.startupMode; } /** * @see org.apache.sling.launchpad.api.StartupHandler#isFinished() */ @Override public boolean isFinished() { return this.finished.get(); } /** * @see java.lang.Runnable#run() */ @Override public void run() { while ( !this.finished.get() ) { Boolean doInc = null; try { doInc = this.queue.take(); } catch (final InterruptedException e) { // ignore } if ( doInc != null && doInc ) { // if the installer is idle we first wait // we have to do this to give the installer or plugins for the installer, // time to start after the start level has changed if ( this.startupShouldWait.get() == 0 ) { this.sleep(2000L); } // now we wait until the installer is idle while ( this.startupShouldWait.get() != 0 ) { this.sleep(50L); } this.incStartLevel(); } } } /** * Increment the current start level by one */ private void incStartLevel() { final int newLevel = this.startLevelService.getStartLevel() + 1; this.log(Logger.LOG_DEBUG, "Increasing start level to " + String.valueOf(newLevel)); this.startLevelService.setStartLevel(newLevel); } /** * @see org.apache.sling.launchpad.api.StartupHandler#waitWithStartup(boolean) */ @Override public void waitWithStartup(final boolean flag) { this.log(Logger.LOG_DEBUG, "Wait with startup " + flag); if ( flag ) { this.startupShouldWait.incrementAndGet(); } else { this.startupShouldWait.decrementAndGet(); } } /** * Sleep a little bit * @param time Sleeping time */ private void sleep(final long time) { try { Thread.sleep(time); } catch ( final InterruptedException e) { // ignore } } /** * Put a task in the queue * @param info Add new boolean information to queue */ private void enqueue(final boolean info) { try { this.queue.put(info); } catch (final InterruptedException e) { // ignore } } /** * @see org.osgi.framework.FrameworkListener#frameworkEvent(org.osgi.framework.FrameworkEvent) */ @Override public void frameworkEvent(final FrameworkEvent event) { if ( finished.get() ) { return; } this.log(Logger.LOG_DEBUG, "Received framework event " + event); if ( !this.useIncremental ) { // restart if ( event.getType() == FrameworkEvent.STARTED ) { this.startupFinished(); } } else { // first startup or update if ( event.getType() == FrameworkEvent.STARTED ) { this.enqueue(true); } else if ( event.getType() == FrameworkEvent.STARTLEVEL_CHANGED ) { if ( this.startLevelService.getStartLevel() >= this.targetStartLevel ) { this.startupFinished(); } else { this.enqueue(true); final int startLevel = this.startLevelService.getStartLevel(); this.log(Logger.LOG_DEBUG, "Startup progress " + String.valueOf(startLevel) + '/' + String.valueOf(targetStartLevel)); final float ratio = (float) startLevel / (float) targetStartLevel; this.startupProgress(ratio); } } } } private void log(final int level, final String msg) { log(null, level, msg, null); } private void log(final int level, final String msg, final Throwable t) { log(null, level, msg, t); } private void log(final ServiceReference<?> sRef, final int level, final String msg, final Throwable t) { boolean loggedWithService = false; if ( this.logService == null ) { final ServiceReference<?> ref = this.bundleContext.getServiceReference("org.osgi.service.log.LogService"); if ( ref != null ) { final Object ls = this.bundleContext.getService(ref); if ( ls != null ) { final Class<?>[] formalParams = { ServiceReference.class, Integer.TYPE, String.class, Throwable.class }; try { final Method logMethod = ls.getClass().getMethod("log", formalParams); logMethod.setAccessible(true); logService = new Object[] { ls, logMethod }; } catch (final NoSuchMethodException ex) { // no need to log } } } } if ( this.logService != null ) { final Object[] params = {sRef, new Integer(level), msg, t}; try { ((Method) this.logService[1]).invoke(this.logService[0], params); loggedWithService = true; } catch (final InvocationTargetException ex) { // no need to log } catch (final IllegalAccessException ex) { // no need to log } } if ( !loggedWithService ) { logger.log(level, msg); } } /** * Notify finished startup */ private void startupFinished() { this.log(Logger.LOG_INFO, "Startup finished in " + String.valueOf(System.currentTimeMillis() - this.startedAt) + "ms"); this.finished.set(true); for (final StartupListener listener : this.listenerTracker.getServices(new StartupListener[0])) { try { listener.startupFinished(this.startupMode); } catch (Throwable t) { this.log(Logger.LOG_ERROR, "Error calling StartupListener " + listener, t); } } if ( this.mbeanStartupListener != null ) { this.mbeanStartupListener.startupFinished(this.startupMode); } // stop the queue this.enqueue(false); // clear bundle set this.activeBundles.clear(); // unregister listeners if ( !this.useIncremental ) { this.bundleContext.removeBundleListener(this); } this.bundleContext.removeFrameworkListener(this); // register startup service final Dictionary<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(StartupMode.class.getName(), this.startupMode.name()); serviceProps.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Startup Service"); serviceProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation"); this.bundleContext.registerService(StartupService.class, new StartupService() { @Override public StartupMode getStartupMode() { return startupMode; } }, serviceProps); } /** * Notify startup progress * @param ratio ratio */ private void startupProgress(final float ratio) { for (final StartupListener listener : this.listenerTracker.getServices(new StartupListener[0])) { try { listener.startupProgress(ratio); } catch (final Throwable t) { this.log(Logger.LOG_ERROR, "Error calling StartupListener " + listener, t); } } if ( this.mbeanStartupListener != null ) { this.mbeanStartupListener.startupProgress(ratio); } } /** * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent) */ @Override public void bundleChanged(final BundleEvent event) { if (!finished.get()) { this.log(Logger.LOG_DEBUG, "Received bundle event " + event); if (event.getType() == BundleEvent.RESOLVED || event.getType() == BundleEvent.STARTED) { // Add (if not existing) bundle to active bundles and refresh progress bar activeBundles.add(event.getBundle().getSymbolicName()); this.log(Logger.LOG_DEBUG, "Startup progress " + String.valueOf(activeBundles.size()) + '/' + String.valueOf(expectedBundlesCount)); final float ratio = (float) activeBundles.size() / (float) expectedBundlesCount; this.startupProgress(ratio); } else if (event.getType() == BundleEvent.STOPPED) { // Only remove bundle from active bundles, // but do not refresh progress bar, to prevent progress bar from going back activeBundles.remove(event.getBundle().getSymbolicName()); } } } }