package org.cytoscape.rest.internal.resource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Singleton; import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; 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.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.cytoscape.io.write.CyWriter; import org.cytoscape.io.write.PresentationWriterFactory; import org.cytoscape.model.CyEdge; import org.cytoscape.model.CyIdentifiable; import org.cytoscape.model.CyNetwork; import org.cytoscape.model.CyNode; import org.cytoscape.rest.internal.GraphicsWriterManager; import org.cytoscape.rest.internal.datamapper.VisualStyleMapper; import org.cytoscape.rest.internal.serializer.VisualStyleSerializer; import org.cytoscape.rest.internal.task.HeadlessTaskMonitor; import org.cytoscape.task.write.ExportNetworkViewTaskFactory; import org.cytoscape.view.model.CyNetworkView; import org.cytoscape.view.model.View; import org.cytoscape.view.model.VisualLexicon; import org.cytoscape.view.model.VisualProperty; import org.cytoscape.view.presentation.RenderingEngine; import org.cytoscape.view.presentation.RenderingEngineManager; import org.cytoscape.view.presentation.property.BasicVisualLexicon; import org.cytoscape.work.Task; import org.cytoscape.work.TaskIterator; import org.cytoscape.work.util.BoundedDouble; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * REST API for Network View objects. * * TODO: add custom view information section * */ @Singleton @Path("/v1/networks/{networkId}/views") public class NetworkViewResource extends AbstractResource { // Filter rendering engine by ID. private static final String DING_ID = "org.cytoscape.ding"; private static final String DEF_HEIGHT = "600"; @Context @NotNull private RenderingEngineManager renderingEngineManager; @Context @NotNull private GraphicsWriterManager graphicsWriterManager; @Context @NotNull private ExportNetworkViewTaskFactory exportNetworkViewTaskFactory; private final VisualStyleMapper styleMapper; private final VisualStyleSerializer styleSerializer; private VisualLexicon lexicon; private Collection<VisualProperty<?>> nodeLexicon; private Collection<VisualProperty<?>> edgeLexicon; private Collection<VisualProperty<?>> networkLexicon; public NetworkViewResource() { super(); this.styleMapper = new VisualStyleMapper(); this.styleSerializer = new VisualStyleSerializer(); } private final void initLexicon() { // Prepare lexicon this.lexicon = getLexicon(); nodeLexicon = lexicon.getAllDescendants(BasicVisualLexicon.NODE); edgeLexicon = lexicon.getAllDescendants(BasicVisualLexicon.EDGE); networkLexicon = lexicon.getAllDescendants(BasicVisualLexicon.NETWORK).stream() .filter(vp->vp.getIdString().startsWith("NETWORK")).collect(Collectors.toSet());; } /** * @summary Create view for the network * * @param networkId * Network SUID * * @return SUID for the new Network View. * */ @POST @Path("/") @Produces(MediaType.APPLICATION_JSON) public Response createNetworkView(@PathParam("networkId") Long networkId) { final CyNetwork network = getCyNetwork(networkId); final CyNetworkView view = networkViewFactory.createNetworkView(network); networkViewManager.addNetworkView(view); return Response.status(Response.Status.CREATED) .entity(getNumberObjectString("networkViewSUID", view.getSUID())).build(); } /** * Cytoscape can have multiple views per network model. This feature is not * exposed to end-users, but you can access it from this API. * * @summary Get number of views for the given network model * * @param networkId * Network SUID * * @return Number of views for the network model * */ @GET @Path("/count") @Produces(MediaType.APPLICATION_JSON) public String getNetworkViewCount(@PathParam("networkId") Long networkId) { return getNumberObjectString(JsonTags.COUNT, networkViewManager.getNetworkViews(getCyNetwork(networkId)).size()); } /** * * In general, a network has one view. But if there are multiple views, this * deletes all of them. * * @summary Delete all network views * * @param networkId * Network SUID * */ @DELETE @Path("/") @Produces(MediaType.APPLICATION_JSON) public Response deleteAllNetworkViews(@PathParam("networkId") Long networkId) { try { final Collection<CyNetworkView> views = this.networkViewManager.getNetworkViews(getCyNetwork(networkId)); final Set<CyNetworkView> toBeDestroyed = new HashSet<CyNetworkView>(views); for (final CyNetworkView view : toBeDestroyed) { networkViewManager.destroyNetworkView(view); } return Response.ok().build(); } catch (Exception e) { throw getError("Could not delete network views for network with SUID: " + networkId, e, Response.Status.INTERNAL_SERVER_ERROR); } } /** * * This returns the first view of the network. As of Cytoscape 3.2.x, this is the * only view accessible Cytoscape GUI. * * @summary Convenience method to get the first view model. * * @param networkId * Network SUID * @param file (Optional) If you want to get the view as a file, you can specify output file name with extension. * For example, file=test.sif creates new SIF file in the current directory. * * @return Network view in JSON or location of the file */ @GET @Path("/first") @Produces(MediaType.APPLICATION_JSON) public Response getFirstNetworkView(@PathParam("networkId") Long networkId, @QueryParam("file") String file) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); if (views.isEmpty()) { throw new NotFoundException("Could not find view for the network: " + networkId); } final CyNetworkView view = views.iterator().next(); return getNetworkView(networkId, view.getSUID(), file); } /** * @summary Delete a view whatever found first. * * @param networkId * Network SUID * */ @DELETE @Path("/first") @Produces(MediaType.APPLICATION_JSON) public Response deleteFirstNetworkView(@PathParam("networkId") Long networkId) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); if (views.isEmpty() == false) { networkViewManager.destroyNetworkView(views.iterator().next()); } return Response.ok().build(); } /** * To use this API, you need to know SUID of the CyNetworkView, in addition * to CyNetwork SUID. * * @summary Get a network view (as JSON or a file) * * @param networkId * Network SUID * @param viewId * Network View SUID * * @param file (Optional) If you want to get the view as a file, you can specify output file name with extension. * For example, file=test.sif creates new SIF file in the current directory. * * @return View in Cytoscape.js JSON. Currently, view information is (x, y) * location only. If you specify <strong>file</strong> option, this returns absolute path to the file. */ @GET @Path("/{viewId}") @Produces(MediaType.APPLICATION_JSON) public Response getNetworkView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @QueryParam("file") String file) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); CyNetworkView targetView = null; for (final CyNetworkView view : views) { final Long vid = view.getSUID(); if (vid.equals(viewId)) { targetView = view; break; } } if(targetView == null) { return Response.ok("{}").build(); } else { if(file != null) { return Response.ok(writeNetworkFile(file, targetView)).build(); } else { return Response.ok(getNetworkViewString(targetView)).build(); } } } private final Map<String, String> writeNetworkFile(String file, CyNetworkView view) { File networkFile = null; try { networkFile = new File(file); TaskIterator itr = exportNetworkViewTaskFactory.createTaskIterator(view, networkFile); while(itr.hasNext()) { final Task task = itr.next(); task.run(new HeadlessTaskMonitor()); } } catch (Exception e) { e.printStackTrace(); throw getError("Could not save network file.", e, Response.Status.INTERNAL_SERVER_ERROR); } final Map<String, String> message = new HashMap<>(); message.put("file", networkFile.getAbsolutePath()); return message; } /** * @summary Get SUID of all network views * * @param networkId * Network SUID * * @return Array of all network view SUIDs * */ @GET @Path("/") @Produces(MediaType.APPLICATION_JSON) public Collection<Long> getAllNetworkViews(@PathParam("networkId") Long networkId) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); final Collection<Long> suids = new HashSet<Long>(); for (final CyNetworkView view : views) { final Long viewId = view.getSUID(); suids.add(viewId); } return suids; } private final String getNetworkViewString(final CyNetworkView networkView) { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); CyWriter writer = cytoscapeJsWriterFactory.createWriter(stream, networkView); String jsonString = null; try { writer.run(null); jsonString = stream.toString("UTF-8"); stream.close(); } catch (Exception e) { e.printStackTrace(); } return jsonString; } /** * Generate a PNG image as stream. Default size is 600 px. * * @summary Get PNG image of a network view * * @param networkId * Network SUID * @param viewId * Network View SUID * @param height * Optional height of the image. Width will be set automatically. * * @return PNG image stream. */ @GET @Path("/first.png") @Produces("image/png") public Response getFirstImageAsPng(@PathParam("networkId") Long networkId, @DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) { return getImage("png", networkId, height); } @GET @Path("/first.svg") @Produces("image/svg+xml") public Response getFirstImageAsSvg(@PathParam("networkId") Long networkId, @DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) { return getImage("svg", networkId, height); } @GET @Path("/first.pdf") @Produces("image/pdf") public Response getFirstImageAsPdf(@PathParam("networkId") Long networkId, @DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) { return getImage("pdf", networkId, height); } private final Response getImage(String fileType, Long networkId, int height) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); if (views.isEmpty()) { throw getError("Could not create image.", new NotFoundException("Could not find view for the network: " + networkId), Response.Status.NOT_FOUND); } final PresentationWriterFactory factory = graphicsWriterManager.getFactory(fileType); final CyNetworkView view = views.iterator().next(); return imageGenerator(fileType, factory, view, height, height); } /** * Generate a PNG network image as stream. Default size is 600 px. * * @summary Get PNG image of a network view * * @param networkId * Network SUID * @param viewId * Network View SUID * @param height * Optional height of the image. Width will be set automatically. * * @return PNG image stream. */ @GET @Path("/{viewId}.png") @Produces("image/png") public Response getImageAsPng(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) { return getImageForView("png", networkId, viewId, height); } @GET @Path("/{viewId}.svg") @Produces("image/svg+xml") public Response getImageAsSvg(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) { return getImageForView("svg", networkId, viewId, height); } @GET @Path("/{viewId}.pdf") @Produces("image/pdf") public Response getImageAsPdf(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId) { return getImageForView("pdf", networkId, viewId, 500); } private Response getImageForView(String fileType, Long networkId, Long viewId, Integer height) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); for (final CyNetworkView view : views) { final Long vid = view.getSUID(); if (vid.equals(viewId)) { return getImage(fileType, networkId, height); } } throw new NotFoundException("Could not find view for the network: " + networkId); } private final Response imageGenerator(final String fileType, PresentationWriterFactory factory, final CyNetworkView view, int width, int height) { final Collection<RenderingEngine<?>> re = renderingEngineManager.getRenderingEngines(view); if (re.isEmpty()) { throw new IllegalArgumentException("No rendering engine."); } try { RenderingEngine<?> engine = null; for (RenderingEngine<?> r : re) { if (r.getRendererId().equals(DING_ID)) { engine = r; break; } } if (engine == null) { throw new IllegalArgumentException("Could not find Ding rendering eigine."); } final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final CyWriter writer = factory.createWriter(baos, engine); if (fileType.equals("png")) { // Do some hack to set properties... // TODO: Are there cleaner way to access these props? final Object zl = writer.getClass().getMethod("getZoom").invoke(writer); final BoundedDouble bound = (BoundedDouble)zl; //System.out.println("UB = " + bound.getUpperBound()); //System.out.println("LB = " + bound.getLowerBound()); // Set large upper bound for generating large PNG. bound.setBounds(bound.getLowerBound(), 5000.0); writer.getClass().getMethod("setHeightInPixels", int.class).invoke(writer, height); } writer.run(new HeadlessTaskMonitor()); final byte[] imageData = baos.toByteArray(); return Response.ok(new ByteArrayInputStream(imageData)).build(); } catch (Exception e) { e.printStackTrace(); throw getError("Could not create image.", e, Response.Status.INTERNAL_SERVER_ERROR); } } /** * By passing list of key-value pair for each Visual Property, update node view. * * The body should have the following JSON: * * <pre> * [ * { * "SUID": SUID of node, * "view": [ * { * "visualProperty": "Visual Property Name, like NODE_FILL_COLOR", * "value": "Serialized form of value, like 'red.'" * }, * ... * {} * ] * }, * ... * {} * ] * </pre> * * Note that this API directly set the value to the view objects, and once Visual Style applied, * those values are overridden by the Visual Style. * * @summary Update node/edge view objects at once * * @param networkId Network SUID * @param viewId Network view SUID * @param objectType Type of objects ("nodes" or "edges") * */ @PUT @Path("/{viewId}/{objectType}") @Consumes(MediaType.APPLICATION_JSON) public Response updateViews(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @PathParam("objectType") String objectType, final InputStream is) { final CyNetworkView networkView = getView(networkId, viewId); final ObjectMapper objMapper = new ObjectMapper(); try { // This should be an JSON array. final JsonNode rootNode = objMapper.readValue(is, JsonNode.class); for (JsonNode entry : rootNode) { final Long objectId = entry.get(CyIdentifiable.SUID).asLong(); final JsonNode viewNode = entry.get("view"); if(objectId == null || viewNode == null) { continue; } View<? extends CyIdentifiable> view = null; if (objectType.equals("nodes")) { view = networkView.getNodeView(networkView.getModel().getNode(objectId)); } else if (objectType.equals("edges")) { view = networkView.getEdgeView(networkView.getModel().getEdge(objectId)); } else if(objectType.equals("network")) { view = networkView; } else { throw getError("Method not supported.", new IllegalStateException(), Response.Status.INTERNAL_SERVER_ERROR); } if (view == null) { throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND); } styleMapper.updateView(view, viewNode, getLexicon()); } // Repaint networkView.updateView(); } catch (Exception e) { throw getError( "Could not parse the input JSON for updating view because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR); } return Response.ok().build(); } /** * By passing a list of key-value pair for each Visual Property, update single node/edge view. * * The body should have the following JSON: * * <pre> * [ * { * "visualProperty": "Visual Property Name, like NODE_FILL_COLOR", * "value": "Serialized form of value, like 'red.'" * }, * ... * {} * ] * </pre> * * Note that this API directly set the value to the view objects, and once Visual Style applied, * those values are overridden by the Visual Style. * * @summary Update single node/edge view object * * @param networkId Network SUID * @param viewId Network view SUID * @param objectType Type of objects (nodes, edges, or network) * @param objectId node/edge SUID (NOT node/edge view SUID) * */ @PUT @Path("/{viewId}/{objectType}/{objectId}") @Consumes(MediaType.APPLICATION_JSON) public Response updateView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @PathParam("objectType") String objectType, @PathParam("objectId") Long objectId, final InputStream is) { final CyNetworkView networkView = getView(networkId, viewId); View<? extends CyIdentifiable> view = null; if(objectType.equals("nodes")) { view = networkView.getNodeView(networkView.getModel().getNode(objectId)); } else if(objectType.equals("edges")) { view = networkView.getEdgeView(networkView.getModel().getEdge(objectId)); } else if(objectType.equals("network")) { view = networkView; } if(view == null) { throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND); } final ObjectMapper objMapper = new ObjectMapper(); try { // This should be an JSON array. final JsonNode rootNode = objMapper.readValue(is, JsonNode.class); styleMapper.updateView(view, rootNode, getLexicon()); } catch (Exception e) { throw getError("Could not parse the input JSON for updating view because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR); } // Repaint networkView.updateView(); return Response.ok().build(); } /** * * By passing a list of key-value pairs for each Visual Property, update network view. * * The body should have the following JSON: * * <pre> * [ * { * "visualProperty": "Visual Property Name, like NETWORK_BACKGROUND_PAINT", * "value": "Serialized form of value, like 'red.'" * }, * ... * {} * ] * </pre> * * Note that this API directly set the value to the view objects, and once Visual Style applied, * those values are overridden by the Visual Style. * * @summary Update single network view value, such as background color or zoom level. * * @param networkId Network SUID * @param viewId Network view SUID * */ @PUT @Path("/{viewId}/network") @Consumes(MediaType.APPLICATION_JSON) public Response updateNetworkView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, final InputStream is) { final CyNetworkView networkView = getView(networkId, viewId); final ObjectMapper objMapper = new ObjectMapper(); try { // This should be an JSON array. final JsonNode rootNode = objMapper.readValue(is, JsonNode.class); styleMapper.updateView(networkView, rootNode, getLexicon()); } catch (Exception e) { throw getError("Could not parse the input JSON for updating view because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR); } // Repaint networkView.updateView(); return Response.ok().build(); } @GET @Path("/{viewId}/network") @Produces(MediaType.APPLICATION_JSON) public Response getNetworkVisualProps(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId) { return this.getViews(networkId, viewId, "network", null); } /** * @summary Get view object for the specified type (node or edge) * * @param networkId Network SUID * @param viewId Network view SUID * @param objectType nodes or edges * @param objectId Object's SUID * */ @GET @Path("/{viewId}/{objectType}/{objectId}") @Produces(MediaType.APPLICATION_JSON) public String getView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @PathParam("objectType") String objectType, @PathParam("objectId") Long objectId) { final CyNetworkView networkView = getView(networkId, viewId); View<? extends CyIdentifiable> view = null; Collection<VisualProperty<?>> lexicon = null; if(nodeLexicon == null) { initLexicon(); } if(objectType.equals("nodes")) { view = networkView.getNodeView(networkView.getModel().getNode(objectId)); lexicon = nodeLexicon; } else if(objectType.equals("edges")) { view = networkView.getNodeView(networkView.getModel().getNode(objectId)); lexicon = edgeLexicon; } if(view == null) { throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND); } try { return styleSerializer.serializeView(view, lexicon); } catch (IOException e) { throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR); } } @GET @Path("/{viewId}/{objectType}/{objectId}/{visualProperty}") @Produces(MediaType.APPLICATION_JSON) public String getSingleVisualPropertyValue(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @PathParam("objectType") String objectType, @PathParam("objectId") Long objectId, @PathParam("visualProperty") String visualProperty) { final CyNetworkView networkView = getView(networkId, viewId); Collection<VisualProperty<?>> vps = null; View<? extends CyIdentifiable> view = null; if(nodeLexicon == null) { initLexicon(); } if(objectType.equals("nodes")) { view = networkView.getNodeView(networkView.getModel().getNode(objectId)); vps = nodeLexicon; } else if(objectType.equals("edges")) { view = networkView.getNodeView(networkView.getModel().getNode(objectId)); vps = edgeLexicon; } if(view == null) { throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND); } return getSingleVp(visualProperty, view, vps); } @GET @Path("/{viewId}/network/{visualProperty}") @Produces(MediaType.APPLICATION_JSON) public String getNetworkVisualProp(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @PathParam("visualProperty") String visualProperty) { final CyNetworkView networkView = getView(networkId, viewId); if(nodeLexicon == null) { initLexicon(); } return getSingleVp(visualProperty, networkView, networkLexicon); } private String getSingleVp(final String visualProperty, final View<? extends CyIdentifiable> view, final Collection<VisualProperty<?>> vps) { VisualProperty<?> targetVp = null; for(final VisualProperty<?> vp: vps) { if(vp.getIdString().equals(visualProperty)) { targetVp = vp; break; } } if(targetVp == null) { throw getError("Could not find such Visual Property: " + visualProperty , new NotFoundException(), Response.Status.NOT_FOUND); } try { return styleSerializer.serializeSingleVisualProp(view, targetVp); } catch (IOException e) { throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR); } } /** * @summary Get current values for a specific Visual Property * * @param networkId Network SUID * @param viewId SUID of network view * @param objectType nodes or edges * * @param visualProperty Unique name of a Visual Property */ @GET @Path("/{viewId}/{objectType}") @Produces(MediaType.APPLICATION_JSON) public Response getViews(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, @PathParam("objectType") String objectType, @QueryParam("visualProperty") String visualProperty) { if(visualProperty != null) { final String result = getSingleVisualPropertyOfViews(networkId, viewId, objectType, visualProperty); return Response.ok(result).build(); } if(nodeLexicon == null) { initLexicon(); } Collection<VisualProperty<?>> vps = null; if(objectType.equals("nodes")) { vps = nodeLexicon; } else if(objectType.equals("edges")) { vps = edgeLexicon; } else if(objectType.equals("network")) { vps = networkLexicon; } return Response.ok(getViewForVPList(networkId, viewId, objectType, vps)).build(); } private final String getViewForVPList(final Long networkId, final Long viewId, final String objectType, Collection<VisualProperty<?>> vps) { final CyNetworkView networkView = getView(networkId, viewId); Collection<? extends View<? extends CyIdentifiable>> graphObjects = null; if(objectType.equals("nodes")) { graphObjects = networkView.getNodeViews(); } else if(objectType.equals("edges")) { graphObjects = networkView.getEdgeViews(); } else if(objectType.equals("network")) { try { return styleSerializer.serializeView(networkView, vps); } catch (IOException e) { throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR); } } if(graphObjects == null || graphObjects.isEmpty()) { throw getError("Could not find views.", new IllegalArgumentException(), Response.Status.NOT_FOUND); } try { return styleSerializer.serializeViews(graphObjects, vps); } catch (IOException e) { throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR); } } private final String getSingleVisualPropertyOfViews(Long networkId, Long viewId, String objectType, String visualPropertyName) { if(nodeLexicon == null) { initLexicon(); } final Collection<VisualProperty<?>> vps = new HashSet<>(); VisualProperty<?> vp = null; if(objectType.equals("nodes")) { vp = lexicon.lookup(CyNode.class, visualPropertyName); } else if(objectType.equals("edges")) { vp = lexicon.lookup(CyEdge.class, visualPropertyName); } if(vp == null) { throw getError("Visual Property does not exist: " + visualPropertyName, new NotFoundException(), Response.Status.NOT_FOUND); } vps.add(vp); return getViewForVPList(networkId, viewId, objectType, vps); } private final CyNetworkView getView(Long networkId, Long viewId) { final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId); for (final CyNetworkView view : views) { final Long vid = view.getSUID(); if (vid.equals(viewId)) { return view; } } throw new NotFoundException("Could not find view: " + viewId); } }