From 7f09e24112e75ba4d3318394566a7963c6eda7c6 Mon Sep 17 00:00:00 2001 From: jingjingbic Date: Wed, 15 Nov 2023 13:31:05 -0800 Subject: [PATCH 1/4] Add new API for UD-2656. --- pom.xml | 4 +- .../rest/services/v3/SearchServiceV3.java | 158 ++++++++++++++++++ 2 files changed, 160 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fcfa6c4a..245b6eb9 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.ndexbio ndexbio-rest war - 2.5.5 + 2.5.6-SNAPSHOT ndexbio-rest http://maven.apache.org 2013 @@ -20,7 +20,7 @@ org.ndexbio ndex-object-model - 2.5.5 + 2.5.6-SNAPSHOT diff --git a/src/main/java/org/ndexbio/rest/services/v3/SearchServiceV3.java b/src/main/java/org/ndexbio/rest/services/v3/SearchServiceV3.java index 5eda0208..f6ecd16c 100644 --- a/src/main/java/org/ndexbio/rest/services/v3/SearchServiceV3.java +++ b/src/main/java/org/ndexbio/rest/services/v3/SearchServiceV3.java @@ -3,6 +3,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.net.URISyntaxException; import java.sql.SQLException; import java.util.ArrayList; @@ -34,15 +36,22 @@ import org.ndexbio.common.persistence.CX2NetworkLoader; import org.ndexbio.cx2.aspect.element.core.CxAttributeDeclaration; import org.ndexbio.cx2.aspect.element.core.CxEdge; +import org.ndexbio.cx2.aspect.element.core.CxMetadata; +import org.ndexbio.cx2.aspect.element.core.CxNode; import org.ndexbio.cx2.aspect.element.core.DeclarationEntry; import org.ndexbio.cxio.aspects.datamodels.EdgeAttributesElement; +import org.ndexbio.cxio.core.AspectIterator; +import org.ndexbio.cxio.util.CxConstants; import org.ndexbio.model.errorcodes.NDExError; import org.ndexbio.model.exceptions.BadRequestException; import org.ndexbio.model.exceptions.NdexException; +import org.ndexbio.model.exceptions.ObjectNotFoundException; import org.ndexbio.model.exceptions.UnauthorizedOperationException; +import org.ndexbio.model.network.query.CXObjectFilter; import org.ndexbio.model.network.query.FilterCriterion; import org.ndexbio.model.network.query.FilteredDirectQuery; import org.ndexbio.model.object.CXSimplePathQuery; +import org.ndexbio.model.object.network.NetworkSummary; import org.ndexbio.model.tools.EdgeFilter; import org.ndexbio.rest.Configuration; import org.ndexbio.rest.filters.BasicAuthenticationFilter; @@ -51,7 +60,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -62,6 +74,8 @@ public class SearchServiceV3 extends NdexService { static Logger accLogger = LoggerFactory.getLogger(BasicAuthenticationFilter.accessLoggerName); + static final HashMap EMPTYOBJECT = new HashMap<>(); + public SearchServiceV3(@Context HttpServletRequest httpRequest) { super(httpRequest); } @@ -291,6 +305,150 @@ public Response interconnectQuery( } + + @PermitAll + @POST + @Path("/networks/{networkId}/nodes") + @Produces("application/json") + + + public Response getNodeAttributes (@PathParam("networkId") final String networkIdStr, + @QueryParam("accesskey") String accessKey, + final CXObjectFilter filterObject) throws SQLException, IOException, NdexException { + + if(filterObject.getAttributeNames().size()==0) { + throw new BadRequestException("At least one attribute name is reqired in the 'attributeNames' field."); + } + + try (NetworkDAO dao = new NetworkDAO()) { + UUID userId = getLoggedInUserId(); + UUID networkId = UUID.fromString(networkIdStr); + if ( dao.isReadable(networkId, userId) || dao.accessKeyIsValid(networkId, accessKey)) { + List md = dao.getCx2MetaDataList(networkId); + + String pathPrefix = Configuration.getInstance().getNdexRoot() + "/data/" + networkIdStr + "/aspects_cx2/"; + + // get attribute declaration + CxAttributeDeclaration decl= null; + if (!md.stream().anyMatch(m -> m.getName().equals(CxNode.ASPECT_NAME))) + return Response.ok().type(MediaType.APPLICATION_JSON_TYPE) + .entity(CxConstants.EMPTYOBJECT).build(); + + + if (md.stream().anyMatch(m -> m.getName().equals(CxAttributeDeclaration.ASPECT_NAME))) { + try (AspectIterator ei = new AspectIterator<>( pathPrefix + CxAttributeDeclaration.ASPECT_NAME, CxAttributeDeclaration.class)) { + while (ei.hasNext()) { + decl = ei.next(); + break; + } + } + } + if ( decl == null) { + decl = new CxAttributeDeclaration(); + } + + //check if the attributes exists + Map nodeAttrDecl = decl.getAttributesInAspect(CxNode.ASPECT_NAME); + if ( nodeAttrDecl == null) + throw new ObjectNotFoundException("This network has no attributes on nodes."); + + for (String attrName : filterObject.getAttributeNames()) { + if (nodeAttrDecl.get(attrName)==null) + throw new ObjectNotFoundException("Node attribute '"+attrName+"' is not found in this network."); + } + + PipedInputStream in = new PipedInputStream(); + + PipedOutputStream out; + + try { + out = new PipedOutputStream(in); + } catch (IOException e) { + in.close(); + throw new NdexException("IOExcetion when creating the piped output stream: "+ e.getMessage()); + } + + new NodeAttrsFilterThread(out,filterObject, nodeAttrDecl, pathPrefix).start(); + return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(in).build(); + + } + + throw new UnauthorizedOperationException ("Unauthorized access to network " + networkId); + } + } + + protected class NodeAttrsFilterThread extends Thread { + + private PipedOutputStream out; + private Set ids; + private String pathPrefix; + private Set attrNames; + private Map decls; + + + public NodeAttrsFilterThread(PipedOutputStream out, + CXObjectFilter filterObject, Map nodeAttrDecl, + String pathPrefix) { + this.out = out; + ids = filterObject.getIds(); + this.attrNames = filterObject.getAttributeNames(); + this.pathPrefix= pathPrefix; + this.decls = nodeAttrDecl; + + } + + @Override + public void run() { + + int count = 0; + + JsonFactory factory = new JsonFactory(); + JsonGenerator generator; + try { + generator = factory.createGenerator(out); + generator.writeStartObject(); // Start the object + generator.setCodec(new ObjectMapper()); + + //iterate throw the node aspect and return the filtered result + try (AspectIterator ei = new AspectIterator<>( pathPrefix + CxNode.ASPECT_NAME, CxNode.class)) { + while (ei.hasNext()) { + if ( count == ids.size()) + break; + + CxNode n = ei.next(); + if ( ids.contains(n.getId())) { + Map attrs = new HashMap<>(); + for (String attrName : attrNames) { + attrs.put(attrName, n.getWelldoneAttributeValue(attrName, decls.get(attrName))); + } + generator.writeFieldName(n.getId().toString()); + generator.writeObject(attrs); + count ++; + } + } + generator.writeEndObject(); // End the object + generator.close(); + } catch (JsonProcessingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NdexException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + try { + out.flush(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + /* @PermitAll @POST From 3f50ffc37eb85b4444a3d755fed4d810b73c747b Mon Sep 17 00:00:00 2001 From: jingjingbic Date: Thu, 8 Feb 2024 17:20:42 -0800 Subject: [PATCH 2/4] Mint DOI immediately when user requests. (UD-2788). Also fixed a error message in the getUserByAccountName function. --- .../ndexbio/rest/services/AdminServiceV2.java | 109 +++++++++++++++++- .../ndexbio/rest/services/UserServiceV2.java | 2 +- 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java b/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java index 99d173da..5133d6eb 100644 --- a/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java +++ b/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java @@ -49,6 +49,7 @@ import javax.ws.rs.GET; import javax.ws.rs.POST; 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; @@ -61,13 +62,16 @@ import org.ndexbio.model.exceptions.BadRequestException; import org.ndexbio.model.exceptions.ForbiddenOperationException; import org.ndexbio.model.exceptions.NdexException; +import org.ndexbio.model.object.NdexPropertyValuePair; import org.ndexbio.model.object.NdexStatus; import org.ndexbio.model.object.User; import org.ndexbio.model.object.network.NetworkIndexLevel; +import org.ndexbio.model.object.network.NetworkSummary; import org.ndexbio.model.object.network.VisibilityType; import org.ndexbio.rest.Configuration; import org.ndexbio.rest.NdexHttpServletDispatcher; import org.ndexbio.rest.helpers.AmazonSESMailSender; +import org.ndexbio.rest.helpers.EZIDClient; import org.ndexbio.rest.helpers.Security; import org.ndexbio.rest.server.StandaloneServer; import org.ndexbio.task.NdexServerQueue; @@ -201,13 +205,13 @@ public void addRequest( if ( submitterEmail == null) throw new BadRequestException("contactEmail is missing in the request."); - String key = dao.requestDOI(networkId, isCertified); + dao.requestDOI(networkId, isCertified); dao.setFlag(networkId, "iscomplete", false); dao.commit(); NdexServerQueue.INSTANCE.addSystemTask(new SolrTaskRebuildNetworkIdx(networkId,SolrIndexScope.global,false,null, NetworkIndexLevel.ALL,false)); - String name = dao.getNetworkName(networkId); + /* String name = dao.getNetworkName(networkId); String url = Configuration.getInstance().getHostURI() + "/viewer/networks/"+ networkId ; if ( dao.getNetworkVisibility(networkId) == VisibilityType.PRIVATE) @@ -216,8 +220,10 @@ public void addRequest( String creationURL = Configuration.getInstance().getHostURI() + "/v3/networks/" + networkId + "/DOI?key=" + URLEncoder.encode(Security.encrypt(networkId.toString()),StandardCharsets.UTF_8.toString()) +"&email=" + - URLEncoder.encode(submitterEmail, StandardCharsets.UTF_8.toString()); + URLEncoder.encode(submitterEmail, StandardCharsets.UTF_8.toString());*/ + mintDOI(networkId, submitterEmail); + /* //Reading in the email template String emailTemplate = Util.readFile(Configuration.getInstance().getNdexRoot() + "/conf/Server_notification_email_template.html"); @@ -238,7 +244,7 @@ public void addRequest( Matcher.quoteReplacement(messageBody)) ; AmazonSESMailSender.getInstance().sendEmail(adminEmailAddress, - htmlEmail, "DOI request on NDEx Network", "html"); + htmlEmail, "DOI request on NDEx Network", "html");*/ } break; } @@ -337,5 +343,100 @@ protected static int getClassCount(Connection db,String className) throws SQLExc } + + private static String mintDOI( + UUID networkUUID, + String submitter + ) throws Exception { + + // String uuidFromKey = Security.decrypt(key,Configuration.getInstance().getSecretKeySpec()); + String submitterEmail = submitter; + + /*if( !networkId.equals(uuidFromKey)) + throw new BadRequestException("Invalid key in the URL."); */ + + // UUID networkUUID = UUID.fromString(networkId); + + try (NetworkDAO dao = new NetworkDAO() ) { + String currentDOI = dao.getNetworkDOI(networkUUID); + if ( currentDOI ==null || !currentDOI.equals(NetworkDAO.PENDING)) { + throw new ForbiddenOperationException("This operation only works when a DOI is pending. The current value of DOI is: " + currentDOI ); + } + dao.setDOI(networkUUID, "CREATING"); + dao.commit(); + + NetworkSummary s = dao.getNetworkSummaryById(networkUUID); + + String author = null; + for (NdexPropertyValuePair p : s.getProperties() ) { + if ( p.getPredicateString().equals("author")) + author = p.getValue(); + + } + if ( author == null) { + dao.setDOI(networkUUID, NetworkDAO.PENDING); + dao.commit(); + throw new NdexException("Property author is missing in the network."); + } + + String url = Configuration.getInstance().getHostURI() + "/viewer/networks/"+ networkUUID.toString(); + + if ( dao.getNetworkVisibility(networkUUID) == VisibilityType.PRIVATE) { + url += "?accesskey=" + dao.getNetworkAccessKey(networkUUID); + } + + String id; + try { + id = EZIDClient.createDOI( + url , + author, s.getName(), + Configuration.getInstance().getDOIPrefix(), + Configuration.getInstance().getDOIUser(), + Configuration.getInstance().getDOIPswd()); + } catch (Exception e) { + dao.setDOI(networkUUID, NetworkDAO.PENDING); + List warnings = dao.getWarnings(networkUUID); + if (warnings == null) + warnings = new ArrayList<>(); + warnings.add("Failed to create DOI in EZID site. Cause: " + e.getMessage()); + dao.setWarning(networkUUID, warnings); + dao.commit(); + e.printStackTrace(); + throw new NdexException("Failed to create DOI in EZID site. Cause: " + e.getMessage()); + + } + + dao.setDOI(networkUUID, id); + dao.commit(); + + //Send confirmation to submitter and admin + + //Reading in the email template + String emailTemplate = Util.readFile(Configuration.getInstance().getNdexRoot() + "/conf/Server_notification_email_template.html"); + String adminEmailAddress = Configuration.getInstance().getProperty("NdexSystemUserEmail"); + + String messageBody = "Dear NDEx user " + s.getOwner() + ",

