Skip to content

Commit

Permalink
Refactorings regarding source model loading and saving
Browse files Browse the repository at this point in the history
With this change, I'd like to consistently use the term source model and
avoid the term model source. This is to avoid the overlap with Sprotty's
concept of ModelSource and its unclear relation to a source model and
GModel.

I suggest to avoid Model Source altogether and instead introduce and
consistently use "source model" to refer to the underlying model that we
read and write. Thus we have in GLSP two types of models:
  * Source Model
  * Graph Model (GModel)

Thus, I did the following refactorings:
  * Rename ModelSourceLoader to SourceModelPersistence
  * Add saving to the SourceModelPersistence interface
  * Delegate from the SaveModelActionHandler to that implementation
  * Rename ModelSourceWatcher into SourceModelWatcher
  * Update javadoc and variable names where fit
  • Loading branch information
planger committed Mar 25, 2022
1 parent 7e41611 commit 2e4cbf8
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,75 +15,37 @@
********************************************************************************/
package org.eclipse.glsp.server.actions;

import static org.eclipse.glsp.server.types.GLSPServerException.getOrThrow;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

import org.apache.log4j.Logger;
import org.eclipse.glsp.graph.GGraph;
import org.eclipse.glsp.server.features.modelsourcewatcher.ModelSourceWatcher;
import org.eclipse.glsp.server.gson.GraphGsonConfigurationFactory;
import org.eclipse.glsp.server.features.core.model.SourceModelStorage;
import org.eclipse.glsp.server.features.modelsourcewatcher.SourceModelWatcher;
import org.eclipse.glsp.server.model.GModelState;
import org.eclipse.glsp.server.types.GLSPServerException;
import org.eclipse.glsp.server.utils.ClientOptionsUtil;

import com.google.gson.Gson;
import com.google.inject.Inject;

public class SaveModelActionHandler extends AbstractActionHandler<SaveModelAction> {
private static final Logger LOG = Logger.getLogger(SaveModelActionHandler.class);

@Inject
protected GraphGsonConfigurationFactory gsonConfigurator;
protected Optional<SourceModelWatcher> modelSourceWatcher;

@Inject
protected Optional<ModelSourceWatcher> modelSourceWatcher;
protected GModelState modelState;

@Inject
protected GModelState modelState;
protected SourceModelStorage sourceModelStorage;

@Override
public List<Action> executeAction(final SaveModelAction action) {
modelSourceWatcher.ifPresent(watcher -> watcher.pauseWatching());
try {
saveModelState(action);
if (sourceModelStorage.saveSourceModel(action)) {
modelState.saveIsDone();
}
} finally {
modelSourceWatcher.ifPresent(watcher -> watcher.continueWatching());
}
return listOf(new SetDirtyStateAction(modelState.isDirty(), SetDirtyStateAction.Reason.SAVE));
}

protected void saveModelState(final SaveModelAction action) {
File file = convertToFile(action);
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
Gson gson = gsonConfigurator.configureGson().setPrettyPrinting().create();
gson.toJson(modelState.getRoot(), GGraph.class, writer);
if (saveIsDone(action)) {
modelState.saveIsDone();
}
} catch (IOException e) {
LOG.error(e);
throw new GLSPServerException("An error occured during save process.", e);
}
}

protected boolean saveIsDone(final SaveModelAction action) {
String sourceUri = ClientOptionsUtil.adaptUri(modelState.getClientOptions().get(ClientOptionsUtil.SOURCE_URI));
return action.getFileUri().map(uri -> ClientOptionsUtil.adaptUri(uri).equals(sourceUri)).orElse(true);
}

