package org.cytoscape.rest.internal.resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.cytoscape.io.write.CyWriter;
import org.cytoscape.io.write.VizmapWriterFactory;
import org.cytoscape.rest.internal.CyActivator.WriterListener;
import org.cytoscape.rest.internal.MappingFactoryManager;
import org.cytoscape.rest.internal.datamapper.VisualStyleMapper;
import org.cytoscape.rest.internal.serializer.VisualStyleModule;
import org.cytoscape.rest.internal.serializer.VisualStyleSerializer;
import org.cytoscape.rest.internal.task.HeadlessTaskMonitor;
import org.cytoscape.view.model.DiscreteRange;
import org.cytoscape.view.model.Range;
import org.cytoscape.view.model.VisualLexicon;
import org.cytoscape.view.model.VisualProperty;
import org.cytoscape.view.vizmap.VisualMappingFunction;
import org.cytoscape.view.vizmap.VisualPropertyDependency;
import org.cytoscape.view.vizmap.VisualStyle;
import org.cytoscape.view.vizmap.VisualStyleFactory;
import org.cytoscape.work.TaskMonitor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@Singleton
@Path("/v1/styles")
public class StyleResource extends AbstractResource {
private final VisualStyleSerializer styleSerializer = new VisualStyleSerializer();
@Context
private WriterListener writerListener;
@Context
private TaskMonitor tm;
@Context
private VisualStyleFactory vsFactory;
@Context
private MappingFactoryManager factoryManager;
private final ObjectMapper styleMapper;
private final VisualStyleMapper visualStyleMapper;
private final VisualStyleSerializer visualStyleSerializer;
public StyleResource() {
super();
this.styleMapper = new ObjectMapper();
this.styleMapper.registerModule(new VisualStyleModule());
this.visualStyleMapper = new VisualStyleMapper();
this.visualStyleSerializer = new VisualStyleSerializer();
}
/**
*
* @summary Get list of Visual Style titles
*
* @return List of Visual Style titles.
*/
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public String getStyleNames() {
final Collection<VisualStyle> styles = vmm.getAllVisualStyles();
final List<String> styleNames = new ArrayList<String>();
for (final VisualStyle style : styles) {
styleNames.add(style.getTitle());
}
try {
return getNames(styleNames);
} catch (IOException e) {
throw getError("Could not get style names.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* @summary Get number of Visual Styles
*
* @return Number of Visual Styles available in current session.
*
*/
@GET
@Path("/count")
@Produces(MediaType.APPLICATION_JSON)
public String getStylCount() {
return getNumberObjectString(JsonTags.COUNT, vmm.getAllVisualStyles().size());
}
/**
*
* @summary Delete a Visual Style
*
* @param name
*/
@DELETE
@Path("/{name}")
@Produces(MediaType.APPLICATION_JSON)
public Response deleteStyle(@PathParam("name") String name) {
vmm.removeVisualStyle(getStyleByName(name));
return Response.ok().build();
}
/**
* @summary Delete all Visual Styles except default style
*
*/
@DELETE
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public Response deleteAllStyles() {
Set<VisualStyle> styles = vmm.getAllVisualStyles();
Set<VisualStyle> toBeDeleted = new HashSet<VisualStyle>();
for(final VisualStyle style: styles) {
if(style.getTitle().equals("default") == false) {
toBeDeleted.add(style);
}
}
toBeDeleted.stream()
.forEach(style->vmm.removeVisualStyle(style));
return Response.ok().build();
}
/**
*
* @summary Delete a Visual Mapping from a Visual Style
*
* @param name Name of the Visual Style
* @param vpName Visual Property name associated with the mapping
*
*/
@DELETE
@Path("/{name}/mappings/{vpName}")
@Produces(MediaType.APPLICATION_JSON)
public Response deleteMapping(@PathParam("name") String name, @PathParam("vpName") String vpName) {
final VisualStyle style = getStyleByName(name);
final VisualProperty<?> vp = getVisualProperty(vpName);
if(vp == null) {
throw new NotFoundException("Could not find Visual Property: " + vpName);
}
final VisualMappingFunction<?,?> mapping = style.getVisualMappingFunction(vp);
if(mapping == null) {
throw new NotFoundException("Could not find mapping for: " + vpName);
}
style.removeVisualMappingFunction(vp);
return Response.ok().build();
}
/**
*
* @summary Get all default values for the Visual Style
*
* @param name Name of the Visual Style
*
* @return List of all default values
*
*/
@GET
@Path("/{name}/defaults")
@Produces(MediaType.APPLICATION_JSON)
public String getDefaults(@PathParam("name") String name) {
return serializeDefaultValues(name);
}
/**
*
* @summary Get a default value for the Visual Property
*
* @param name Name of Visual Style
* @param vp Unique ID of the Visual Property. This should be the unique ID of the VP.
*
* @return Default value for the Visual Property
*
*/
@GET
@Path("/{name}/defaults/{vp}")
@Produces(MediaType.APPLICATION_JSON)
public String getDefaultValue(@PathParam("name") String name, @PathParam("vp") String vp) {
return serializeDefaultValue(name, vp);
}
/**
* This method is only for Visual Properties with DiscreteRange, such as
* NODE_SHAPE or EDGE_LINE_TYPE.
*
* @summary Get all available range values for the Visual Property
*
* @param vp Visual Property ID
*
* @return List of all available values for the visual property.
*/
@GET
@Path("/visualproperties/{vp}/values")
@Produces(MediaType.APPLICATION_JSON)
public String getRangeValues(@PathParam("vp") String vp) {
return serializeRangeValue(vp);
}
/**
* @summary Get all available Visual Properties
*
* @return Array of Visual Properties
*
*/
@GET
@Path("/visualproperties")
@Produces(MediaType.APPLICATION_JSON)
public String getVisualProperties() {
final Set<VisualProperty<?>> vps = getAllVisualProperties();
try {
return styleSerializer.serializeVisualProperties(vps);
} catch (IOException e) {
e.printStackTrace();
throw getError("Could not serialize Visual Properties.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* @summary Get a Visual Property
*
* @param visualProperty Target Visual Property ID
*
* @return Visual Property as object
*/
@GET
@Path("/visualproperties/{visualProperty}")
@Produces(MediaType.APPLICATION_JSON)
public String getSingleVisualProperty(@PathParam("visualProperty") String visualProperty) {
final VisualProperty<?> vp = getVisualProperty(visualProperty);
try {
return styleSerializer.serializeVisualProperty(vp);
} catch (IOException e) {
throw getError("Could not serialize Visual Properties.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* @summary Get all Visual Mappings for the Visual Style
*
* @param name Name of the Visual Style
*
* @return List of all Visual Mappings.
*
*/
@GET
@Path("/{name}/mappings")
@Produces(MediaType.APPLICATION_JSON)
public String getMappings(@PathParam("name") String name) {
final VisualStyle style = getStyleByName(name);
final Collection<VisualMappingFunction<?, ?>> mappings = style.getAllVisualMappingFunctions();
if(mappings.isEmpty()) {
return "[]";
}
try {
return styleMapper.writeValueAsString(mappings);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw getError("Could not serialize Mappings.", e, Response.Status.INTERNAL_SERVER_ERROR);
} catch(Exception ex) {
ex.printStackTrace();
throw getError("Could not serialize Mappings.", ex, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* @summary Get a Visual Mapping associated with the Visual Property
*
* @param name Name of the Visual Style
* @param vp Unique ID of the Visual Property. This should be the unique ID of the VP.
*
* @return Visual Mapping assigned to the Visual Property
*
*/
@GET
@Path("/{name}/mappings/{vp}")
@Produces(MediaType.APPLICATION_JSON)
public String getMapping(@PathParam("name") String name, @PathParam("vp") String vp) {
final VisualStyle style = getStyleByName(name);
final VisualMappingFunction<?, ?> mapping = getMappingFunction(vp, style);
try {
return styleMapper.writeValueAsString(mapping);
} catch (JsonProcessingException e) {
throw getError("Could not serialize Mapping.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
private final VisualMappingFunction<?, ?> getMappingFunction(final String vp, final VisualStyle style) {
final VisualLexicon lexicon = getLexicon();
final Set<VisualProperty<?>> allVp = lexicon.getAllVisualProperties();
VisualProperty<?> visualProp = null;
for (VisualProperty<?> curVp : allVp) {
if (curVp.getIdString().equals(vp)) {
visualProp = curVp;
break;
}
}
if (visualProp == null) {
throw new NotFoundException("Could not find VisualProperty: " + vp);
}
final VisualMappingFunction<?, ?> mapping = style.getVisualMappingFunction(visualProp);
if(mapping == null) {
throw new NotFoundException("Could not find visual mapping function for " + vp);
}
return mapping;
}
private final String serializeDefaultValues(String name) {
final VisualStyle style = getStyleByName(name);
final VisualLexicon lexicon = getLexicon();
final Collection<VisualProperty<?>> vps = lexicon.getAllVisualProperties();
try {
return styleSerializer.serializeDefaults(vps, style);
} catch (IOException e) {
throw getError("Could not serialize default values.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
private final String serializeDefaultValue(final String styleName, final String vpName) {
final VisualStyle style = getStyleByName(styleName);
VisualProperty<Object> vp = (VisualProperty<Object>) getVisualProperty(vpName);
try {
return styleSerializer.serializeDefault(vp, style);
} catch (IOException e) {
throw getError("Could not serialize default value for " + vpName, e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
private final String serializeRangeValue(final String vpName) {
VisualProperty<Object> vp = (VisualProperty<Object>) getVisualProperty(vpName);
Range<Object> range = vp.getRange();
if (range.isDiscrete()) {
final DiscreteRange<Object> discRange = (DiscreteRange<Object>)range;
try {
return styleSerializer.serializeDiscreteRange(vp, discRange);
} catch (IOException e) {
throw getError("Could not serialize default value for "
+ vpName, e, Response.Status.INTERNAL_SERVER_ERROR);
}
} else {
throw new NotFoundException("Range object is not available for " + vpName);
}
}
private final Set<VisualProperty<?>> getAllVisualProperties() {
final VisualLexicon lexicon = getLexicon();
return lexicon.getAllVisualProperties();
}
private final VisualProperty<?> getVisualProperty(String vpName) {
final VisualLexicon lexicon = getLexicon();
final Collection<VisualProperty<?>> vps = lexicon.getAllVisualProperties();
for (VisualProperty<?> vp : vps) {
if (vp.getIdString().equals(vpName)) {
return vp;
}
}
return null;
}
// /////////// Update Default Values ///////////////
/**
*
* @summary Update a default value for the Visual Property
*
* The body of the request should be a list of key-value pair:
* <pre>
* [
* {
* "visualProperty": VISUAL_PROPERTY_ID,
* "value": value
* }, ...
* ]
* </pre>
*
* @param name Name of the Visual Style
*
*/
@PUT
@Path("/{name}/defaults")
@Produces(MediaType.APPLICATION_JSON)
public Response updateDefaults(@PathParam("name") String name, InputStream is) {
final VisualStyle style = getStyleByName(name);
final ObjectMapper objMapper = new ObjectMapper();
try {
final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
updateVisualProperties(rootNode, style);
} catch (Exception e) {
throw getError("Could not update default values.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
return Response.ok().build();
}
@SuppressWarnings("unchecked")
private final void updateVisualProperties(final JsonNode rootNode, VisualStyle style) {
for(JsonNode defaultNode: rootNode) {
final String vpName = defaultNode.get(VisualStyleMapper.MAPPING_VP).textValue();
@SuppressWarnings("rawtypes")
final VisualProperty vp = getVisualProperty(vpName);
if (vp == null) {
continue;
}
final Object value = vp.parseSerializableString(defaultNode.get("value").asText());
if(value != null) {
style.setDefaultValue(vp, value);
}
}
}
/**
*
* @summary Get a Visual Style in Cytoscape.js CSS format.
*
* @param name Name of the Visual Style
*
* @return Visual Style in Cytoscape.js CSS format. This is always in an array.
*
*/
@GET
@Path("/{name}.json")
@Produces(MediaType.APPLICATION_JSON)
public String getStyle(@PathParam("name") String name) {
if(networkViewManager.getNetworkViewSet().isEmpty()) {
throw getError("You need at least one view object to use this feature."
, new IllegalStateException(), Response.Status.INTERNAL_SERVER_ERROR);
}
final VisualStyle style = getStyleByName(name);
final VizmapWriterFactory jsonVsFact = this.writerListener.getFactory();
final ByteArrayOutputStream os = new ByteArrayOutputStream();
final Set<VisualStyle> styleCollection = new HashSet<VisualStyle>();
styleCollection.add(style);
try {
final CyWriter styleWriter = jsonVsFact.createWriter(os, styleCollection);
styleWriter.run(new HeadlessTaskMonitor());
String jsonString = os.toString("UTF-8");
os.close();
return jsonString;
} catch (Exception e) {
throw getError("Could not get Visual Style in Cytoscape.js format.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* This returns JSON version of Visual Style object with full details. Format is simple:
*
* <pre>
* {
* "title": (name of this Visual Style),
* "defaults": [ default values ],
* "mappings": [ Mappings ]
* }
* </pre>
*
* Essentially, this is a JSON version of the Visual Style XML file.
*
*
* @summary Get a Visual Style with full details
*
* @param name Name of the Visual Style
*
* @return Visual Style in Cytoscape JSON format
*/
@GET
@Path("/{name}")
@Produces(MediaType.APPLICATION_JSON)
public String getStyleFull(@PathParam("name") String name) {
final VisualStyle style = getStyleByName(name);
try {
return styleSerializer.serializeStyle(getLexicon().getAllVisualProperties(), style);
} catch (IOException e) {
throw getError("Could not get Visual Style JSON.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* @summary Create a new Visual Style from JSON.
*
* @return Title of the new Visual Style.
*/
@POST
@Path("/")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createStyle(InputStream is) {
final ObjectMapper objMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objMapper.readValue(is, JsonNode.class);
VisualStyle style = this.visualStyleMapper.buildVisualStyle(factoryManager, vsFactory, getLexicon(),
rootNode);
vmm.addVisualStyle(style);
// This may be different from the original one if same name exists.
final String result = "{\"title\": \""+ style.getTitle() + "\"}";
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (Exception e) {
throw getError("Could not create new Visual Style.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* Create a new Visual Mapping function from JSON and add it to the Style.
*
* <h3>Discrete Mapping</h3>
* <h3>Continuous Mapping</h3>
* <h3>Passthrough Mapping</h3>
*
* @summary Add a new Visual Mapping
*
* @param name Name of the Visual Style
*/
@POST
@Path("/{name}/mappings")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createMappings(@PathParam("name") String name,InputStream is) {
final VisualStyle style = getStyleByName(name);
final ObjectMapper objMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objMapper.readValue(is, JsonNode.class);
this.visualStyleMapper.buildMappings(style, factoryManager, getLexicon(),rootNode);
} catch (Exception e) {
e.printStackTrace();
throw getError("Could not create new Mapping.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
return Response.status(Response.Status.CREATED).build();
}
/**
*
* @summary Update name of a Visual Style
*
* @param name Original name of the Visual Style
*
*/
@PUT
@Path("/{name}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public void updateStyleName(@PathParam("name") String name, InputStream is) {
final VisualStyle style = getStyleByName(name);
final ObjectMapper objMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objMapper.readValue(is, JsonNode.class);
this.visualStyleMapper.updateStyleName(style, getLexicon(), rootNode);
} catch (Exception e) {
throw getError("Could not update Visual Style title.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Currently, this is same as POST; it simply replaces existing mapping.
* You need to send complete information for the new mappings.
*
* @summary Update an existing Visual Mapping
*
* @param name Name of visual Style
* @param vp Target Visual Property
*
*/
@PUT
@Path("/{name}/mappings/{vp}")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateMapping(@PathParam("name") String name, @PathParam("vp") String vp, InputStream is) {
final VisualStyle style = getStyleByName(name);
final VisualMappingFunction<?, ?> currentMapping = getMappingFunction(vp, style);
final ObjectMapper objMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objMapper.readValue(is, JsonNode.class);
this.visualStyleMapper.buildMappings(style, factoryManager, getLexicon(),rootNode);
} catch (Exception e) {
e.printStackTrace();
throw getError("Could not update Mapping.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
return Response.ok().build();
}
private final VisualStyle getStyleByName(final String name) {
final Collection<VisualStyle> styles = vmm.getAllVisualStyles();
for (final VisualStyle style : styles) {
if (style.getTitle().equals(name)) {
return style;
}
}
throw new NotFoundException("Could not find Visual Style: " + name);
}
/**
*
* Check status of Visual Property Dependencies. If a dependency is enables, it has true for "enabled."
*
* @summary Get all Visual Property Dependency status
*
* @param name Name of the Visual Style
*
* @return List of the status of all Visual Property dependencies.
*
*/
@GET
@Path("/{name}/dependencies")
@Produces(MediaType.APPLICATION_JSON)
public String getAllDependencies(@PathParam("name") String name) {
final VisualStyle style = getStyleByName(name);
final Set<VisualPropertyDependency<?>> dependencies = style.getAllVisualPropertyDependencies();
try {
return visualStyleSerializer.serializeDependecies(dependencies);
} catch (IOException e) {
throw getError("Could not get Visual Property denendencies.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* The body should be the following format:
* <pre>
* [
* {
* "visualPropertyDependnecy" : "DEPENDENCY_ID",
* "enabled" : true or false
* }, ... {}
* ]
* </pre>
*
* @summary Set Visual Property Dependency flags
*
* @param name Name of Visual Style
*
*/
@PUT
@Path("/{name}/dependencies")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public void updateDependencies(@PathParam("name") String name, InputStream is) {
final VisualStyle style = getStyleByName(name);
final ObjectMapper objMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objMapper.readValue(is, JsonNode.class);
this.visualStyleMapper.updateDependencies(style, rootNode);
} catch (Exception e) {
throw getError("Could not update Visual Style title.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
}