All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ogema.application.manager.impl.ApplicationManagerImpl Maven / Gradle / Ivy

Go to download

Application Manager Package of the OGEMA reference implementation by Fraunhofer Society.

The newest version!
/**
 * Copyright 2011-2018 Fraunhofer-Gesellschaft zur Förderung der angewandten Wissenschaften e.V.
 *
 * Licensed 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.ogema.application.manager.impl;

import java.io.File;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.ogema.accesscontrol.AdminPermission;
import org.ogema.core.administration.AdministrationManager;
import org.ogema.core.administration.FrameworkClock;
import org.ogema.core.administration.RegisteredTimer;
import org.ogema.core.application.AppID;
import org.ogema.core.application.Application;
import org.ogema.core.application.ApplicationManager;
import org.ogema.core.application.ExceptionListener;
import org.ogema.core.application.Timer;
import org.ogema.core.application.TimerListener;
import org.ogema.core.channelmanager.ChannelAccess;
import org.ogema.core.hardwaremanager.HardwareManager;
import org.ogema.core.installationmanager.InstallationManagement;
import org.ogema.core.logging.OgemaLogger;
import org.ogema.core.rads.impl.AdvancedAccessImpl;
import org.ogema.core.resourcemanager.ResourceAccess;
import org.ogema.core.resourcemanager.ResourceManagement;
import org.ogema.core.resourcemanager.pattern.ResourcePatternAccess;
import org.ogema.core.security.WebAccessManager;
import org.ogema.core.tools.SerializationManager;
import org.ogema.patternaccess.AdministrationPatternAccess;
import org.ogema.resourcemanager.impl.ApplicationResourceManager;
import org.ogema.timer.TimerRemovedListener;
import org.ogema.timer.TimerScheduler;
import org.ogema.tools.impl.SerializationManagerImpl;
import org.ogema.util.Util;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ApplicationManagerImpl implements ApplicationManager, TimerRemovedListener {

	private static final long serialVersionUID = 11534545646813546L;
	final TimerScheduler scheduler;
	final List timers;
	private final FrameworkClock clock;

	protected final ExecutorService executor;
	private final Queue> workQueue;
	private static final int WORKQUEUE_FORCE_DRAIN_SIZE = 50;
	final Callable drainWorkQueueTask;
	private final ApplicationThreadFactory tfac;

	private final Application application;
	ApplicationTracker tracker;
	protected final Logger logger;
	final ApplicationResourceManager resMan;
	AdministrationPatternAccess advAcc;
	private final AppID appID;
	private final List exceptionListeners = new ArrayList<>();

	final BundleContext bContext;

	public ApplicationManagerImpl(Application app, ApplicationTracker tracker, AppID id) {
		this.drainWorkQueueTask = new Callable() {

			@Override
			public Void call() throws Exception {
				drainWorkQueue();
				return null;
			}
		};
		this.tracker = Objects.requireNonNull(tracker);
		this.scheduler = tracker.getTimerScheduler();
		this.clock = tracker.getClock();
		this.application = Objects.requireNonNull(app);
		this.appID = id;
		workQueue = new ConcurrentLinkedQueue<>();
		this.executor = Executors.newSingleThreadExecutor(tfac = new ApplicationThreadFactory(application));
		logger = LoggerFactory.getLogger("org.ogema.core.application-" + app.getClass().getName());
		this.resMan = new ApplicationResourceManager(this, app, tracker.getResourceDBManager(),
				tracker.getPermissionManager());
		timers = new LinkedList<>();

		this.bContext = id.getBundle().getBundleContext();
	}

	// constructor used only for timer tests
	protected ApplicationManagerImpl(Application app, TimerScheduler sched, FrameworkClock clock) {
		this.drainWorkQueueTask = new Callable() {

			@Override
			public Void call() throws Exception {
				drainWorkQueue();
				return null;
			}
		};
		this.application = app;
		this.scheduler = sched;
		this.clock = clock;
		this.bContext = null;
		workQueue = new ConcurrentLinkedQueue<>();
		this.executor = Executors.newSingleThreadExecutor(tfac = new ApplicationThreadFactory(application));
		logger = LoggerFactory.getLogger("AppMan." + app.getClass().getName());
		resMan = null;
		this.appID = AppIDImpl.getNewID(app);
		timers = new LinkedList<>();
	}

	@Override
	public void shutdown() {
		tracker.removeApplication(application);
	}

	protected void startApplication() {
		Callable callStart = new Callable() {

			@Override
			public Boolean call() throws Exception {
				Util.currentAppThreadLocale.set(appID);
				try {
					application.start(ApplicationManagerImpl.this);
				} catch (Throwable t) {
					logger.error("app {} failed to start: ", application, t);
					close();
					throw t;
				}
				return true;
			}

		};
		submitEvent(callStart);
	}

	protected boolean stopApplication() {
		boolean stopped = false;
		if (isApplicationThread()) {
			try {
				application.stop(Application.AppStopReason.APP_STOP);
			} catch (Throwable e) { // user implemented code, better catch everything
				logger.error("Error stopping application " + appID.getIDString(),e);
			}
			stopped = true;
		}
		else {
			Callable callStop = new Callable() {

				@Override
				public Boolean call() throws Exception {
					// XXX actually, i have no idea why it is stopped...
					application.stop(Application.AppStopReason.APP_STOP);
					return true;
				}

			};

			try {
				// TODO consider removing all other callbacks in queue before
				// submitting this.
				// FIXME the timeout does not seem to work...
				final Future stopEvent = submitEvent(callStop);
				if (stopEvent != null) {
					try {
						stopped = stopEvent.get(3, TimeUnit.SECONDS);
					} catch (TimeoutException ee) {
						// the app-specific logger output typically has a high log level configured
						LoggerFactory.getLogger(ApplicationTracker.class).warn("Application takes longer to shutdown than requested: {}",application.getClass().getName());
						stopped = stopEvent.get(7, TimeUnit.SECONDS);
					}
				}
				else {
					stopped = executor.isShutdown(); // if event was rejected, assume stopped from executor status.
				}
			} catch (InterruptedException ex) {
				logger.warn("stop() call of application " + application.getClass().getName() + " interrupted.", ex);
			} catch (ExecutionException ex) {
				logger.warn("stop() call of application " + application.getClass().getName() + " failed.", ex);
			} catch (TimeoutException te) {
				logger.warn("stop() call of application " + application.getClass().getName() + " timed out.");
				if (tfac.getLastThread() != null) {
					List l = Arrays.asList(tfac.getLastThread().getStackTrace());
					logger.debug("application '%s' stop() call timed out, application thread stack trace: %s",
							application.getClass().getName(), l);
				}
			}
		}
		if (stopped) {
			exceptionListeners.clear();
		}
		return stopped;
	}

	/**
	 * @return the resManager
	 */
	@Override
	public ResourceManagement getResourceManagement() {
		return resMan;
	}

	@Override
	public ResourceAccess getResourceAccess() {
		return resMan;
	}

	@Override
	public Timer createTimer(long period) {
		Timer t = scheduler.createTimer(executor, getLogger(),this);
		t.setTimingInterval(period);
		synchronized (timers) {
			timers.add(t);
		}
		return t;
	}

	@Override
	public Timer createTimer(long millies, TimerListener listener) {
		Timer timer = createTimer(millies);
		timer.addListener(listener);
		return timer;
	}

	@Override
	public void destroyTimer(Timer t) {
		// will trigger a timerRemoved callback to this object
		t.destroy();
	}

	@Override
	public InstallationManagement getInstallationManagement() {
		// TODO Auto-generated method stub
		return null;
	}

	public ChannelAccess getChannelDriverManager() {
		return tracker.getChannelAccess();
	}

	@Override
	public HardwareManager getHardwareManager() {
		return tracker.getHardwareManager();
	}

	@Override
	public void addExceptionListener(ExceptionListener listener) {
		if (exceptionListeners.contains(listener)) {
			logger.debug(
					"Application tried to add an already-registered exception listener a second time. Request has been ignored.");
		}
		else {
			exceptionListeners.add(listener);
		}
	}

	@Override
	public WebAccessManager getWebAccessManager() {
		return tracker.getWebAccessManager(appID);
	}

	// FIXME class AdvancedAccessImpl should not be accessible 
	@Override
	public synchronized ResourcePatternAccess getResourcePatternAccess() {
		if (advAcc == null) {
			advAcc = new AdvancedAccessImpl(this, tracker.getPermissionManager());
		}
		return advAcc;
	}

	@Override
	public AdministrationManager getAdministrationManager() {
		final SecurityManager sm = System.getSecurityManager();
		if (sm != null)
			sm.checkPermission(new AdminPermission(AdminPermission.APP));
		return tracker.administration;
	}

	@Override
	public ChannelAccess getChannelAccess() {
		return tracker.channelAccess;
	}

	@Override
	public OgemaLogger getLogger() {
		final Logger logger = LoggerFactory.getLogger(application.getClass().getName());
		return logger instanceof OgemaLogger ? (OgemaLogger) logger : new SimpleLogger(logger);
	}

	@Override
	public long getFrameworkTime() {
		return clock.getExecutionTime();
	}

	/**
	 * shutdown timers and executor, calling stop() on the application is done by {@link ApplicationTracker}
	 */
	protected void close() {
		List timersCopy;
		synchronized (timers) {
			timersCopy = new LinkedList<>(timers); // copy list to avoid ConcurrentModification 
		}
		for (Iterator it = timersCopy.iterator(); it.hasNext();) {
			it.next().destroy(); // will remove timer from timers list
		}
		workQueue.clear();
		executor.shutdown();
		try {
			boolean shutdown = executor.awaitTermination(2, TimeUnit.SECONDS);
			if (!shutdown) {
				executor.shutdownNow();
				executor.awaitTermination(2, TimeUnit.SECONDS);
			}
		} catch (InterruptedException e) { /* ignore */}
		synchronized (this) {
			if (advAcc != null)
				advAcc.close();
			advAcc = null;
		}
		resMan.close();
		tracker.closeWebAccessManager(appID);
		if (!executor.isTerminated()) 
			logger.error("App {} did not shut down properly, there are still running tasks",appID.getIDString());
		else
			logger.debug("shut down application manager for app '{}'", appID.getIDString());
		((AppIDImpl) appID).close();
	}

	@Override
	public  Future submitEvent(Callable application) {
		if (executor.isShutdown()) {
			return null;
		}
		Future f = executor.submit(application);
		workQueue.add(f);
		if (workQueue.size() > WORKQUEUE_FORCE_DRAIN_SIZE) {
			final Future future = workQueue.peek();
			if (future != null && future.isDone()) {
				executor.submit(drainWorkQueueTask);
			}
		}
		return f;
	}

	/**
	 * Removes completed futures from the workqueue and logs all exceptions as warnings.
	 * Synchronization note: must be called in app thread only.
	 */
	protected void drainWorkQueue() {
		while (!workQueue.isEmpty() && workQueue.peek().isDone()) {
			Future f = workQueue.poll();
			try {
				f.get();
			} catch (ExecutionException ee) {
				reportException(ee.getCause());
			} catch (InterruptedException ie) {
				// after isDone() == true?!?
				getLogger().error("really unexpected exception in ApplicationManagerImpl.drainWorkQueue(), review code",
						ie);
			}
		}
		// System.out.printf("%d jobs done, %d in queue%n", done, workQueue.size());
	}

	@Override
	public SerializationManager getSerializationManager() {
		if (System.getSecurityManager() == null) {
			return new SerializationManagerImpl(getResourceAccess(), getResourceManagement());
		}
		else {
			return AccessController.doPrivileged(new PrivilegedAction() {

				@Override
				public SerializationManager run() {
					return new SerializationManagerImpl(getResourceAccess(), getResourceManagement());
				}
			});
		}
	}

	/**
	 * @return true iff the current thread is this application's thread.
	 */
	public boolean isApplicationThread() {
		return Thread.currentThread() == tfac.getLastThread();
	}

	@Override
	public AppID getAppID() {
		return appID;
	}

	@Override
	public File getDataFile(String filename) {
		return this.bContext.getDataFile(filename);
	}

	@Override
	public SerializationManager getSerializationManager(int maxDepth, boolean followReferences,
			boolean writeSchedules) {
		final SerializationManagerImpl result = new SerializationManagerImpl(getResourceAccess(),
				getResourceManagement());
		result.setMaxDepth(maxDepth);
		result.setFollowReferences(followReferences);
		result.setSerializeSchedules(writeSchedules);
		return result;
	}

	@Override
	public void reportException(Throwable exception) {

		if (exceptionListeners.isEmpty()) {
			getLogger().error(
					"Exception was reported but no ExceptionListener was registered to handle it",
					exception);
		}
		for (ExceptionListener listener : exceptionListeners) {
			listener.exceptionOccured(exception);
		}

	}

	protected List getTimers() {
		synchronized (timers) {
			return DefaultRegisteredTimer.asRegisteredTimers(this, timers);
		}
	}

	@Override
	public void timerRemoved(Timer timer) {
		synchronized (timers) {
			timers.remove(timer);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy