/****************************************************************************
* Copyright (C) 2012 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.gui.executor;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.openecard.gui.ResultStatus;
import org.openecard.gui.StepResult;
import org.openecard.gui.UserConsentNavigator;
import org.openecard.gui.definition.InputInfoUnit;
import org.openecard.gui.definition.OutputInfoUnit;
import org.openecard.gui.definition.Step;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class capable of displaying and executing a user consent. <br/>
* This class is a helper to display the steps of a user consent. It displays one after the other and reacts differently
* depending of the outcome of a step. It also executes actions associated with the steps after they are finished.
*
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class ExecutionEngine {
private static final Logger logger = LoggerFactory.getLogger(ExecutionEngine.class);
private final UserConsentNavigator navigator;
private final TreeMap<String, ExecutionResults> results = new TreeMap<String, ExecutionResults>();
/**
* Creates an ExecutionEngine instance and initializes it with the given navigator.
* The navigator must be previously obtained from a user consent implementation.
*
* @param navigator The navigator used to initialize this instance with.
*/
public ExecutionEngine(UserConsentNavigator navigator) {
this.navigator = navigator;
}
/**
* Processes the user consent associated with this instance. <br/>
* The following algorithm is used to process the dialog.
* <ol>
* <li>Display the first step.</li>
* <li>Evaluate step result. Break execution on CANCEL.</li>
* <li>Execute step action. Break execution on CANCEL.</li>
* <li>Display either next previous or current step, or a replacement according to result.</li>
* <li>Proceed with point 2.</li>
* </ol>
*
* @return Overall result of the execution.
*/
public ResultStatus process() {
StepResult next = navigator.next(); // get first step
// loop over steps. break inside loop
while (true) {
ResultStatus result = next.getStatus();
// close dialog on cancel
if (result == ResultStatus.CANCEL) {
navigator.close();
return result;
}
// get result and put it in resultmap
List<OutputInfoUnit> stepResults = next.getResults();
Map<String, ExecutionResults> oldResults = Collections.unmodifiableMap(results);
results.put(next.getStepID(), new ExecutionResults(next.getStepID(), stepResults));
// replace InfoInputUnit values in live list
if (! next.getStep().isResetOnLoad()) {
Step s = next.getStep();
List<InputInfoUnit> inputInfo = s.getInputInfoUnits();
Map<String, InputInfoUnit> infoMap = new HashMap<String, InputInfoUnit>();
// create index over infos
for (InputInfoUnit nextInfo : inputInfo) {
infoMap.put(nextInfo.getID(), nextInfo);
}
for (OutputInfoUnit nextOut : stepResults) {
InputInfoUnit matchingInfo = infoMap.get(nextOut.getID());
// an entry must exist, otherwise this is an error in the GUI implementation
// this type of error should be found in tests
matchingInfo.copyContentFrom(nextOut);
}
}
// perform action
StepAction action = next.getStep().getAction();
StepActionCallable actionCallable = new StepActionCallable(action, oldResults, next);
// use separate thread or tasks running outside the JVM context, like PCSC calls, won't stop on cancellation
ExecutorService execService = Executors.newSingleThreadExecutor();
Future<StepActionResult> actionFuture = execService.submit(actionCallable);
navigator.setRunningAction(actionFuture);
StepActionResult actionResult;
try {
actionResult = actionFuture.get();
} catch (CancellationException ex) {
logger.info("StepAction was canceled.", ex);
navigator.close();
return ResultStatus.CANCEL;
} catch (InterruptedException ex) {
logger.info("StepAction was interrupted.", ex);
navigator.close();
return ResultStatus.CANCEL;
} catch (ExecutionException ex) {
logger.error("StepAction failed with error.", ex.getCause());
navigator.close();
return ResultStatus.CANCEL;
}
// break out if cancel was returned
if (actionResult.getStatus() == StepActionResultStatus.CANCEL) {
logger.info("StepAction was canceled.");
navigator.close();
return ResultStatus.CANCEL;
}
// replace step if told by result value
if (actionResult.getReplacement() != null) {
switch (actionResult.getStatus()) {
case BACK:
next = navigator.replacePrevious(actionResult.getReplacement());
break;
case NEXT:
if (navigator.hasNext()) {
next = navigator.replaceNext(actionResult.getReplacement());
} else {
navigator.close();
return convertStatus(StepActionResultStatus.NEXT);
}
break;
case REPEAT:
next = navigator.replaceCurrent(actionResult.getReplacement());
break;
default:
// fallthrough because CANCEL is already handled
break;
}
} else {
// no replacement just proceed
switch (actionResult.getStatus()) {
case BACK:
next = navigator.previous();
break;
case NEXT:
if (navigator.hasNext()) {
next = navigator.next();
} else {
navigator.close();
return convertStatus(StepActionResultStatus.NEXT);
}
break;
case REPEAT:
next = navigator.current();
break;
default:
// fallthrough because CANCEL is already handled
break;
}
}
}
}
/**
* Get all step results of the execution.
*
* @return Mapping of the step results with step ID as key.
*/
public Map<String, ExecutionResults> getResults() {
return Collections.unmodifiableMap(results);
}
private ResultStatus convertStatus(StepActionResultStatus in) {
switch (in) {
case BACK:
return ResultStatus.BACK;
case NEXT:
return ResultStatus.OK;
default:
return ResultStatus.OK; // repeat undefined for this kind of status
}
}
}