Java tutorial
/* * Copyright (C) 2016 Keith M. Hughes * Copyright (C) 2012 Google Inc. * * 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 io.smartspaces.activity.impl; import io.smartspaces.SmartSpacesException; import io.smartspaces.activity.ActivityBehavior; import io.smartspaces.activity.ActivityState; import io.smartspaces.activity.ActivityStatus; import io.smartspaces.activity.SupportedActivity; import io.smartspaces.activity.annotation.ConfigurationPropertyAnnotationProcessor; import io.smartspaces.activity.annotation.StandardConfigurationPropertyAnnotationProcessor; import io.smartspaces.activity.component.ActivityComponent; import io.smartspaces.activity.component.ActivityComponentContext; import io.smartspaces.activity.execution.ActivityMethodInvocation; import io.smartspaces.configuration.Configuration; import io.smartspaces.hardware.driver.Driver; import io.smartspaces.resource.managed.ManagedResource; import io.smartspaces.resource.managed.ManagedResources; import io.smartspaces.resource.managed.StandardManagedResources; import io.smartspaces.scope.ManagedScope; import io.smartspaces.scope.StandardManagedScope; import io.smartspaces.tasks.ManagedTasks; import io.smartspaces.tasks.StandardManagedTasks; import io.smartspaces.util.io.FileSupport; import io.smartspaces.util.io.FileSupportImpl; import java.io.File; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * Support for building an Smart Spaces activity. * * @author Keith M. Hughes */ public abstract class BaseActivity extends ActivitySupport implements SupportedActivity, ActivityBehavior { /** * The maximum amount of time to wait for all handlers to complete, in msecs. */ private static final int SHUTDOWN_EVENT_HANDLER_COMPLETION_MAX_SAMPLE_TIME = 3000; /** * How long to wait between samples when checking for event handlers to * complete, in msecs. */ private static final int SHUTDOWN_EVENT_HANDLER_COMPLETION_SAMPLE_TIME = 500; /** * Filename for activity startup config log. */ private static final String ACTIVITY_STARTUP_CONFIG_LOG = "startup.conf"; /** * Display header to use for the activity status. */ public static final String ACTIVITY_STATUS_HEADER = "Activity Status"; /** * Display header to use for the activity status description. */ public static final String ACTIVITY_STATUS_DESCRIPTION_HEADER = "Activity Status Description"; /** * Context all activity components will run under. */ private ActivityComponentContext componentContext; /** * The collection of managed resources for the activity. */ private ManagedResources managedResources; /** * The tasks that are being managed. */ private StandardManagedTasks managedTasks; /** * The managed scope for the activity. */ private ManagedScope managedScope; /** * File support for use with the activity. */ private final FileSupport fileSupport = FileSupportImpl.INSTANCE; /** * The annotation processor for configuration parameters. */ private ConfigurationPropertyAnnotationProcessor configurationAnnotationProcessor; @Override public <T extends ActivityComponent> T getActivityComponent(String componentType) { return componentContext.getActivityComponent(componentType); } @Override public <T extends ActivityComponent> T getRequiredActivityComponent(String componentType) throws SmartSpacesException { return componentContext.getRequiredActivityComponent(componentType); } @Override public <T extends ActivityComponent> T addActivityComponent(T component) { return componentContext.addComponent(component); } @Override public void addActivityComponents(ActivityComponent... components) { componentContext.addComponents(components); } @Override public <T extends ActivityComponent> T addActivityComponent(String componentType) { return componentContext.addComponent(componentType); } @Override public void addActivityComponents(String... componentTypes) { componentContext.addComponents(componentTypes); } @Override public ConfigurationPropertyAnnotationProcessor getActivityConfigurationPropertyAnnotationProcessor() { return configurationAnnotationProcessor; } /** * Get the component context. * * @return the component context */ public ActivityComponentContext getActivityComponentContext() { return componentContext; } @Override public void addManagedResource(ManagedResource resource) { managedResources.addResource(resource); } @Override public ManagedResources getManagedResources() { return managedResources; } @Override public ManagedTasks getManagedTasks() { return managedTasks; } @Override public ManagedScope getActivityManagedScope() { return managedScope; } /** * Add a driver to the activity as a {@link ManagedResource}. * * <p> * The drivers * {@link Driver#prepare(smartspaces.system.smartspacesEnvironment, org.apache.commons.logging.Log)} * method will be called with the activity's space environment and log. * * @param driver * the driver to add to the activity */ public void addDriver(Driver driver) { driver.prepare(getSpaceEnvironment(), getConfiguration(), getLog()); addManagedResource(driver); } @Override public void updateConfiguration(Map<String, String> update) { try { handleUpdateConfigurationUnprotected(update); } catch (Throwable e) { logException("Failure when calling onActivityConfiguration", e); } } /** * Do a configuration update unprotected by an exception handler. * * @param update * the update, can be {@code null} */ private void handleUpdateConfigurationUnprotected(Map<String, String> update) { commonActivityConfigurationUpdate(update); callOnActivityConfigurationUpdate(update); } /** * Properly call {@link #onActivityConfiguration(Map)}. * * @param update * the update map */ private void callOnActivityConfigurationUpdate(Map<String, String> update) { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityConfigurationUpdate(update); } finally { getExecutionContext().exitMethod(invocation); } } @Override public void startup() { long beginStartTime = getSpaceEnvironment().getTimeProvider().getCurrentTime(); componentContext = new ActivityComponentContext(this, getActivityRuntime().getActivityComponentFactory()); managedResources = new StandardManagedResources(getLog()); managedTasks = new StandardManagedTasks(getSpaceEnvironment().getExecutorService(), getLog()); managedScope = new StandardManagedScope(managedResources, managedTasks, getSpaceEnvironment().getExecutorService()); setActivityStatus(ActivityState.STARTUP_ATTEMPT); componentContext.beginStartupPhase(); try { callOnActivityPreSetup(); logConfiguration(ACTIVITY_STARTUP_CONFIG_LOG); configurationAnnotationProcessor = new StandardConfigurationPropertyAnnotationProcessor( getConfiguration(), getLog()); configurationAnnotationProcessor.process(this); handleUpdateConfigurationUnprotected(null); commonActivitySetup(); callOnActivitySetup(); managedScope.startup(); componentContext.initialStartupComponents(); commonActivityStartup(); callOnActivityStartup(); setActivityStatus(ActivityState.RUNNING); if (getLog().isInfoEnabled()) { getLog().info(String.format("Live activity %s running in %d milliseconds", getUuid(), (getSpaceEnvironment().getTimeProvider().getCurrentTime() - beginStartTime))); } // Let everything start running before Post Startup componentContext.endStartupPhase(true); try { commonActivityPostStartup(); callOnActivityPostStartup(); } catch (Throwable e) { logException("Exception while running Post Startup", e); } } catch (Throwable e) { componentContext.shutdownAndClearRunningComponents(); componentContext.endStartupPhase(false); managedTasks.shutdownAll(); managedResources.shutdownResourcesAndClear(); logException("Could not startup activity", e); setActivityStatus(ActivityState.STARTUP_FAILURE, null, e); } } /** * Properly call {@link #onActivityPreSetup()}. */ private void callOnActivityPreSetup() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityPreSetup(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Properly call {@link #onActivityStartup()}. */ private void callOnActivitySetup() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivitySetup(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Properly call {@link #onActivityStartup()}. */ private void callOnActivityStartup() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityStartup(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Properly call {@link #onActivityPostStartup()}. */ private void callOnActivityPostStartup() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityPostStartup(); } finally { getExecutionContext().exitMethod(invocation); } } @Override public void handleStartupFailure() { try { callOnActivityStartupFailure(); } finally { shutdown(); } } /** * Properly call {@link #onActivityStartupFailure}. */ private void callOnActivityStartupFailure() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityStartupFailure(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Any activity specific handling of startup failures. */ public void onActivityStartupFailure() { // Default is to do nothing. } /** * Get the active status detail for this activity. By default this is simply * an 'active' indicator along with status details from any managed * components. * * @param activityStatus * the raw status of the activity * * @return status detail for this activity including components */ protected String getActivityStatusDetailComposite(ActivityStatus activityStatus) { StringBuilder detailString = new StringBuilder(); detailString.append(String.format(StatusDetail.HEADER_FORMAT, "status-detail")) .append(String.format(StatusDetail.PREFIX_FORMAT, "activity-status")).append(ACTIVITY_STATUS_HEADER) .append(StatusDetail.SEPARATOR).append(activityStatus.getState()).append(StatusDetail.POSTFIX); String description = activityStatus.getDescription(); if (description != null && !description.trim().isEmpty()) { detailString.append(String.format(StatusDetail.PREFIX_FORMAT, "activity-status-description")) .append(ACTIVITY_STATUS_DESCRIPTION_HEADER).append(StatusDetail.SEPARATOR).append(description) .append(StatusDetail.POSTFIX); } if (componentContext != null) { for (ActivityComponent component : componentContext.getConfiguredComponents()) { String detail = component.getComponentStatusDetail(); if (detail != null) { detailString.append(String.format(StatusDetail.PREFIX_FORMAT, "component-status")) .append(component.getDescription()).append(StatusDetail.SEPARATOR).append(detail) .append(StatusDetail.POSTFIX); } } } if (managedResources != null) { List<ManagedResource> resources = managedResources.getResources(); if (!resources.isEmpty()) { detailString.append(String.format(StatusDetail.PREFIX_FORMAT, "managed-resources")) .append("Managed Resources").append(StatusDetail.SEPARATOR); for (ManagedResource managedResource : resources) { detailString.append(managedResource.toString()).append(StatusDetail.BREAK); } detailString.append(StatusDetail.POSTFIX); } } detailString.append(StatusDetail.FOOTER); return detailString.toString(); } @Override public void activate() { try { commonActivityActivate(); callOnActivityActivate(); setActivityStatus(ActivityState.ACTIVE); } catch (Throwable e) { logException("Cannot activate activity", e); setActivityStatus(ActivityState.ACTIVATE_FAILURE, null, e); } } /** * Properly call {@link #onActivityActivate()}. */ private void callOnActivityActivate() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityActivate(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Activate the activity. */ @Override public void onActivityActivate() { // Default is to do nothing. } @Override public boolean isActivated() { return ActivityState.ACTIVE.equals(getActivityStatus().getState()); } @Override public void deactivate() { try { commonActivityDeactivate(); callOnActivityDeactivate(); setActivityStatus(ActivityState.RUNNING); } catch (Throwable e) { logException("Cannot deactivate activity", e); setActivityStatus(ActivityState.DEACTIVATE_FAILURE, null, e); } } /** * Properly call {@link #onActivityDeactivate()}. */ private void callOnActivityDeactivate() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityDeactivate(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Deactivate the activity. */ @Override public void onActivityDeactivate() { // Default is to do nothing. } @Override public void shutdown() { Throwable t = null; boolean cleanShutdown = true; try { callOnActivityPreShutdown(); } catch (Throwable e) { t = e; logException("Error while calling onActivityPreShutdown", e); cleanShutdown = false; } try { commonActivityPreShutdown(); } catch (Throwable e) { t = e; logException("Error while calling commonActivityPreShutdown", e); cleanShutdown = false; } componentContext.beginShutdownPhase(); boolean handlersAllComplete = componentContext.waitOnNoProcessingHandlings( SHUTDOWN_EVENT_HANDLER_COMPLETION_SAMPLE_TIME, SHUTDOWN_EVENT_HANDLER_COMPLETION_MAX_SAMPLE_TIME); if (!handlersAllComplete) { getLog().warn(String.format("Handlers still running after %d msecs of shutdown", SHUTDOWN_EVENT_HANDLER_COMPLETION_MAX_SAMPLE_TIME)); } if (managedTasks != null) { managedTasks.shutdownAll(); managedTasks = null; } try { if (cleanShutdown) { callOnActivityShutdown(); } } catch (Throwable e) { t = e; logException("Error while calling onActivityShutdown", e); cleanShutdown = false; } try { if (cleanShutdown) { commonActivityShutdown(); } } catch (Throwable e) { t = e; logException("Error while calling commonActivityShutdown", e); cleanShutdown = false; } try { callOnActivityCleanup(); } catch (Throwable e) { t = e; logException("Error while calling onActivityCleanup", e); cleanShutdown = false; } try { commonActivityCleanup(); } catch (Throwable e) { t = e; logException("Error while cleaning up common activity", e); cleanShutdown = false; } try { if (!componentContext.shutdownAndClearRunningComponents()) { cleanShutdown = false; } } catch (Throwable e) { t = e; logException("Error while shutting down activity components", e); cleanShutdown = false; } managedResources.shutdownResourcesAndClear(); if (cleanShutdown) { setActivityStatus(ActivityState.READY, "Post clean shutdown"); } else { setActivityStatus(ActivityState.SHUTDOWN_FAILURE, "Failures during shutdown", t); } } /** * Properly call {@link #onActivityPreShutdown()}. */ private void callOnActivityPreShutdown() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityPreShutdown(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Properly call {@link #onActivityShutdown()}. */ private void callOnActivityShutdown() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityShutdown(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Properly call {@link #onActivityCleanup()}. */ private void callOnActivityCleanup() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityCleanup(); } finally { getExecutionContext().exitMethod(invocation); } } @Override public void checkActivityState() { // If in startup, don't do a scan ActivityState state = getActivityStatus().getState(); if (state == ActivityState.STARTUP_ATTEMPT || !state.isRunning()) { return; } boolean areAllComponentsRunning = componentContext.areAllComponentsRunning(); boolean callOnCheckActivityState = callOnCheckActivityState(); if (!areAllComponentsRunning || !callOnCheckActivityState) { // TODO(keith): Figure out if we can get an exception in here setActivityStatus(ActivityState.CRASHED, "Activity no longer running"); getLog().error( String.format("Activity marked as CRASHED, components stat %s, onCheckActivityState() %s", areAllComponentsRunning, callOnCheckActivityState)); try { callOnActivityFailure(); } catch (Throwable e) { logException("Error while calling onActivityFailure", e); } try { callOnActivityCleanup(); } catch (Throwable e) { logException("Error while calling onActivityCleanup", e); } try { commonActivityCleanup(); } catch (Throwable e) { logException("Error while cleaning up activity", e); } } } /** * Properly call {@link #onActivityShutdown()}. */ private void callOnActivityFailure() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { onActivityFailure(); } finally { getExecutionContext().exitMethod(invocation); } } /** * Call {@link #onActivityCheckState()} properly. * * @return {@code true} if the activity is running correctly */ private boolean callOnCheckActivityState() { ActivityMethodInvocation invocation = getExecutionContext().enterMethod(); try { return onActivityCheckState(); } finally { getExecutionContext().exitMethod(invocation); } } @Override public void commonActivityConfigurationUpdate(Map<String, String> update) { // Default is to do nothing. } @Override public void commonActivitySetup() { // Default is do nothing. } @Override public void commonActivityStartup() { // Default is do nothing. } @Override public void commonActivityPostStartup() { // Default is do nothing. } @Override public void commonActivityActivate() { // Default is do nothing. } @Override public void commonActivityDeactivate() { // Default is do nothing. } @Override public void commonActivityPreShutdown() { // Default is do nothing. } @Override public void commonActivityShutdown() { // Default is do nothing. } @Override public void commonActivityCleanup() { // Default is do nothing. } @Override public void onActivityConfigurationUpdate(Map<String, String> update) { // Default is do nothing. } @Override public void onActivityPreSetup() { // Default is to do nothing. } @Override public void onActivitySetup() { // Default is to do nothing. } @Override public void onActivityStartup() { // Default is to do nothing. } @Override public void onActivityPostStartup() { // Default is to do nothing. } @Override public void onActivityPreShutdown() { // Default is nothing on pre shutdown. } @Override public void onActivityShutdown() { // Default is nothing on shutdown. } @Override public void onActivityComponentError(ActivityComponent component, String message, Throwable t) { // Default is to do nothing. Basic logging is handled elsewhere. } @Override public boolean onActivityCheckState() { // Default is all is OK. return true; } @Override public void onActivityFailure() { // Default is to do nothing. } @Override public void onActivityCleanup() { // Default is to do nothing. } @Override protected ActivityStatus potentiallyModifyStatus(ActivityStatus activityStatus) { return new ActivityStatus(activityStatus.getState(), getActivityStatusDetailComposite(activityStatus), activityStatus.getException()); } /** * Log the activity configuration information to an activity config log file. * * @param logFile * file to write the log into */ private void logConfiguration(String logFile) { try { StringBuilder logBuilder = new StringBuilder(); getLog().info("Logging activity configuration to " + logFile); Configuration configuration = getConfiguration(); Map<String, String> configMap = configuration.getCollapsedMap(); TreeMap<String, String> sortedMap = new TreeMap<>(configMap); for (Map.Entry<String, String> entry : sortedMap.entrySet()) { String value; try { value = configuration.evaluate(entry.getValue()); } catch (Throwable e) { value = e.toString(); } logBuilder.append(String.format("%s=%s\n", entry.getKey(), value)); } File configLog = new File(getActivityFilesystem().getLogDirectory(), logFile); fileSupport.writeFile(configLog, logBuilder.toString()); } catch (Throwable e) { logException("While logging activity configuration", e); } } }