blob: 1b94adb596612a1898a78eeb25d9d53e421c15ac [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2019 Xored Software Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Xored Software Inc - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.rcptt.tesla.internal.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.rcptt.tesla.core.Q7WaitUtils;
import org.eclipse.rcptt.tesla.core.context.ContextManagement.Context;
import org.eclipse.rcptt.tesla.core.info.AdvancedInformation;
import org.eclipse.rcptt.tesla.core.info.InfoFactory;
import org.eclipse.rcptt.tesla.core.info.Q7WaitInfoRoot;
import org.eclipse.rcptt.tesla.core.protocol.ElementCommand;
import org.eclipse.rcptt.tesla.core.protocol.GetStateResponse;
import org.eclipse.rcptt.tesla.core.protocol.IElementProcessorMapper;
import org.eclipse.rcptt.tesla.core.protocol.Nop;
import org.eclipse.rcptt.tesla.core.protocol.ProtocolFactory;
import org.eclipse.rcptt.tesla.core.protocol.ProtocolPackage;
import org.eclipse.rcptt.tesla.core.protocol.SelectCommand;
import org.eclipse.rcptt.tesla.core.protocol.SelectResponse;
import org.eclipse.rcptt.tesla.core.protocol.TeslaStream;
import org.eclipse.rcptt.tesla.core.protocol.raw.Command;
import org.eclipse.rcptt.tesla.core.protocol.raw.Element;
import org.eclipse.rcptt.tesla.core.protocol.raw.RawFactory;
import org.eclipse.rcptt.tesla.core.protocol.raw.Response;
import org.eclipse.rcptt.tesla.core.protocol.raw.ResponseStatus;
import org.eclipse.rcptt.tesla.internal.core.info.GeneralInformationCollector;
import org.eclipse.rcptt.tesla.internal.core.processing.ElementGenerator;
import org.eclipse.rcptt.tesla.internal.core.processing.ITeslaCommandProcessor;
import org.eclipse.rcptt.tesla.internal.core.processing.ITeslaCommandProcessor.PreExecuteStatus;
import org.eclipse.rcptt.util.Exceptions;
public abstract class AbstractTeslaClient implements IElementProcessorMapper {
private boolean processing = false;
private final ElementGenerator generator = new ElementGenerator();
protected final List<Command> localQueue = new ArrayList<Command>();
private Map<String, Set<ITeslaCommandProcessor>> elementProcessors = new HashMap<String, Set<ITeslaCommandProcessor>>();
private final Map<Command, ITeslaCommandProcessor.PreExecuteStatus> preStatuses = new HashMap<Command, ITeslaCommandProcessor.PreExecuteStatus>();
private final TeslaProcessorManager processors = new TeslaProcessorManager();
private Context currentContext;
private boolean closed = false;
private final String id;
private final AtomicInteger hasEvents = new AtomicInteger(-1);
private AdvancedInformation lastFailureInformation;
public AbstractTeslaClient(String id) {
super();
this.id = id;
processors.initializeProcessors(this, id);
}
public String getID() {
return id;
}
synchronized public void shutdown() {
processors.terminate();
elementProcessors.clear();
closed = true;
}
protected abstract TeslaStream stream();
@Override
synchronized public void map(Element element, ITeslaCommandProcessor processor) {
if (element != null && processor != null) {
putProcessor(element, processor);
processors.postSelect(element, new IElementProcessorMapper() {
@Override
public void map(Element element, ITeslaCommandProcessor processor) {
putProcessor(element, processor);
}
});
}
}
synchronized private void putProcessor(Element element, ITeslaCommandProcessor processor) {
final String key = makeKey(element);
Set<ITeslaCommandProcessor> set = this.elementProcessors.get(key);
if (set == null) {
set = new LinkedHashSet<ITeslaCommandProcessor>();
}
Set<ITeslaCommandProcessor> newSet = new LinkedHashSet<ITeslaCommandProcessor>();
newSet.add(processor);
newSet.addAll(set);
// II: So that processors are iterated in
// reverse order, i.e. processor which has
// been put last, is iterated at first
this.elementProcessors.put(key, newSet);
}
private SelectResponse handleSelect(SelectCommand cmd) {
// Check for extensions first
String kind = cmd.getData().getKind();
SelectResponse response = processors.select(cmd, generator, this);
if (response == null) {
response = ProtocolFactory.eINSTANCE.createSelectResponse();
response.setStatus(ResponseStatus.FAILED);
response.setMessage("No Element with kind:" + kind + " is available for selection...");
}
if (response.getStatus().equals(ResponseStatus.FAILED)) {
handleFailedResponse(cmd, response);
}
return response;
}
private String makeKey(Element uiElement) {
return uiElement.getKind() + ":" + uiElement.getId();
}
synchronized public boolean processNext(Context context, Q7WaitInfoRoot info) {
// Skip this event if we are already processing another one.
if (!closed && !processing && (hasCommand() || !localQueue.isEmpty()) && canProceed(context, info)) {
return doOneCommand(context, info);
}
return false;
}
public void hasEvent(String kind, String name, Q7WaitInfoRoot info) {
hasEvents.incrementAndGet();
Q7WaitUtils.startInfo(kind, name, info);
}
protected boolean canProceed(Context context, Q7WaitInfoRoot info) {
hasEvents.set(0);
boolean result = processors.canProceed(context, info);
if (hasEvents.get() > 0) {
return false;
}
return result;
}
public void clean() {
lastFailureInformation = null;
processors.clean();
for (PreExecuteStatus status : preStatuses.values()) {
status.clean();
}
preStatuses.clear();
elementProcessors.clear();
}
private boolean preExecute(Command command, Q7WaitInfoRoot info) {
if (command instanceof ElementCommand) {
ElementCommand cmd = (ElementCommand) command;
Element element = cmd.getElement();
if (element != null) {
Set<ITeslaCommandProcessor> processors = elementProcessors.get(makeKey(element));
if (processors != null) {
PreExecuteStatus preStatus = preStatuses.get(command);
for (ITeslaCommandProcessor processor : processors) {
if (processor != null) {
try {
PreExecuteStatus status = processor.preExecute(command, preStatus, info);
if (status != null) {
preStatuses.put(command, status);
if (preStatus != status) {
cleanPreStatus(preStatus);
}
if (!status.canExecute) {
return false;
}
} else {
preStatuses.remove(command);
cleanPreStatus(preStatus);
}
} catch (Throwable e) {
TeslaCore.log(e);
}
}
}
}
}
} else {
PreExecuteStatus status = processors.preExecute(command, preStatuses.get(command), info);
if (status != null && !status.canExecute) {
preStatuses.put(command, status);
return false;
}
}
return true;
}
private void cleanPreStatus(PreExecuteStatus preStatus) {
if (preStatus != null) {
try {
// We need to call clean to be sure status is finalized all
// things.
preStatus.clean();
} catch (Throwable ee) {
TeslaCore.log(ee);
}
}
}
synchronized public void addCommand(Command cmd) {
localQueue.add(cmd);
notifyUI();
}
public void notifyUI() {
processors.notifyUI();
}
private boolean doOneCommand(Context context, Q7WaitInfoRoot info) {
synchronized (this) {
processing = true;
}
this.currentContext = context;
Command command = null;
try {
final boolean isLocalQueue;
if (!localQueue.isEmpty()) {
command = localQueue.remove(0);
isLocalQueue = true;
} else {
command = stream().readCommand();
if (command == null) {
return false;
}
isLocalQueue = false;
}
try {
if (!preExecute(command, info)) {
if (isLocalQueue) {
localQueue.add(0, command);
} else {
localQueue.add(command);
}
return false;
}
stream().writeResponse(execute(command));
} catch (Throwable e) {
logException(e);
stream().writeResponse(createErrorResponse(e));
}
} finally {
synchronized (this) {
processing = false;
}
currentContext = null;
}
return true;
}
private Response execute(final Command command) {
// long startTime = System.currentTimeMillis();
if (!(command instanceof Nop)) {
lastFailureInformation = null;
}
if (command instanceof ElementCommand) {
ElementCommand cmd = (ElementCommand) command;
Element element = cmd.getElement();
if (element != null) {
Set<ITeslaCommandProcessor> processors = elementProcessors.get(makeKey(element));
if (processors != null) {
for (ITeslaCommandProcessor processor : processors) {
if (processor != null) {
Response response = processor.executeCommand(command, this);
if (response != null) {
if (response.getStatus().equals(ResponseStatus.FAILED)) {
handleFailedResponse(command, response);
}
return response;
}
}
}
}
}
} else {
Response response = processors.executeCommand(command, this, true);
if (response != null) {
if (response.getStatus().equals(ResponseStatus.FAILED)) {
handleFailedResponse(command, response);
}
return response;
}
}
if (command.eClass().getEPackage().equals(ProtocolFactory.eINSTANCE.getEPackage())) {
switch (command.eClass().getClassifierID()) {
case ProtocolPackage.SELECT_COMMAND:
return handleSelect((SelectCommand) command);
case ProtocolPackage.SHUTDOWN:
shutdown();
return RawFactory.eINSTANCE.createResponse();
case ProtocolPackage.NOP:
return RawFactory.eINSTANCE.createResponse();
case ProtocolPackage.GET_STATE:
GetStateResponse res = ProtocolFactory.eINSTANCE.createGetStateResponse();
Element state = RawFactory.eINSTANCE.createElement();
String returnedState = currentContext.getID();
// System.out.println("RETURNED WAIT STATE:" +
// returnedState);
state.setId(returnedState);
state.setKind("State");
res.setState(state);
return res;
case ProtocolPackage.WAIT_FOR_STATE:
return RawFactory.eINSTANCE.createResponse();
case ProtocolPackage.ROLLBACK_TO_STATE:
// TODO
return RawFactory.eINSTANCE.createResponse();
}
}
Response failed = RawFactory.eINSTANCE.createResponse();
failed.setStatus(ResponseStatus.FAILED);
failed.setMessage("Unsupported command");
return failed;
}
private Response createErrorResponse(Throwable e) {
Response errorResponse = RawFactory.eINSTANCE.createResponse();
errorResponse.setStatus(ResponseStatus.FAILED);
errorResponse.setMessage("Exception happened:\n" + Exceptions.toString(e)); // class
// name
// and
// exception
// message
return errorResponse;
}
synchronized public AdvancedInformation getLastFailureInformation() {
return lastFailureInformation;
}
synchronized public void clearLastFailureInformation() {
lastFailureInformation = null;
}
private void handleFailedResponse(Command command, Response response) {
if (response != null) {
// Collect advanced information for this error
AdvancedInformation information = getAdvancedInformation(command);
this.lastFailureInformation = information;
response.setAdvancedInformation(EcoreUtil.copy(information));
}
}
synchronized public AdvancedInformation getAdvancedInformation(Command command) {
// Collect advanced information
AdvancedInformation information = InfoFactory.eINSTANCE.createAdvancedInformation();
processors.collectInformation(information, command);
GeneralInformationCollector.collectInformation(information);
return information;
}
synchronized public void collectLastFailureInformation() {
AdvancedInformation information = getAdvancedInformation(null);
this.lastFailureInformation = information;
}
public abstract void logException(Throwable t);
public abstract boolean hasCommand();
public <T> T getProcessor(Class<T> clazz$) {
return processors.getProcessor(clazz$);
}
public <T> List<T> getProcessors(Class<T> clazz$) {
return processors.getProcessors(clazz$);
}
}