" + + "Your DOI request for the network
" + + s.getName() + "(" + networkUUID.toString() + ")
" + + "has been processed.

" + + "You digital Object Identifier (DOI) is:
" + + id + "

" + + "Your identifier's URL form is:
" + + "https://doi.org/" + id + "

" + + "Please be advised that it can take several hours before your new DOI becomes resolvable."; + + + String htmlEmail = emailTemplate.replaceFirst("%%____%%", + Matcher.quoteReplacement(messageBody)) ; + + AmazonSESMailSender.getInstance().sendEmail(submitterEmail, + htmlEmail, "A DOI has been created for your NDEx Network", "html",adminEmailAddress); + + + return "DOI " + id +" has been created on this network. Confirmation emails have been sent."; + } + + } + } diff --git a/src/main/java/org/ndexbio/rest/services/UserServiceV2.java b/src/main/java/org/ndexbio/rest/services/UserServiceV2.java index 1bf50a61..b6456457 100644 --- a/src/main/java/org/ndexbio/rest/services/UserServiceV2.java +++ b/src/main/java/org/ndexbio/rest/services/UserServiceV2.java @@ -333,7 +333,7 @@ public User getUserByAccountNameOrAuthenticatUser( if ( booleanStr!=null) { if ( !booleanStr.toLowerCase().equals("true")) - throw new IllegalArgumentException("Paramber valid can only be true."); + throw new IllegalArgumentException("The 'valid' parameter must be set to 'true'."); return authenticateUser(); } From 9d1efe2b2081d792cffc64d49d6c4627190a4066 Mon Sep 17 00:00:00 2001 From: jingjingbic Date: Fri, 9 Feb 2024 09:35:24 -0800 Subject: [PATCH 3/4] Minor cleanup. UD-2788 --- src/main/java/org/ndexbio/rest/services/AdminServiceV2.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java b/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java index 5133d6eb..858acb80 100644 --- a/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java +++ b/src/main/java/org/ndexbio/rest/services/AdminServiceV2.java @@ -30,8 +30,6 @@ */ package org.ndexbio.rest.services; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -49,7 +47,6 @@ import javax.ws.rs.GET; import javax.ws.rs.POST; 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; @@ -72,7 +69,6 @@ import org.ndexbio.rest.NdexHttpServletDispatcher; import org.ndexbio.rest.helpers.AmazonSESMailSender; import org.ndexbio.rest.helpers.EZIDClient; -import org.ndexbio.rest.helpers.Security; import org.ndexbio.rest.server.StandaloneServer; import org.ndexbio.task.NdexServerQueue; import org.ndexbio.task.SolrIndexScope; @@ -299,6 +295,7 @@ public void addRequest( * 1) add support for Tomcat * 2) only allow privileged users to shut down Tomcat. */ + @SuppressWarnings("static-method") @GET @PermitAll @NdexOpenFunction From 20d8911f01edb0d3152cc1f700db9e66c0de7acc Mon Sep 17 00:00:00 2001 From: jingjingbic Date: Fri, 9 Feb 2024 11:11:12 -0800 Subject: [PATCH 4/4] remove snapshot from version and prepare for the release. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f8780ecc..0dcff502 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.ndexbio ndexbio-rest war - 2.5.6-SNAPSHOT + 2.5.6 ndexbio-rest http://maven.apache.org 2013 @@ -20,7 +20,7 @@ org.ndexbio ndex-object-model - 2.5.6-SNAPSHOT + 2.5.6