protected File convertToFile(final SaveModelAction action) {
if (action.getFileUri().isPresent()) {
return ClientOptionsUtil.getAsFile(action.getFileUri().get());
}
return getOrThrow(ClientOptionsUtil.getSourceUriAsFile(modelState.getClientOptions()),
"Invalid file URI:" + ClientOptionsUtil.getSourceUri(modelState.getClientOptions()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
import org.eclipse.glsp.server.features.contextactions.SetContextActions;
import org.eclipse.glsp.server.features.contextmenu.ContextMenuItemProvider;
import org.eclipse.glsp.server.features.core.model.GModelFactory;
import org.eclipse.glsp.server.features.core.model.ModelSourceLoader;
import org.eclipse.glsp.server.features.core.model.SourceModelStorage;
import org.eclipse.glsp.server.features.core.model.RequestBoundsAction;
import org.eclipse.glsp.server.features.core.model.RequestModelActionHandler;
import org.eclipse.glsp.server.features.core.model.SetBoundsAction;
Expand All @@ -62,7 +62,7 @@
import org.eclipse.glsp.server.features.directediting.RequestEditValidationHandler;
import org.eclipse.glsp.server.features.directediting.SetEditValidationResultAction;
import org.eclipse.glsp.server.features.modelsourcewatcher.ModelSourceChangedAction;
import org.eclipse.glsp.server.features.modelsourcewatcher.ModelSourceWatcher;
import org.eclipse.glsp.server.features.modelsourcewatcher.SourceModelWatcher;
import org.eclipse.glsp.server.features.navigation.NavigateToExternalTargetAction;
import org.eclipse.glsp.server.features.navigation.NavigateToTargetAction;
import org.eclipse.glsp.server.features.navigation.NavigationTargetProvider;
Expand Down Expand Up @@ -121,9 +121,9 @@
* <li>{@link DiagramConfiguration}
* <li>{@link ServerConfigurationContribution}
* <li>{@link GModelState}
* <li>{@link ModelSourceLoader}
* <li>{@link SourceModelStorage}
* <li>{@link GModelFactory}
* <li>{@link ModelSourceWatcher} as {@link Optional}
* <li>{@link SourceModelWatcher} as {@link Optional}
* <li>{@link GraphGsonConfigurationFactory}
* <li>{@link ModelValidator} as {@link Optional}
* <li>{@link LabelEditValidator} as {@link Optional}
Expand Down Expand Up @@ -167,9 +167,9 @@ protected void configureBase() {
bind(ServerConfigurationContribution.class).to(bindServerConfigurationContribution()).in(Singleton.class);
// Model-related bindings
configureGModelState(bindGModelState());
bind(ModelSourceLoader.class).to(bindSourceModelLoader());
bind(SourceModelStorage.class).to(bindSourceModelStorage());
bind(GModelFactory.class).to(bindGModelFactory());
bindOptionally(ModelSourceWatcher.class, bindModelSourceWatcher())
bindOptionally(SourceModelWatcher.class, bindSourceModelWatcher())
.ifPresent(binder -> binder.in(Singleton.class));
bind(GraphGsonConfigurationFactory.class).to(bindGraphGsonConfiguratorFactory()).in(Singleton.class);

Expand Down Expand Up @@ -234,11 +234,11 @@ protected Class<? extends GModelState> bindGModelState() {
return DefaultGModelState.class;
}

protected abstract Class<? extends ModelSourceLoader> bindSourceModelLoader();
protected abstract Class<? extends SourceModelStorage> bindSourceModelStorage();

protected abstract Class<? extends GModelFactory> bindGModelFactory();

protected Class<? extends ModelSourceWatcher> bindModelSourceWatcher() {
protected Class<? extends SourceModelWatcher> bindSourceModelWatcher() {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import org.eclipse.glsp.server.features.clipboard.RequestClipboardDataActionHandler;
import org.eclipse.glsp.server.features.core.model.ComputedBoundsActionHandler;
import org.eclipse.glsp.server.features.core.model.GModelFactory;
import org.eclipse.glsp.server.features.core.model.JsonFileGModelLoader;
import org.eclipse.glsp.server.features.core.model.ModelSourceLoader;
import org.eclipse.glsp.server.features.core.model.JsonFileGModelStore;
import org.eclipse.glsp.server.features.core.model.SourceModelStorage;
import org.eclipse.glsp.server.features.directediting.ApplyLabelEditOperationHandler;
import org.eclipse.glsp.server.features.toolpalette.ToolPaletteItemProvider;
import org.eclipse.glsp.server.features.undoredo.UndoRedoActionHandler;
Expand All @@ -40,8 +40,8 @@
public abstract class GModelJsonDiagramModule extends DiagramModule {

@Override
protected Class<? extends ModelSourceLoader> bindSourceModelLoader() {
return JsonFileGModelLoader.class;
protected Class<? extends SourceModelStorage> bindSourceModelStorage() {
return JsonFileGModelStore.class;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* <p>
* The responsibility of a {@link GModelFactory} implementation is to define how a {@link GModelState} is to be
* translated into a {@link GModelRoot} that is sent to the client for rendering. Before a {@link GModelFactory}
* is invoked, the {@link ModelSourceLoader} has already been executed for loading the source model into the
* is invoked, the {@link SourceModelStorage} has already been executed for loading the source model into the
* {@link GModelState}. The {@link GModelFactory} then produces the {@link GModelRoot} from the source model in the
* {@link GModelState}. Implementations of {@link GModelFactory} are usually specific to the type of source model, as
* they need to understand the source model in order to translate it into a graph model.
Expand All @@ -40,11 +40,11 @@
* If an index is needed for mapping between the graph model and the source model, as is typically the case for
* {@link ActionHandler action handlers} and {@link OperationHandler operation handlers}, it is the responsibility of
* the graph model factory to create such an index while producing the graph model from the source model. The index
* shall be put into the model state too. Typically the {@link GModelIndex} is extended for a particular model source
* shall be put into the model state too. Typically the {@link GModelIndex} is extended for a particular source model
* type as well.
* </p>
*
* @see ModelSourceLoader
* @see SourceModelStorage
* @see GModelIndex
*/
public interface GModelFactory {
Expand All @@ -57,7 +57,7 @@ public interface GModelFactory {
void createGModel();

/**
* Graph model factory to be used if the graph model is already available from the model source.
* Graph model factory to be used if the graph model is already available from the source model.
*/
final class NullImpl implements GModelFactory {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,22 @@

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;

import org.apache.log4j.Logger;
import org.eclipse.glsp.graph.DefaultTypes;
import org.eclipse.glsp.graph.GGraph;
import org.eclipse.glsp.graph.GModelRoot;
import org.eclipse.glsp.graph.GraphFactory;
import org.eclipse.glsp.server.actions.SaveModelAction;
import org.eclipse.glsp.server.gson.GraphGsonConfigurationFactory;
import org.eclipse.glsp.server.model.GModelState;
import org.eclipse.glsp.server.types.GLSPServerException;
Expand All @@ -39,36 +44,31 @@
import com.google.inject.Inject;

/**
* A source model loader that reads the graph model directly from a JSON file.
* A source model storage that reads and writes the graph model directly from and to a JSON file.
*/
public class JsonFileGModelLoader implements ModelSourceLoader {
public class JsonFileGModelStore implements SourceModelStorage {

private static Logger LOG = Logger.getLogger(JsonFileGModelLoader.class);
private static Logger LOG = Logger.getLogger(JsonFileGModelStore.class);
private static String EMPTY_ROOT_ID = "glsp-graph";

@Inject
private GraphGsonConfigurationFactory gsonConfiguratior;
private GraphGsonConfigurationFactory gsonConfigurator;

@Inject
protected GModelState modelState;

@Override
public void loadSourceModel(final RequestModelAction action) {
final File file = convertToFile(modelState);
final File file = convertToFile(action.getOptions());
loadSourceModel(file, modelState).ifPresent(root -> {
modelState.setRoot(root);
modelState.getRoot().setRevision(-1);
});
}

protected File convertToFile(final GModelState modelState) {
return getOrThrow(ClientOptionsUtil.getSourceUriAsFile(modelState.getClientOptions()),
"Invalid file URI:" + ClientOptionsUtil.getSourceUri(modelState.getClientOptions()));
}

protected Optional<GModelRoot> loadSourceModel(final File file, final GModelState modelState) {
try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
Gson gson = gsonConfiguratior.configureGson().create();
Gson gson = gsonConfigurator.configureGson().create();
GGraph root = gson.fromJson(reader, GGraph.class);
if (root == null) {
boolean isEmpty = file.length() == 0;
Expand All @@ -91,4 +91,34 @@ protected GModelRoot createNewEmptyRoot(final GModelState modelState) {
return root;
}

@Override
public boolean saveSourceModel(final SaveModelAction action) {
File file = convertToFile(action);
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
Gson gson = gsonConfigurator.configureGson().setPrettyPrinting().create();
gson.toJson(modelState.getRoot(), GGraph.class, writer);
return saveIsDone(action);
} catch (IOException e) {
LOG.error(e);
throw new GLSPServerException("An error occured during save process.", e);
}
}

protected boolean saveIsDone(final SaveModelAction action) {
String sourceUri = ClientOptionsUtil.adaptUri(modelState.getClientOptions().get(ClientOptionsUtil.SOURCE_URI));
return action.getFileUri().map(uri -> ClientOptionsUtil.adaptUri(uri).equals(sourceUri)).orElse(true);
}

protected File convertToFile(final SaveModelAction action) {
if (action.getFileUri().isPresent()) {
return ClientOptionsUtil.getAsFile(action.getFileUri().get());
}
return convertToFile(modelState.getClientOptions());
}

protected File convertToFile(final Map<String, String> clientOptions) {
return getOrThrow(ClientOptionsUtil.getSourceUriAsFile(clientOptions),
"Invalid file URI:" + ClientOptionsUtil.getSourceUri(clientOptions));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.eclipse.glsp.server.actions.AbstractActionHandler;
import org.eclipse.glsp.server.actions.Action;
import org.eclipse.glsp.server.actions.ActionDispatcher;
import org.eclipse.glsp.server.features.modelsourcewatcher.ModelSourceWatcher;
import org.eclipse.glsp.server.features.modelsourcewatcher.SourceModelWatcher;
import org.eclipse.glsp.server.model.GModelState;
import org.eclipse.glsp.server.utils.ServerMessageUtil;
import org.eclipse.glsp.server.utils.ServerStatusUtil;
Expand All @@ -31,13 +31,13 @@
public class RequestModelActionHandler extends AbstractActionHandler<RequestModelAction> {

@Inject
protected ModelSourceLoader sourceModelLoader;
protected SourceModelStorage sourceModelStorage;

@Inject
protected ActionDispatcher actionDispatcher;

@Inject
protected Optional<ModelSourceWatcher> modelSourceWatcher;
protected Optional<SourceModelWatcher> sourceModelWatcher;

@Inject
protected ModelSubmissionHandler modelSubmissionHandler;
Expand All @@ -50,10 +50,10 @@ public List<Action> executeAction(final RequestModelAction action) {
modelState.setClientOptions(action.getOptions());

notifyStartLoading();
sourceModelLoader.loadSourceModel(action);
sourceModelStorage.loadSourceModel(action);
notifyFinishedLoading();

modelSourceWatcher.ifPresent(watcher -> watcher.startWatching());
sourceModelWatcher.ifPresent(watcher -> watcher.startWatching());

return modelSubmissionHandler.submitModel();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,39 @@
********************************************************************************/
package org.eclipse.glsp.server.features.core.model;

import org.eclipse.glsp.server.actions.SaveModelAction;
import org.eclipse.glsp.server.utils.ClientOptionsUtil;

/**
* A source model loader loads models into the model state.
* A source model storage handles the persistence of source models, i.e. loading and saving it from/into the model
* state.
* <p>
* A <i>source model</i> is an arbitrary model from which the graph model of the diagram is to be created.
* Implementations of source model loaders are specific to the type of source model or persistence format that is used
* for a type of source model. A source model loader obtains the information on which source model shall loaded from a
* Implementations of source model stores are specific to the type of source model or persistence format that is used
* for a type of source model. A source model storage obtains the information on which source model shall loaded from a
* {@link RequestModelAction}; typically its client options. Once the source model is loaded, a model loader is expected
* to put the loaded source model into the model state for further processing, such as transforming the loaded model
* into a graph model (see {@link GModelFactory}).
* On {@link #saveSourceModel(SaveModelAction) save}, the source model storage persists the source model from the model
* state.
* </p>
*
* @see ClientOptionsUtil
* @see GModelFactory
*/
public interface ModelSourceLoader {
public interface SourceModelStorage {
/**
* Loads a source model into the <code>modelState</code>.
*
* @param action Action sent by the client to specifying the information needed to load the source model.
*/
void loadSourceModel(RequestModelAction action);

/**
* Saves the source model from the <code>modelState</code>.
*
* @param action Action sent by the client to specifying the information needed to save the source model.
* @return Returns <code>true</code> if save is done, <code>false</code> otherwise.
*/
boolean saveSourceModel(SaveModelAction action);
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

import com.google.inject.Inject;

public class FileWatcher implements ClientSessionListener, ModelSourceWatcher {
public class FileWatcher implements ClientSessionListener, SourceModelWatcher {

protected Debouncer<ClientNotification> clientNotificationDebouncer;

Expand Down
Loading

0 comments on commit 2e4cbf8

Please sign in to comment.