/* license-start
*
* Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, at <http://www.gnu.org/licenses/>.
*
* Contributors:
* Crispico - Initial API and implementation
*
* license-end
*/
package org.flowerplatform.editor.model.remote;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.flowerplatform.communication.CommunicationPlugin;
import org.flowerplatform.communication.channel.CommunicationChannel;
import org.flowerplatform.communication.command.IServerCommand;
import org.flowerplatform.communication.service.InvokeServiceMethodServerCommand;
import org.flowerplatform.communication.service.ServiceInvocationContext;
import org.flowerplatform.communication.stateful_service.RemoteInvocation;
import org.flowerplatform.communication.stateful_service.StatefulServiceInvocationContext;
import org.flowerplatform.communication.tree.GenericTreeContext;
import org.flowerplatform.communication.tree.remote.PathFragment;
import org.flowerplatform.editor.model.ContentAssistItem;
import org.flowerplatform.editor.model.EditorModelPlugin;
import org.flowerplatform.editor.model.IContentAssist;
import org.flowerplatform.editor.model.change_processor.DiagramUpdaterChangeProcessorContext;
import org.flowerplatform.editor.model.change_processor.IDiagrammableElementFeatureChangesProcessor;
import org.flowerplatform.editor.model.remote.command.AbstractEMFServerCommand;
import org.flowerplatform.editor.model.remote.scenario.ScenarioTreeStatefulService;
import org.flowerplatform.editor.remote.EditableResource;
import org.flowerplatform.editor.remote.EditableResourceClient;
import org.flowerplatform.editor.remote.FileBasedEditorStatefulService;
import org.flowerplatform.emf_model.notation.Diagram;
import org.flowerplatform.emf_model.notation.NotationPackage;
import org.flowerplatform.emf_model.notation.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.crispico.flower.mp.model.codesync.CodeSyncElement;
import com.crispico.flower.mp.model.codesync.CodeSyncFactory;
import com.crispico.flower.mp.model.codesync.ScenarioElement;
/**
* @author Cristian Spiescu
* @author Mariana Gheorghe
*/
public class DiagramEditorStatefulService extends FileBasedEditorStatefulService {
public static final String ADDITIONAL_DATA_EDITABLE_RESOURCE = "editableResource";
public static final String ADDITIONAL_DATA_EDITABLE_RESOURCE_CLIENT = "editableResourceClient";
/**
* Added to context when the diagram is open.
*
* @see #sendFullContentToClient(EditableResource, EditableResourceClient)
* @author Mariana Gheorghe
*/
public static final String ADDITIONAL_DATA_INITIAL_FULL_CONTENT = "intialFullContent";
public static final String PROCESSING_CONTEXT_EDITABLE_RESOURCE = ADDITIONAL_DATA_EDITABLE_RESOURCE;
private static final Logger logger = LoggerFactory.getLogger(DiagramEditorStatefulService.class);
private ScenarioTreeStatefulService scenarioTree;
public DiagramEditorStatefulService() {
super();
CommunicationPlugin.getInstance().getCommunicationChannelManager().addWebCommunicationLifecycleListener(this);
scenarioTree = new ScenarioTreeStatefulService();
scenarioTree.setDiagramService(this);
}
@Override
protected boolean areLocalUpdatesAppliedImmediately() {
return false;
}
@Override
protected EditableResource createEditableResourceInstance() {
return new DiagramEditableResource();
}
public Diagram getDiagram(EditableResource editableResource) {
DiagramEditableResource er = (DiagramEditableResource) editableResource;
Diagram diagram = null;
for (EObject o : er.getMainResource().getContents()) {
if (o instanceof Diagram) {
diagram = (Diagram) o;
}
}
if (diagram == null) {
throw new RuntimeException("Cannot find a/the diagram in the resource");
}
return diagram;
}
@Override
protected void loadEditableResource(StatefulServiceInvocationContext context, EditableResource editableResource) {
super.loadEditableResource(context, editableResource);
DiagramEditableResource er = (DiagramEditableResource) editableResource;
URI resourceURI = EditorModelPlugin.getInstance().getModelAccessController().getURIFromFile(er.getFile());
er.setChangeRecorder(new ChangeRecorder());
try {
er.setMainResource(er.getResourceSet().getResource(resourceURI, true));
er.getResourceSet().eAdapters().add(new ECrossReferenceAdapter());
} catch (Exception e) {
throw new RuntimeException("Error while loading file content " + er.getFile(), e);
}
}
@Override
protected void disposeEditableResource(EditableResource editableResource) {
// DiagramCodeSync3EditableResource diagramEditableResource = (DiagramCodeSync3EditableResource) editableResource;
// diagramEditableResource.transferAdapter.dispose();
// TODO CS/CS3 implementat codul de dispose
// cred ca tr. sa fie ceva de genul de mai jos, insa poate pentru toate resursele?
// mainResource.eAdapters().clear();
// mainResource.unload();
//
// getResourceSet().getResources().remove(mainResource);
// ((ComposedAdapterFactory) adapterFactory).dispose();
}
@Override
protected void doSave(EditableResource editableResource) {
Map<Object, Object> options = EditorModelPlugin.getInstance().getLoadSaveOptions();
for (Resource resource : ((DiagramEditableResource) editableResource).getResourceSet().getResources()) {
try {
resource.save(options);
((DiagramEditableResource) editableResource).setDirty(false);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
protected Map<String, Object> createProcessingContext(EditableResource editableResource) {
Map<String, Object> processingContext = new HashMap<String, Object>();
processingContext.put(PROCESSING_CONTEXT_EDITABLE_RESOURCE, editableResource);
return processingContext;
}
@Override
protected void sendFullContentToClient(EditableResource editableResource, EditableResourceClient client) {
// Resource hbResource = new FlowerTeneoResource(URI.createURI("hb:/?dsname=flowerDataStore&query=from Diagram"));
// try {
// hbResource.load(Collections.EMPTY_MAP);
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// ((DiagramEditableResource) editableResource).setMainResource(hbResource);
Diagram diagram = getDiagram(editableResource);
List<EObject> list = new ArrayList<EObject>();
Map<String, Object> processingContext = createProcessingContext(editableResource);
list.add(diagram);
processingContext.put(ADDITIONAL_DATA_INITIAL_FULL_CONTENT, true);
iterateContents(diagram, list, processingContext);
DiagramUpdaterChangeProcessorContext diagramUpdaterChangeDescriptionProcessingContext = DiagramUpdaterChangeProcessorContext.getDiagramUpdaterChangeDescriptionProcessingContext(processingContext, false);
client_updateTransferableObjects(client.getCommunicationChannel(), client.getStatefulClientId(), list, Collections.emptyList(), null, diagramUpdaterChangeDescriptionProcessingContext != null ? diagramUpdaterChangeDescriptionProcessingContext.getViewDetailsUpdates() : null);
String diagramId = diagram.eResource().getURIFragment(diagram);
invokeClientMethod(client.getCommunicationChannel(), client.getStatefulClientId(), "openDiagram", new Object[] { diagramId });
// hbResource.unload();
}
/**
* @author Cristian Spiescu
* @author Cristina Constantinescu
*/
private void iterateContents(View view, List<EObject> list, Map<String, Object> processingContext) {
// feature changes are processed here because we want this to be done for the diagram also
List<IDiagrammableElementFeatureChangesProcessor> processors = EditorModelPlugin.getInstance().getDiagramUpdaterChangeProcessor().getDiagrammableElementFeatureChangesProcessors(view.getViewType());
if (processors != null) {
for (IDiagrammableElementFeatureChangesProcessor processor : processors) {
processor.processFeatureChanges(view.getDiagrammableElement(), null, view, processingContext);
}
}
Iterator<EObject> iter = view.eContents().iterator();
while (iter.hasNext()) {
EObject next = iter.next();
if (next instanceof View) {
View child = (View) next;
iterateContents(child, list, processingContext);
}
list.add(next);
}
}
// TODO CS/CS3 de facut semnatura sa accepte ERC; e absurd, ca eu il calculez prin iteratie din nou aici; si semnatura nu e uniforma cu celelalte
@Override
protected void updateEditableResourceContentAndDispatchUpdates(StatefulServiceInvocationContext statefulServiceInvocationContext, EditableResource editableResource, Object updatesToApply) {
// if (!(updatesToApply instanceof AbstractEMFServerCommand)) {
// logger.error("Content update cannot be casted to a command: {}", updatesToApply);
// }
// FlowerTeneoResource hbResource = new FlowerTeneoResource(URI.createURI("hb:/?dsname=flowerDataStore&query=from Diagram"));
// hbResource.load(Collections.EMPTY_MAP);
// ((DiagramEditableResource) editableResource).setMainResource(hbResource);
// ((DiagramEditableResource) editableResource).setChangeRecorder(hbResource.getChangeRecorder());
DiagramEditableResource diagramEditableResource = (DiagramEditableResource) editableResource;
diagramEditableResource.getChangeRecorder().beginRecording(Collections.singleton(diagramEditableResource.getMainResource().getResourceSet()));
diagramEditableResource.setDirty(true);
try {
EditableResourceClient client = editableResource.getEditableResourceClientByCommunicationChannel(statefulServiceInvocationContext.getCommunicationChannel());
if (updatesToApply instanceof AbstractEMFServerCommand) {
AbstractEMFServerCommand command = (AbstractEMFServerCommand) updatesToApply;
command.setEditorStatefulService(this);
command.setEditableResourceClient(client);
command.setEditableResource(diagramEditableResource);
command.executeCommand();
} else if (updatesToApply instanceof IServerCommand) {
IServerCommand command = (IServerCommand) updatesToApply;
if (command instanceof InvokeServiceMethodServerCommand) {
Map<String, Object> additionalData = new HashMap<String, Object>();
additionalData.put(ADDITIONAL_DATA_EDITABLE_RESOURCE_CLIENT, client);
additionalData.put(ADDITIONAL_DATA_EDITABLE_RESOURCE, editableResource);
((InvokeServiceMethodServerCommand) command).setAdditionalDataForServiceInvocationContext(additionalData);
}
command.setCommunicationChannel(statefulServiceInvocationContext.getCommunicationChannel());
command.executeCommand();
}
// diagramEditableResource.transferAdapter.prepareForTrasfer();
// TODO CS/CS3 HIGH nu trimitem lista de obiecte pt dispose inca; trebuie si un try/finally: clear
// client_updateTransferableObjects(originatingCommunicationChannel, originatingStatefulClientId, diagramEditableResource.transferAdapter.getObjectsToUpdateQueue(), null);
// diagramEditableResource.transferAdapter.clearQueue();
} finally {
ChangeDescription changeDescription = diagramEditableResource.getChangeRecorder().endRecording();
Map<String, Object> processingContext = createProcessingContext(diagramEditableResource);
diagramEditableResource.getChangeRecorder().beginRecording(Collections.singleton(diagramEditableResource.getMainResource().getResourceSet()));
// TODO CS/CS3 dubla inregistrare: sa facem prin constructie mai intai procesare elemente si apoi view-uri?...
// oricum, cred ca ar trebui inca un try/finally; sa facem un mecanism multiplu, ca la MDA? dar cum ne dam seama ca nu e infinit?
// sa nu mai facem viewdetails ca camp, si sa facem comanda speciala?
// de asemenea, tr. si un map, pentru a evita adaugarea de 2 ori in timpul celor 2 inregistrari
EditorModelPlugin.getInstance().getMainChangesDispatcher().processChangeDescription(processingContext, changeDescription);
EditorModelPlugin.getInstance().getComposedChangeProcessor().processChangeDescription(changeDescription, processingContext);
changeDescription = diagramEditableResource.getChangeRecorder().endRecording();
diagramEditableResource.getChangeRecorder().beginRecording(Collections.singleton(diagramEditableResource.getMainResource().getResourceSet()));
EditorModelPlugin.getInstance().getComposedChangeProcessor().processChangeDescription(changeDescription, processingContext);
changeDescription = diagramEditableResource.getChangeRecorder().endRecording();
diagramEditableResource.getChangeRecorder().dispose();
// MyChangeRecorder.threadLocalChangeRecorder.set(null);
// diagramEditableResource.changeRecorder = null;
EditorModelPlugin.getInstance().getComposedChangeProcessor().processChangeDescription(changeDescription, processingContext);
DiagramUpdaterChangeProcessorContext diagramUpdaterChangeDescriptionProcessingContext = DiagramUpdaterChangeProcessorContext.getDiagramUpdaterChangeDescriptionProcessingContext(processingContext, false);
if (diagramUpdaterChangeDescriptionProcessingContext != null && !diagramUpdaterChangeDescriptionProcessingContext.isEmpty()) {
for (EditableResourceClient client : diagramEditableResource.getClients()) {
client_updateTransferableObjects(client.getCommunicationChannel(), client.getStatefulClientId(),
diagramUpdaterChangeDescriptionProcessingContext.getObjectsToUpdate(),
diagramUpdaterChangeDescriptionProcessingContext.getObjectsToDispose(),
diagramUpdaterChangeDescriptionProcessingContext.getObjectIdsToDispose(),
diagramUpdaterChangeDescriptionProcessingContext.getViewDetailsUpdates());
}
}
}
// hbResource.save(Collections.EMPTY_MAP);
// hbResource.unload();
}
public ScenarioTreeStatefulService getScenarioTreeStatefulService() {
return scenarioTree;
}
private DiagramEditableResource getDiagramEditableResource(ServiceInvocationContext context) {
return (DiagramEditableResource) context.getAdditionalData().get(ADDITIONAL_DATA_EDITABLE_RESOURCE);
}
///////////////////////////////////////////////////////////////
// Proxies to client methods
///////////////////////////////////////////////////////////////
/**
* Before sending the objects to the client, first iterate the <code>objectsToUpdate</code> list and
* clean references towards model elements. This should be done when the objects are removed from the
* resource; however, we do not want to treat this as a change and process it.
*
* @author Mariana Gheorghe
*/
public void client_updateTransferableObjects(CommunicationChannel communicationChannel, String statefulClientId, Collection<?> objectsToUpdate, Collection<?> objectsToDispose, Collection<?> objectsIdsToDispose, Collection<ViewDetailsUpdate> viewDetailsUpdates) {
if (viewDetailsUpdates != null) {
CopyOnWriteArrayList<ViewDetailsUpdate> updates = new CopyOnWriteArrayList<ViewDetailsUpdate>(viewDetailsUpdates);
if (objectsIdsToDispose != null && objectsIdsToDispose.size() > 0) {
for (ViewDetailsUpdate update : updates) {
if (objectsIdsToDispose.contains(update.getViewId())) {
viewDetailsUpdates.remove(update);
}
}
}
}
for (Object object : objectsToDispose) {
if (object instanceof View) {
((View) object).setDiagrammableElement(null);
}
objectsToUpdate.remove(object);
}
invokeClientMethod(communicationChannel, statefulClientId, "updateTransferableObjects", new Object[] { objectsToUpdate, objectsIdsToDispose, viewDetailsUpdates });
}
///////////////////////////////////////////////////////////////
// @RemoteInvocation methods
///////////////////////////////////////////////////////////////
@RemoteInvocation
public void handleDragOnDiagram(StatefulServiceInvocationContext context, List<String> paths, String diagramId) {
DiagramEditableResource editableResource = getDiagramEditableResource(context);
Diagram diagram = (Diagram) editableResource.getEObjectById(diagramId);
EditorModelPlugin.getInstance().getComposedDragOnDiagramHandler().handleDragOnDiagram(context, paths, diagram, null, null, context.getCommunicationChannel());
}
/**
* @author Mariana Gheorghe
*/
@RemoteInvocation
public List<ContentAssistItem> contentAssist(StatefulServiceInvocationContext context, String viewId, String pattern) {
logger.debug("Search types for pattern [{}]", pattern);
DiagramEditableResource editableResource = getDiagramEditableResource(context);
View view = (View) editableResource.getEObjectById(viewId);
CodeSyncElement diagrammableElement = (CodeSyncElement) view.getDiagrammableElement();
if (diagrammableElement == null) {
throw new RuntimeException("No diagrammable element for view with id " + viewId);
}
// populate the search context, needed to set the search scope
Map<String, Object> searchContext = new HashMap<String, Object>();
searchContext.put(IContentAssist.TYPE, diagrammableElement.getType());
searchContext.put(IContentAssist.RESOURCE, editableResource.getFile());
List<ContentAssistItem> types = EditorModelPlugin.getInstance()
.getComposedContentAssist().findMatches(searchContext, pattern);
if (types == null) {
logger.debug("No types found for pattern [{}]", pattern);
} else {
logger.debug("Found [{}] types for pattern [{}]", types.size(), pattern);
}
return types;
}
/**
* @author Mariana Gheorghe
*/
@RemoteInvocation
public void addNewScenario(StatefulServiceInvocationContext context, String editableResourcePath, String name) {
DiagramEditableResource er = (DiagramEditableResource) getEditableResource(editableResourcePath);
Resource resource = scenarioTree.getScenariosResource(er);
ScenarioElement scenario = CodeSyncFactory.eINSTANCE.createScenarioElement();
scenario.setName(name);
scenario.setType("scenarioRoot");
resource.getContents().add(scenario);
Map<Object, Object> clientContext = new HashMap<Object, Object>();
clientContext.put("diagramEditableResourcePath", editableResourcePath);
clientContext.put("selectNode", true);
openNode(context, null, clientContext);
}
protected ScenarioElement createScenarioElement(CodeSyncElement cse, ScenarioElement parent) {
ScenarioElement interaction = CodeSyncFactory.eINSTANCE.createScenarioElement();
interaction.setInteraction(cse);
interaction.setName(cse.getName());
interaction.setType("scenarioElement");
parent.getChildren().add(interaction);
String number = getNumberLabel(interaction);
if (number.startsWith(".")) {
number = number.substring(1);
}
interaction.setNumber(number);
return interaction;
}
protected String getNumberLabel(ScenarioElement scenario) {
ScenarioElement parent = ((ScenarioElement) scenario.eContainer());
if (parent.getType().equals("scenarioRoot")) {
return "";
}
int i = 0;
for (CodeSyncElement child : parent.getChildren()) {
if (child.getType() != null && child.getType().equals("scenarioElement")) {
i++;
}
if (child.equals(scenario)) {
break;
}
}
return getNumberLabel(parent) + "." + i;
}
protected ScenarioElement addScenarioInteraction(ScenarioElement scenario, CodeSyncElement source, CodeSyncElement target) {
if (source.equals(scenario.getInteraction())) {
ScenarioElement interaction = createScenarioElement(target, scenario);
return interaction;
}
for (CodeSyncElement child : scenario.getChildren()) {
ScenarioElement interaction = addScenarioInteraction((ScenarioElement) child, source, target);
if (interaction != null) {
return interaction;
}
}
return null;
}
protected void updateLabelsAfterIndex(ScenarioElement scenario, int index) {
for (int i = index; i < scenario.getChildren().size(); i++) {
ScenarioElement child = (ScenarioElement) scenario.getChildren().get(i);
if (child.getType() != null && child.getType().equals("scenarioElement")) {
StringBuilder label = new StringBuilder(child.getNumber());
int lastIndex = label.lastIndexOf(".");
if (lastIndex < 0) {
lastIndex = 0;
} else {
lastIndex++;
}
int newIndex = Integer.parseInt(label.substring(lastIndex)) - 1;
label.replace(lastIndex, label.length(), String.valueOf(newIndex));
child.setNumber(label.toString());
}
}
}
/**
* @author Mariana Gheorghe
*/
@RemoteInvocation
public void openNode(StatefulServiceInvocationContext context, List<PathFragment> path, Map<Object, Object> clientContext) {
// scenarioTree.openNode(context, path, clientContext);
}
/**
* @author Mariana Gheorghe
*/
@RemoteInvocation
public void addNewComment(StatefulServiceInvocationContext context, List<PathFragment> path, String editableResourcePath, String comment, Map<Object, Object> clientContext) {
clientContext.put("diagramEditableResourcePath", editableResourcePath);
GenericTreeContext treeContext = new GenericTreeContext(scenarioTree);
treeContext.setClientContext(clientContext);
ScenarioElement parent = (ScenarioElement) scenarioTree.getNodeByPath(path, treeContext);
ScenarioElement commentNode = CodeSyncFactory.eINSTANCE.createScenarioElement();
commentNode.setComment(comment);
commentNode.setName(comment);
commentNode.setType("commentNode");
parent.getChildren().add(commentNode);
scenarioTree.openNode(context, path, clientContext);
}
/**
* @author Mariana Gheorghe
*/
@RemoteInvocation
public void deleteScenarioElement(StatefulServiceInvocationContext context, List<PathFragment> path, String editableResourcePath, Map<Object, Object> clientContext) {
clientContext.put("diagramEditableResourcePath", editableResourcePath);
GenericTreeContext treeContext = new GenericTreeContext(scenarioTree);
treeContext.setClientContext(clientContext);
ScenarioElement scenarioElement = (ScenarioElement) scenarioTree.getNodeByPath(path, treeContext);
ECrossReferenceAdapter adapter = ECrossReferenceAdapter.getCrossReferenceAdapter(scenarioElement);
Diagram diagram = getDiagram(getEditableResource(editableResourcePath));
for (Setting setting : adapter.getNonNavigableInverseReferences(scenarioElement)) {
if (NotationPackage.eINSTANCE.getView_DiagrammableElement().equals(setting.getEStructuralFeature())) {
View view = (View) setting.getEObject();
diagram.getPersistentEdges().remove(view);
}
}
ScenarioElement parent = (ScenarioElement) scenarioElement.eContainer();
int index = parent.getChildren().indexOf(scenarioElement);
parent.getChildren().remove(scenarioElement);
updateLabelsAfterIndex(parent, index);
}
}