JAL-3878 Create service agnostic param datastore and set
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 24 Mar 2022 12:14:11 +0000 (13:14 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 24 Mar 2022 12:14:11 +0000 (13:14 +0100)
src/jalview/ws2/params/SimpleParamDatastore.java [new file with mode: 0644]
src/jalview/ws2/params/SimpleParamSet.java [new file with mode: 0644]

diff --git a/src/jalview/ws2/params/SimpleParamDatastore.java b/src/jalview/ws2/params/SimpleParamDatastore.java
new file mode 100644 (file)
index 0000000..259bdca
--- /dev/null
@@ -0,0 +1,256 @@
+package jalview.ws2.params;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import jalview.bin.Cache;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
+import jalview.ws.params.ParamManager;
+import jalview.ws.params.WsParamSetI;
+
+/**
+ * A web client agnostic parameters datastore that provides view of the
+ * parameters and delegates parameters storage to {@link ParamManager}
+ * if given. Parameter datastore maintains the applicable service url
+ * the list of service parameters and both presets and user defined
+ * parameter sets 
+ */
+public class SimpleParamDatastore implements ParamDatastoreI
+{
+  protected URL serviceUrl;
+  protected List<ArgumentI> parameters;
+  protected List<SimpleParamSet> servicePresets;
+  protected List<SimpleParamSet> userPresets = new ArrayList<>();
+  protected ParamManager manager;
+
+  /**
+   * Create new parameter datastore bound to the specified url with
+   * given service parameters and presets. Additionally, a parameters
+   * manager may be provided that will be used to load and store
+   * user parameter sets.
+   *  
+   * @param serviceUrl applicable url
+   * @param parameters service parameters
+   * @param presets unmodifiable service presets
+   * @param manager parameter manager used to load and store user presets
+   */
+  public SimpleParamDatastore(URL serviceUrl, List<ArgumentI> parameters,
+      List<? extends WsParamSetI> presets, ParamManager manager)
+  {
+    this.serviceUrl = serviceUrl;
+    this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters));
+    this.servicePresets = new ArrayList<>(presets.size());
+    for (var preset : presets)
+    {
+      if (preset instanceof SimpleParamSet)
+        servicePresets.add((SimpleParamSet) preset);
+      else
+        servicePresets.add(new SimpleParamSet(preset));
+    }
+    this.servicePresets = Collections.unmodifiableList(this.servicePresets);
+    this.manager = manager;
+    if (manager != null)
+      _initManager(manager);
+  }
+  
+  private void _initManager(ParamManager manager)
+  {
+    manager.registerParser(serviceUrl.toString(), this);
+    WsParamSetI[] paramSets = manager.getParameterSet(null, serviceUrl.toString(),
+        true, false);
+    if (paramSets != null)
+    {
+      for (WsParamSetI paramSet : paramSets)
+      {
+        // TODO: handle mismatch between preset and current parameters
+        if (paramSet instanceof SimpleParamSet)
+          userPresets.add((SimpleParamSet) paramSet);
+        else
+        {
+          userPresets.add(new SimpleParamSet(paramSet));
+          Cache.log.warn(String.format(
+              "Parameter set instance type %s is not applicable to service"
+              + "at %s.", paramSet.getClass(), serviceUrl));
+        }
+      }
+    }
+  }
+  
+  @Override
+  public List<WsParamSetI> getPresets()
+  {
+    List<WsParamSetI> presets = new ArrayList<>();
+    presets.addAll(servicePresets);
+    presets.addAll(userPresets);
+    return presets;
+  }
+
+  @Override
+  public SimpleParamSet getPreset(String name)
+  {
+    SimpleParamSet preset = null;
+    preset = getUserPreset(name);
+    if (preset != null)
+      return preset;
+    preset = getServicePreset(name);
+    if (preset != null)
+      return preset;
+    return null;
+  }
+  
+  public SimpleParamSet getUserPreset(String name)
+  {
+    for (SimpleParamSet preset : userPresets)
+    {
+      if (name.equals(preset.getName()))
+        return preset;
+    }
+    return null;
+  }
+  
+  public SimpleParamSet getServicePreset(String name)
+  {
+    for (SimpleParamSet preset : servicePresets)
+    {
+      if (name.equals(preset.getName()))
+        return preset;
+    }
+    return null;
+  }
+
+  @Override
+  public List<ArgumentI> getServiceParameters()
+  {
+    return parameters;
+  }
+
+  @Override
+  public boolean presetExists(String name)
+  {
+    return getPreset(name) != null;
+  }
+
+  @Override
+  public void deletePreset(String name)
+  {
+    var userPreset = getUserPreset(name);
+    if (userPreset != null)
+    {
+      userPresets.remove(userPreset);
+      if (manager != null)
+      {
+        manager.deleteParameterSet(userPreset);
+      }
+    }
+    else if (getServicePreset(name) != null)
+    {
+      throw new RuntimeException(MessageManager.getString(
+          "error.implementation_error_attempt_to_delete_service_preset"));
+    }
+    else
+    {
+      Cache.log.warn("Implementation error: no preset to delete");
+    }
+  }
+
+  @Override
+  public void storePreset(String presetName, String text, List<ArgumentI> jobParams)
+  {
+    var builder = SimpleParamSet.newBuilder();
+    builder.name(presetName);
+    builder.description(text);
+    builder.arguments(jobParams);
+    builder.url(serviceUrl.toString());
+    builder.modifiable(true);
+    var preset = builder.build();
+    userPresets.add(preset);
+    if (manager != null)
+      manager.storeParameterSet(preset);
+  }
+
+  @Override
+  public void updatePreset(String oldName, String newName, String text, List<ArgumentI> jobParams)
+  {
+    var preset = getPreset(oldName != null ? oldName : newName);
+    if (preset == null)
+      throw new RuntimeException(MessageManager.formatMessage(
+          "error.implementation_error_cannot_locate_oldname_presetname",
+          oldName, newName));
+    preset.setName(newName);
+    preset.setDescription(text);
+    preset.setArguments(jobParams);
+    preset.setApplicableUrls(new String[] { serviceUrl.toString() });
+    if (manager != null)
+      manager.storeParameterSet(preset);
+  }
+
+  @Override
+  public WsParamSetI parseServiceParameterFile(String name, String description,
+      String[] serviceURL, String parameters)
+      throws IOException
+  {
+    var builder = SimpleParamSet.newBuilder();
+    builder.name(name);
+    builder.description(description);
+    builder.urls(serviceURL);
+    builder.modifiable(true);
+    Unmarshaller unmarshaller;
+    try
+    {
+      var ctx = JAXBContext.newInstance(ArgumentBeanList.class);
+      unmarshaller = ctx.createUnmarshaller();
+    } catch (JAXBException e)
+    {
+      throw new RuntimeException(e);
+    }
+    ArgumentBeanList argList;
+    try
+    {
+      argList = (ArgumentBeanList) unmarshaller.unmarshal(new StringReader(parameters));
+    } catch (JAXBException | ClassCastException e)
+    {
+      throw new IOException("Unable to load parameters from file", e);
+    }
+    builder.arguments(argList.arguments);
+    return builder.build();
+  }
+
+  @Override
+  public String generateServiceParameterFile(WsParamSetI pset) throws IOException
+  {
+    Marshaller marshaller;
+    try 
+    {
+      var ctx = JAXBContext.newInstance(ArgumentBeanList.class);
+      marshaller = ctx.createMarshaller();
+      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+      marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
+    } catch (JAXBException e)
+    {
+      throw new RuntimeException(e);
+    }
+    ArgumentBeanList argList = ArgumentBeanList.fromList(pset.getArguments());
+    var out = new ByteArrayOutputStream();
+    try
+    {
+      marshaller.marshal(argList, out);
+    } catch (JAXBException e)
+    {
+      throw new IOException("Unable to generate parameters file", e);
+    }
+    return out.toString();
+  }
+
+}
diff --git a/src/jalview/ws2/params/SimpleParamSet.java b/src/jalview/ws2/params/SimpleParamSet.java
new file mode 100644 (file)
index 0000000..9050c5f
--- /dev/null
@@ -0,0 +1,281 @@
+package jalview.ws2.params;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.WsParamSetI;
+
+/**
+ * A simple, web service client agnostic, representation of parameter sets.
+ * Instances are created from the service data fetched from the server or from
+ * the user preset files. This implementation of {@link WsParamSetI} is meant to
+ * decouple parameter set representation form specific clients.
+ * 
+ * @author mmwarowny
+ *
+ */
+public class SimpleParamSet implements WsParamSetI
+{
+  /**
+   * A convenience builder of {@link SimpleParamSet} objects.
+   * 
+   * @author mmwarowny
+   */
+  public static class Builder
+  {
+    private String name = "default";
+
+    private String description = "";
+
+    private List<String> applicableUrls = new ArrayList<>();
+
+    private boolean modifiable = false;
+
+    private List<ArgumentI> arguments = new ArrayList<>();
+
+    public Builder()
+    {
+    }
+
+    /**
+     * Set a name of parameter set.
+     * 
+     * @param val
+     *          name
+     */
+    public void name(String val)
+    {
+      name = val;
+    }
+
+    /**
+     * Set a description of parameter set.
+     * 
+     * @param val
+     *          description
+     */
+    public void description(String val)
+    {
+      description = val;
+    }
+
+    /**
+     * Add a url to applicable urls for parameter set.
+     * 
+     * @param val
+     *          applicable url
+     */
+    public void url(String val)
+    {
+      applicableUrls.add(val);
+    }
+
+    /**
+     * Set all applicable urls for parameter set. Current url list will be
+     * replaced by provided urls.
+     * 
+     * @param val
+     *          applicable urls
+     */
+    public void urls(String[] val)
+    {
+      applicableUrls.clear();
+      for (String url : val)
+        applicableUrls.add(url);
+    }
+
+    /**
+     * Set modifiable flag for parameter set.
+     * 
+     * @param val
+     *          modifiable
+     */
+    public void modifiable(boolean val)
+    {
+      modifiable = val;
+    }
+
+    /**
+     * Add an argument to the preset arguments.
+     * 
+     * @param val
+     *          argument to be added
+     */
+    public void argument(ArgumentI val)
+    {
+      arguments.add(val);
+    }
+
+    /**
+     * Set arguments for parameter set. Current parameters list will be
+     * replaced by provided arguments.
+     * 
+     * @param val
+     *          arguments to be added
+     */
+    public void arguments(List<? extends ArgumentI> val)
+    {
+      arguments.clear();
+      arguments.addAll(val);
+    }
+
+    /**
+     * Build a new {@link SimpleParamSet} object from the current state of this
+     * builder.
+     * 
+     * @return new paramset instance
+     */
+    public SimpleParamSet build()
+    {
+      return new SimpleParamSet(this);
+    }
+  }
+
+  protected String name;
+
+  protected String description;
+
+  protected String[] applicableUrls;
+
+  protected String sourceFile;
+
+  protected boolean modifiable;
+
+  protected List<ArgumentI> arguments;
+
+  protected SimpleParamSet(Builder builder)
+  {
+    this.name = builder.name;
+    this.description = builder.description;
+    this.applicableUrls = builder.applicableUrls.toArray(new String[0]);
+    this.sourceFile = null;
+    this.modifiable = builder.modifiable;
+    setArguments(builder.arguments);
+  }
+
+  /**
+   * Create a copy of the provided paramset. The new instance has the same
+   * properties as the original paramset. The arguments list is a shallow copy
+   * of the original arguments.
+   * 
+   * @param copy
+   */
+  public SimpleParamSet(WsParamSetI copy)
+  {
+    this.name = copy.getName();
+    this.description = copy.getDescription();
+    var urls = copy.getApplicableUrls();
+    this.applicableUrls = Arrays.copyOf(urls, urls.length);
+    this.sourceFile = copy.getSourceFile();
+    this.modifiable = copy.isModifiable();
+    setArguments(copy.getArguments());
+  }
+
+  /**
+   * Create a new instance of the parameter set builder.
+   * 
+   * @return new parameter set builder
+   */
+  public static Builder newBuilder()
+  {
+    return new Builder();
+  }
+
+  @Override
+  public String getName()
+  {
+    return name;
+  }
+
+  /**
+   * Set a human readable name for this parameter set.
+   * 
+   * @param name
+   *          new name
+   */
+  public void setName(String name)
+  {
+    this.name = name;
+  }
+
+  @Override
+  public String getDescription()
+  {
+    return description;
+  }
+
+  /**
+   * Set additional notes for this parameter set.
+   * 
+   * @param description
+   *          additional notes
+   */
+  public void setDescription(String description)
+  {
+    this.description = description;
+  }
+
+  @Override
+  public String[] getApplicableUrls()
+  {
+    return applicableUrls;
+  }
+
+  /**
+   * Set the list of service endpoints which this parameter set is valid for.
+   * 
+   * @param urls
+   *          new service endpoints
+   */
+  public void setApplicableUrls(String[] urls)
+  {
+    this.applicableUrls = urls;
+  }
+
+  @Override
+  public String getSourceFile()
+  {
+    return sourceFile;
+  }
+
+  @Override
+  public void setSourceFile(String newFile)
+  {
+    this.sourceFile = newFile;
+  }
+
+  @Override
+  public boolean isModifiable()
+  {
+    return this.modifiable;
+  }
+
+  /**
+   * Set whether this parameter set is modifiable or not.
+   * 
+   * @param modifiable
+   *          new modifiable value
+   */
+  public void setModifiable(boolean modifiable)
+  {
+    this.modifiable = modifiable;
+  }
+
+  @Override
+  public List<ArgumentI> getArguments()
+  {
+    return this.arguments;
+  }
+
+  @Override
+  public void setArguments(List<ArgumentI> args)
+  {
+    if (!isModifiable())
+      throw new UnsupportedOperationException(
+          "Attempting to modify an unmodifiable parameter set");
+    this.arguments = Collections.unmodifiableList(new ArrayList<>(args));
+  }
+}