package uk.ac.ebi.fg.myequivalents.webservices.server; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.List; import java.util.Set; import javax.servlet.ServletContext; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; 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 javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.ebi.fg.myequivalents.managers.impl.db.DbManagerFactory; import uk.ac.ebi.fg.myequivalents.managers.interfaces.EntityMappingManager; import uk.ac.ebi.fg.myequivalents.managers.interfaces.EntityMappingSearchResult; import uk.ac.ebi.fg.myequivalents.managers.interfaces.EntityMappingSearchResult.Bundle; import uk.ac.ebi.fg.myequivalents.managers.interfaces.ManagerFactory; import uk.ac.ebi.fg.myequivalents.model.Entity; import uk.ac.ebi.fg.myequivalents.resources.Resources; import uk.ac.ebi.fg.myequivalents.utils.jaxb.JAXBUtils; /** * <p>The web service version of the {@link EntityMappingManager} interface. This uses Jersey and set up a REST web service * See {@link uk.ac.ebi.fg.myequivalents.webservices.client.EntityMappingWSClientTest} for usage examples.</p> * * <p>The web services are backed by a {@link ManagerFactory}, which needs to be configured via Spring, see {@link Resources}. * By default {@link DbManagerFactory} is used.</p> * * <p>Usually these services are located at /ws/mapping, e.g., * "https://localhost:8080/ws/mapping/get?entityId=service1:acc1". You can build the path by appending the value in * @Path (which annotates every service method) to /mapping.</p> * * <p>TODO: We need proper exception handling, see <a href = "http://jersey.java.net/documentation/latest/user-guide.html#https://jersey.java.net/documentation/latest/user-guide.html#d0e6592">here</a>.</p> * * <dl><dt>date</dt><dd>Sep 11, 2012</dd></dl> * @author Marco Brandizi * */ @Path ( "/mapping" ) public class EntityMappingWebService { @Context private ServletContext servletContext; protected final Logger log = LoggerFactory.getLogger ( this.getClass () ); /** * We need this version of {@link EntityMappingManager#getMappings(Boolean, String...)} because Jersey/JAX-WS doesn't like arrays. */ @POST @Path( "/get" ) @Produces ( MediaType.APPLICATION_XML ) public EntityMappingSearchResult getMappings ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "raw" ) Boolean isRaw, @FormParam ( "entity" ) List<String> entityIds ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); EntityMappingSearchResult result = emgr.getMappings ( isRaw, entityIds.toArray ( new String [ 0 ] ) ); emgr.close(); return result; } /** * This is just the HTTP/GET version of {@link #getMappings(Boolean, List)}. Normally you will want the * POST invocation, this is here for testing purposes only. */ @GET @Path( "/get" ) @Produces ( MediaType.APPLICATION_XML ) public EntityMappingSearchResult getMappingsViaGET ( @QueryParam ( "login" ) String email, @QueryParam ( "login-secret" ) String apiPassword, @QueryParam ( "raw" ) Boolean isRaw, @QueryParam ( "entity" ) List<String> entityIds ) { return getMappings ( email, apiPassword, isRaw, entityIds ); } /** * This is the equivalent of {@link EntityMappingManager#storeMappings(String...)}. */ @POST @Path( "/store" ) @Produces ( MediaType.APPLICATION_XML ) public void storeMappings ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "entity" ) List<String> entityIds ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); emgr.storeMappings ( entityIds.toArray ( new String [ 0 ]) ); emgr.close(); } /** * This is the equivalent of {@link EntityMappingManager#storeMappingBundle(String...)}. */ @POST @Path( "/bundle/store" ) @Produces ( MediaType.APPLICATION_XML ) public void storeMappingBundle ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "entity" ) List<String> entityIds ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); emgr.storeMappingBundle ( entityIds.toArray ( new String [ 0 ]) ); emgr.close(); } /** * This is the equivalent of {@link EntityMappingManager#storeMappingBundles(EntityMappingSearchResult)}. */ @POST @Path( "/bundles/store" ) @Produces ( MediaType.APPLICATION_XML ) public void storeMappingBundles ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "mappings-xml" ) String mappingsXml ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); emgr.storeMappingBundlesFromXML ( new StringReader ( mappingsXml ) ); emgr.close(); } /** * This is the equivalent of {@link EntityMappingManager#deleteMappings(String...)}. * @return an integer in the form of a string, cause Jersey doesn't like other types very much. */ @POST @Path( "/delete" ) @Produces ( MediaType.APPLICATION_XML ) public String deleteMappings ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "entity" ) List<String> entityIds ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); int result = emgr.deleteMappings ( entityIds.toArray ( new String [ 0 ]) ); emgr.close (); return String.valueOf ( result ); } /** * This is the equivalent of {@link EntityMappingManager#deleteEntities(String...)}. * @return an integer in the form of a string, cause Jersey doesn't like other types very much. */ @POST @Path( "/entity/delete" ) @Produces ( MediaType.APPLICATION_XML ) public String deleteEntities ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "entity" ) List<String> entityIds ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); int result = emgr.deleteEntities ( entityIds.toArray ( new String [ 0 ]) ); emgr.close (); return String.valueOf ( result ); } /** * This is the equivalent of {@link EntityMappingManager#getMappingsForTarget(Boolean, String, String)}. */ @POST @Path( "/target/get" ) @Produces ( MediaType.APPLICATION_XML ) public EntityMappingSearchResult getMappingsForTarget ( @FormParam ( "login" ) String email, @FormParam ( "login-secret" ) String apiPassword, @FormParam ( "raw" ) Boolean wantRawResult, @FormParam ( "service" ) String targetServiceName, @FormParam ( "entity" ) String entityId ) { EntityMappingManager emgr = getEntityMappingManager ( email, apiPassword ); EntityMappingSearchResult result = emgr.getMappingsForTarget ( wantRawResult, targetServiceName, entityId ); emgr.close(); return result; } /** * This is just the HTTP/GET version of {@link #getMappingsForTarget(Boolean, String, String)}. Normally you will want the * POST invocation, this is here for testing purposes only. */ @GET @Path( "/target/get" ) @Produces ( MediaType.APPLICATION_XML ) public EntityMappingSearchResult getMappingsForTargetViaGET ( @QueryParam ( "login" ) String email, @QueryParam ( "login-secret" ) String apiPassword, @QueryParam ( "raw" ) Boolean wantRawResult, @QueryParam ( "service" ) String targetServiceName, @QueryParam ( "entity" ) String entityId ) { return getMappingsForTarget ( email, apiPassword, wantRawResult, targetServiceName, entityId ); } /** * <p>This returns the result from {@link #getMappingsForTarget(Boolean, String, String)} in the format of * an HTTP 301 response, so the browser is re-directed to the result's URI {@link Entity#getURI()}. In case * the result is not unique, some XML is returned, that is similar to the XML returned by * {@link #getMappingsAs(String, Boolean, String...) getMappingsAs ( "xml", ... )}. You can couple such XML * with the URL of an XSL, using the xsl/xslUri parameter. The default for such xsl is /go-to-target/multiple-results.xsl. * See the myEquivalents wiki for details. If the input has no mapping, then HTTP/404 (Not Found) is returned</p> * * <p>You can test this with the examples (after 'mvn jetty:run'): * <ul> * <li><a href = "http://localhost:8080/ws/mapping/target/go-to?entity=test.testweb.service8:acc10&service=test.testweb.service7">common case</a></li> * <li><a href = "http://localhost:8080/ws/mapping/target/go-to?entity=test.testweb.service7:acc1&service=test.testweb.service6">multiple entities on the target</a></li> * <li><a href = "http://localhost:8080/ws/mapping/target/go-to?entity=foo-service:foo-acc&service=foo-target">non-existing target</a></li> * </ul> * </p> */ @GET @Path( "/target/go-to" ) @Produces ( MediaType.TEXT_XML ) public Response getMappingsForTargetRedirection ( @QueryParam ( "login" ) String email, @QueryParam ( "login-secret" ) String apiPassword, @QueryParam ( "service" ) String targetServiceName, @QueryParam ( "entity" ) String entityId, @QueryParam ( "xsl" ) String xslUri ) throws URISyntaxException { EntityMappingSearchResult result = getMappingsForTarget ( email, apiPassword, false, targetServiceName, entityId ); Collection<Bundle> bundles = result.getBundles (); if ( bundles.size () == 0 ) return Response.status ( Status.NOT_FOUND ).build (); if ( bundles.size () > 1 ) throw new RuntimeException ( "Internal error while getting mappings for '" + entityId + "' to '" + targetServiceName + ": there is more than a bundle as result and that's impossible, must be some bug, sorry." ); Bundle bundle = bundles.iterator ().next (); Set<Entity> entities = bundle.getEntities (); if ( entities.size () == 1 ) return Response.status ( Status.MOVED_PERMANENTLY ).location ( new URI ( entities.iterator ().next ().getURI () ) ).build (); if ( xslUri == null ) xslUri = UriBuilder.fromPath ( servletContext.getContextPath () + "/go-to-target/multiple-results.xsl" ).build ().toASCIIString (); else xslUri = new URI ( xslUri ).toASCIIString (); String resultXml = JAXBUtils.marshal ( result, EntityMappingSearchResult.class ); // Inject the XSL pointer into the XML being returned, this will usually make the browser to automatically apply // the XSL rendering. resultXml = resultXml.replaceFirst ( "<\\?xml(.*)\\?>", "<?xml$1 ?>\n" + "<?xml-stylesheet type='text/xsl' href='" + xslUri + "' ?>\n" ); // Turn the result XML into a single-result syntax resultXml = resultXml.replaceFirst ( "<mappings>", String.format ( "<mappings entity-id = '%s' target-service-name = '%s'>\n", entityId, targetServiceName ) ); return Response.ok ( resultXml, MediaType.TEXT_XML ).build (); } /** * Gets the {@link EntityMappingManager} that is used internally in this web service. This is obtained from * {@link Resources} and hence it depends on the Spring configuration, accessed through {@link WebInitializer}. * * TODO: AOP * */ private EntityMappingManager getEntityMappingManager ( String email, String apiPassword ) { log.trace ( "Returning mapping manager for the user {}, {}", email, apiPassword == null ? null : "***" ); return Resources.getInstance ().getMyEqManagerFactory ().newEntityMappingManager ( email, apiPassword ); } }