/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.component.execution.internal;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceException;
import org.osgi.framework.ServiceRegistration;
import de.rcenvironment.core.communication.api.CommunicationService;
import de.rcenvironment.core.component.api.ComponentConstants;
import de.rcenvironment.core.component.api.DistributedComponentKnowledgeService;
import de.rcenvironment.core.component.execution.api.ComponentExecutionContext;
import de.rcenvironment.core.component.execution.api.ComponentExecutionController;
import de.rcenvironment.core.component.execution.api.ComponentExecutionControllerService;
import de.rcenvironment.core.component.execution.api.ComponentExecutionException;
import de.rcenvironment.core.component.execution.api.ComponentExecutionInformation;
import de.rcenvironment.core.component.execution.api.ComponentState;
import de.rcenvironment.core.component.execution.api.EndpointDatumSerializer;
import de.rcenvironment.core.component.execution.api.ExecutionConstants;
import de.rcenvironment.core.component.execution.api.ExecutionContext;
import de.rcenvironment.core.component.execution.api.ExecutionControllerException;
import de.rcenvironment.core.component.execution.api.LocalExecutionControllerUtilsService;
import de.rcenvironment.core.component.execution.api.WorkflowExecutionControllerCallbackService;
import de.rcenvironment.core.component.execution.impl.ComponentExecutionInformationImpl;
import de.rcenvironment.core.component.model.api.ComponentInstallation;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.rpc.RemoteOperationException;
import de.rcenvironment.core.utils.common.security.AllowRemoteAccess;
import de.rcenvironment.core.utils.incubator.DebugSettings;
import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription;
/**
* Implementation of {@link ComponentExecutionControllerService}.
*
* @author Doreen Seider
*/
public class ComponentExecutionControllerServiceImpl implements ComponentExecutionControllerService {
private static final Log LOG = LogFactory.getLog(ComponentExecutionControllerServiceImpl.class);
private static final boolean VERBOSE_LOGGING = DebugSettings.getVerboseLoggingEnabled(ComponentExecutionControllerImpl.class);
/**
* Wait one minute max for component to get cancelled. If it takes longer, it will cancel in the background and will be disposed the
* next time of garbage collecting or the time afterwards etc.
*/
private static final int CANCEL_TIMEOUT_MSEC = 60 * 1000;
private static final int COMPONENT_CONTROLLER_GARBAGE_COLLECTION_INTERVAL_MSEC = 90 * 1000;
private BundleContext bundleContext;
private CommunicationService communicationService;
private LocalExecutionControllerUtilsService exeCtrlUtilsService;
private DistributedComponentKnowledgeService compKnowledgeService;
private EndpointDatumSerializer endpointDatumSerializer;
// FIXME Currently, tokens, which were not used, are not removed over time-> memory leak, but
// only minor because of small amount of
// unused tokens and because of small size of each token. But anyways: token garbage collection
// must be added -- seid_do, Nov 2013
// (see: https://www.sistec.dlr.de/mantis/view.php?id=9539)
private final Set<String> executionAuthTokens = Collections.synchronizedSet(new HashSet<String>());
private Map<String, ServiceRegistration<?>> componentServiceRegistrations = Collections.synchronizedMap(
new HashMap<String, ServiceRegistration<?>>());
private Map<String, ComponentExecutionInformation> componentExecutionInformations = Collections.synchronizedMap(
new HashMap<String, ComponentExecutionInformation>());
private ScheduledFuture<?> componentControllerGarbargeCollectionFuture;
protected void activate(BundleContext context) {
bundleContext = context;
componentControllerGarbargeCollectionFuture = ConcurrencyUtils.getAsyncTaskService().scheduleAtFixedRate(new Runnable() {
@Override
@TaskDescription("Garbage collection: Component controllers")
public void run() {
Set<String> compExeIds = new HashSet<>(componentExecutionInformations.keySet());
if (VERBOSE_LOGGING) {
LOG.debug("Running garbage collection for component controllers: " + compExeIds);
}
for (String executionId : compExeIds) {
ComponentExecutionController componentController = null;
try {
componentController = exeCtrlUtilsService.getExecutionController(
ComponentExecutionController.class, executionId, bundleContext);
} catch (ExecutionControllerException e) {
LOG.debug(StringUtils.format("Component controller garbage collection: Skip component controller: %s; cause: %s",
executionId, e.getMessage()));
continue;
}
if (!componentController.isWorkflowControllerReachable()) {
LOG.debug("Found component controller with unreachable workflow controller: " + executionId);
if (!ComponentConstants.FINAL_COMPONENT_STATES_WITH_DISPOSED.contains(componentController.getState())) {
try {
LOG.debug("Cancel component controller: " + executionId);
componentController.cancelSync(CANCEL_TIMEOUT_MSEC);
} catch (InterruptedException e) {
Thread.interrupted(); // ignore and try to go further
} catch (RuntimeException e) {
LOG.error("Cancelling component during garbage collecting failed: " + executionId, e);
}
}
if (ComponentConstants.FINAL_COMPONENT_STATES.contains(componentController.getState())) {
try {
LOG.debug("Dispose component controller: " + executionId);
performDispose(executionId);
} catch (ExecutionControllerException | RemoteOperationException e) {
LOG.error(StringUtils.format("Failed to dispose component during garbage collecting: %s; cause: %s",
executionId, e.toString()));
}
}
}
}
}
}, COMPONENT_CONTROLLER_GARBAGE_COLLECTION_INTERVAL_MSEC);
}
protected void deactivate() {
if (componentControllerGarbargeCollectionFuture != null) {
componentControllerGarbargeCollectionFuture.cancel(true);
}
}
@Override
@AllowRemoteAccess
public void addComponentExecutionAuthToken(String authToken) throws RemoteOperationException {
executionAuthTokens.add(authToken);
}
@Override
@AllowRemoteAccess
public String createExecutionController(ComponentExecutionContext compExeCtx, String authToken,
Long currentTimestampOnWorkflowNode) throws ComponentExecutionException, RemoteOperationException {
if (!isAllowed(compExeCtx, authToken)) {
throw new ComponentExecutionException("No valid auth token given.");
}
Map<String, String> searchProperties = new HashMap<>();
searchProperties.put(ExecutionConstants.EXECUTION_ID_OSGI_PROP_KEY, compExeCtx.getWorkflowExecutionIdentifier());
WorkflowExecutionControllerCallbackService wfExeCtrlCallbackService;
wfExeCtrlCallbackService =
communicationService.getRemotableService(WorkflowExecutionControllerCallbackService.class, compExeCtx.getWorkflowNodeId());
ComponentExecutionController componentController =
new ComponentExecutionControllerImpl(compExeCtx, wfExeCtrlCallbackService, currentTimestampOnWorkflowNode);
Dictionary<String, String> registerProperties = new Hashtable<String, String>();
registerProperties.put(ExecutionConstants.EXECUTION_ID_OSGI_PROP_KEY, compExeCtx.getExecutionIdentifier());
ServiceRegistration<?> serviceRegistration = bundleContext.registerService(ComponentExecutionController.class.getName(),
componentController, registerProperties);
ComponentExecutionInformationImpl componentExecutionInformation = new ComponentExecutionInformationImpl(compExeCtx);
synchronized (componentExecutionInformations) {
componentExecutionInformations.put(compExeCtx.getExecutionIdentifier(), componentExecutionInformation);
componentServiceRegistrations.put(compExeCtx.getExecutionIdentifier(), serviceRegistration);
}
return compExeCtx.getExecutionIdentifier();
}
private boolean isAllowed(ExecutionContext executionContext, String authToken) {
Collection<ComponentInstallation> allPublishedInstallations = compKnowledgeService.getCurrentComponentKnowledge()
.getAllPublishedInstallations();
boolean published = false;
for (ComponentInstallation compInst : allPublishedInstallations) {
if (compInst.getInstallationId().equals(((ComponentExecutionContext) executionContext).getComponentDescription()
.getComponentInstallation().getInstallationId())) {
published = true;
}
}
boolean authTokenExists = executionAuthTokens.remove(authToken);
return published || authTokenExists;
}
@Override
@AllowRemoteAccess
public void performPrepare(String executionId) throws ExecutionControllerException, RemoteOperationException {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).prepare();
}
@Override
@AllowRemoteAccess
public void performStart(String executionId) throws ExecutionControllerException, RemoteOperationException {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).start();
}
@Override
@AllowRemoteAccess
public void performCancel(String executionId) throws ExecutionControllerException, RemoteOperationException {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).cancel();
}
@Override
@AllowRemoteAccess
public void performPause(String executionId) throws ExecutionControllerException, RemoteOperationException {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).pause();
}
@Override
@AllowRemoteAccess
public void performResume(String executionId) throws ExecutionControllerException, RemoteOperationException {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).resume();
}
@Override
@AllowRemoteAccess
public ComponentExecutionInformation getComponentExecutionInformation(String verificationToken) throws RemoteOperationException {
for (Entry<String, ComponentExecutionController> entry : exeCtrlUtilsService
.getExecutionControllers(ComponentExecutionController.class, bundleContext).entrySet()) {
if (entry.getValue().getVerificationToken() != null && entry.getValue().getVerificationToken().equals(verificationToken)) {
return componentExecutionInformations.get(entry.getKey());
}
}
return null;
}
@Override
@AllowRemoteAccess
public Boolean performVerifyResults(String executionId, String verificationToken, Boolean verified)
throws ExecutionControllerException, RemoteOperationException {
return exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext)
.verifyResults(verificationToken, verified);
}
@Override
@AllowRemoteAccess
public void performDispose(String executionId) throws ExecutionControllerException, RemoteOperationException {
try {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).dispose();
} catch (ServiceException e) {
LOG.warn("Ignored component disposal request as there is no component controller registered (anymore);"
+ " most likely disposal was requested more than once: " + e.toString());
}
synchronized (componentExecutionInformations) {
componentExecutionInformations.remove(executionId);
if (componentServiceRegistrations.containsKey(executionId)) {
componentServiceRegistrations.get(executionId).unregister();
componentServiceRegistrations.remove(executionId);
}
}
}
@Override
@AllowRemoteAccess
public ComponentState getComponentState(String executionId) throws ExecutionControllerException, RemoteOperationException {
return exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext).getState();
}
@Override
public Collection<ComponentExecutionInformation> getComponentExecutionInformations() {
synchronized (componentExecutionInformations) {
return new HashSet<ComponentExecutionInformation>(componentExecutionInformations.values());
}
}
@Override
public void onSendingEndointDatumFailed(String executionId, String serializedEndpointDatum, RemoteOperationException e)
throws ExecutionControllerException {
exeCtrlUtilsService.getExecutionController(ComponentExecutionController.class, executionId, bundleContext)
.onSendingEndointDatumFailed(endpointDatumSerializer.deserializeEndpointDatum(serializedEndpointDatum), e);
}
protected void bindCommunicationService(CommunicationService newService) {
communicationService = newService;
}
protected void bindLocalExecutionControllerUtilsService(LocalExecutionControllerUtilsService newService) {
exeCtrlUtilsService = newService;
}
protected void bindDistributedComponentKnowledgeService(DistributedComponentKnowledgeService componentKnowledgeService) {
this.compKnowledgeService = componentKnowledgeService;
}
protected void bindEndpointDatumSerializer(EndpointDatumSerializer newService) {
this.endpointDatumSerializer = newService;
}
}