diff --git a/pom.xml b/pom.xml index 1354c1cfd200ddff0a2d756f073c416bee6548de..388638de5f451debdd2ea2e190e44f38bf67909c 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.0.2.Final</version> + <scope>provided</scope> </dependency> <dependency> <groupId>javax.persistence</groupId> @@ -40,6 +41,33 @@ <version>1.1</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.reflections</groupId> + <artifactId>reflections</artifactId> + <version>0.9.9-RC1</version> + </dependency> + <dependency> + <groupId>no.bioforsk.vips.common</groupId> + <artifactId>VIPSCommon</artifactId> + <version>1.0-SNAPSHOT</version> + <!--scope>provided</scope--> + </dependency> + <dependency> + <groupId>no.bioforsk.vips.</groupId> + <artifactId>TestModel</artifactId> + <version>1.0-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>com.thetransactioncompany</groupId> + <artifactId>cors-filter</artifactId> + <version>1.7.1</version> + </dependency> + <dependency> + <groupId>javax</groupId> + <artifactId>javaee-web-api</artifactId> + <version>6.0</version> + <type>jar</type> + </dependency> </dependencies> <build> @@ -63,7 +91,7 @@ <name>copyright</name> <!-- copyright tag for classes and interfaces --> <placement>t</placement> - <head>©</head> + <head>© Copyright</head> </tag> </tags> </configuration> diff --git a/src/main/java/no/bioforsk/vips/core/VIPSCoreApplication.java b/src/main/java/no/bioforsk/vips/core/VIPSCoreApplication.java index 63a9d6f2e1722948b6c3fc7e579ced1e4ecf6a0d..4844af80079222b5536a13e25a3eb2dc68a11d39 100644 --- a/src/main/java/no/bioforsk/vips/core/VIPSCoreApplication.java +++ b/src/main/java/no/bioforsk/vips/core/VIPSCoreApplication.java @@ -4,6 +4,11 @@ import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; +/** + * Responsible for adding REST resources + * @copyright 2013 {@link http://www.bioforsk.no Bioforsk} + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ @ApplicationPath("/") public class VIPSCoreApplication extends Application { @@ -20,6 +25,8 @@ public class VIPSCoreApplication extends Application * given list with all resources defined in the project. */ private void addRestResourceClasses(Set<Class<?>> resources) { - resources.add(no.bioforsk.vips.core.service.TestResource.class); + resources.add(no.bioforsk.vips.core.service.ModelResource.class); + + } } \ No newline at end of file diff --git a/src/main/java/no/bioforsk/vips/core/entities/Weather.java b/src/main/java/no/bioforsk/vips/core/entities/Weather.java deleted file mode 100644 index caf30414afb9d607af628b569442ccde9487bcf9..0000000000000000000000000000000000000000 --- a/src/main/java/no/bioforsk/vips/core/entities/Weather.java +++ /dev/null @@ -1,61 +0,0 @@ -package no.bioforsk.vips.core.entities; - -import java.util.Date; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * - * @author treinar - */ -//@XmlRootElement(name="weather") -//@XmlAccessorType(XmlAccessType.FIELD) -public class Weather { - - public Weather() - { - - } - - public Weather(Date date, String details){ - this.setDate(date); - this.setDetails(details); - } - - //@XmlAttribute - private Date date; - - //@XmlElement - private String details; - - /** - * @return the date - */ - public Date getDate() { - return date; - } - - /** - * @param date the date to set - */ - public void setDate(Date date) { - this.date = date; - } - - /** - * @return the details - */ - public String getDetails() { - return details; - } - - /** - * @param details the details to set - */ - public void setDetails(String details) { - this.details = details; - } -} diff --git a/src/main/java/no/bioforsk/vips/core/service/ModelResource.java b/src/main/java/no/bioforsk/vips/core/service/ModelResource.java new file mode 100644 index 0000000000000000000000000000000000000000..e36d4bd9a006df8c9851428d7b0cfe7290d00080 --- /dev/null +++ b/src/main/java/no/bioforsk/vips/core/service/ModelResource.java @@ -0,0 +1,122 @@ +package no.bioforsk.vips.core.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import javax.ws.rs.Consumes; +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.core.Response; +import no.bioforsk.vips.entity.ModelConfiguration; +import no.bioforsk.vips.entity.Result; +import no.bioforsk.vips.entity.ResultImpl; +import no.bioforsk.vips.entity.Weather; +import no.bioforsk.vips.model.Model; +import no.bioforsk.vips.model.factory.ModelFactory; + +/** + * The available resources in this system + * @copyright 2013 {@link http://www.bioforsk.no Bioforsk} + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ +@Path("") +public class ModelResource { + + /** + * Lists all models available in this instance + * @return + */ + @GET + @Path("models") + @Produces("text/plain;charset=UTF-8") + public Response printModels() + { + StringBuilder retVal = new StringBuilder(); + ModelFactory mF = ModelFactory.getInstance(); + for(String key : mF.getModelList().keySet()) + { + retVal.append(key).append("\n"); + } + return Response.ok().entity(retVal.toString()).build(); + } + + /** + * Prints the usage instructions from the requested model + * @param modelId + * @return + */ + @GET + @Path("models/{modelId}/usage") + @Produces("text/plain;charset=UTF-8") + public Response printModelUsage(@PathParam("modelId") String modelId) + { + String usage = ModelFactory.getInstance().getModelUsage(modelId); + return Response.ok().entity(usage).build(); + } + + /** Tests for XML and JSON output */ + @GET + @Path("models/resulttest") + @Produces("application/json") + public Response printResultTest() + { + List<Result> allResults = new ArrayList(); + Result retVal = new ResultImpl(); + retVal.setResultValidTime(new Date()); + retVal.setValue("Parameter1", "Verdi1"); + retVal.setValue("Parameter2", "Verdi2"); + allResults.add(retVal); + retVal = new ResultImpl(); + retVal.setResultValidTime(new Date()); + retVal.setValue("Parameter1", "Verdi5"); + retVal.setValue("Parameter2", "Verdi7"); + allResults.add(retVal); + return Response.ok().entity(allResults).build(); + } + + @GET + @Path("weather/xml") + @Produces("application/xml") + public Response printWeatherXML() + { + return Response.ok().entity(new Weather(new Date(),"Goddag, goddag. Dette her er Meteorologisk institutt.æøåæøå")).build(); + } + + /** + * Marshalling fra POJO til JSON gjøres automagisk (med Jackson, tror jeg) + * @return gladmelding + */ + @GET + @Path("weather/json") + @Produces("application/json") + public Response printWeatherJSON() + { + return Response.ok().entity(new Weather(new Date(),"Goddag, goddag. Dette her er Meteorologisk institutt.æøåæøå")).build(); + } + + /** + * Test for input / POST + */ + + @POST + @Path("models/run") + @Consumes("application/json") + @Produces("application/json") + //@Produces("text/plain;charset=UTF-8") + public Response runModel(ModelConfiguration config) + { + //return Response.ok().entity("runModell called").build(); + try + { + Model calledModel = ModelFactory.getInstance().getModelInstance(config.getModelId()); + return Response.ok().entity(calledModel.getResult()).build(); + } + catch(InstantiationException | IllegalAccessException iae) + { + return Response.serverError().build(); + } + } +} diff --git a/src/main/java/no/bioforsk/vips/core/service/TestResource.java b/src/main/java/no/bioforsk/vips/core/service/TestResource.java deleted file mode 100644 index 6d4cbf95a54f20d6a30f4cc1f1bc9afe797e2666..0000000000000000000000000000000000000000 --- a/src/main/java/no/bioforsk/vips/core/service/TestResource.java +++ /dev/null @@ -1,45 +0,0 @@ -package no.bioforsk.vips.core.service; - -import java.util.Date; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; -import no.bioforsk.vips.core.entities.Weather; - -/** - * - * @copyright Bioforsk.no - * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> - */ -@Path("test") -public class TestResource { - @GET - @Path("Hello/{name}") - public Response printMessageHolihili(@PathParam("name") String name){ - return Response.ok().entity("Neimen heisann, " + name).build(); - }; - - @GET - @Path("weather/xml") - @Produces("application/xml") - public Weather printWeatherXML() - { - return new Weather(new Date(),"Goddag, goddag. Dette her er Meteorologisk institutt."); - } - - /** - * Marshalling fra POJO til JSON gjøres automagisk (med Jackson, tror jeg) - * @return gladmelding - */ - @GET - @Path("weather/json") - @Produces("application/json") - public Response printWeatherJSON() - { - return Response.ok().entity(new Weather(new Date(),"Goddag, goddag. Dette her er Meteorologisk institutt.")).build(); - } - - -} diff --git a/src/main/java/no/bioforsk/vips/core/startup/StartupListener.java b/src/main/java/no/bioforsk/vips/core/startup/StartupListener.java new file mode 100644 index 0000000000000000000000000000000000000000..6037a8ec45e807cc2667dd5df61368a5545cef98 --- /dev/null +++ b/src/main/java/no/bioforsk/vips/core/startup/StartupListener.java @@ -0,0 +1,36 @@ +package no.bioforsk.vips.core.startup; + +import javax.servlet.ServletContextEvent; + +import no.bioforsk.vips.model.factory.ModelFactory; +/** + * This class is created and method contextInitialized called when the application + * is deployed (either at startup of application container or when redeploying the app) + * + * @copyright 2013 {@link http://www.bioforsk.no Bioforsk} + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ +public class StartupListener implements javax.servlet.ServletContextListener{ + + /** + * Called when the application + * is deployed (either at startup of application container or when redeploying the app) + * @param sce + */ + @Override + public void contextInitialized(ServletContextEvent sce) { + System.out.println("VIPSCore system initializing"); + // We create the singleton instance of ModelFactory at startup, as this + // takes a few seconds (due to scanning of file system) + ModelFactory singletonFactory = ModelFactory.getInstance(); + } + + /** + * Called when the web app is closed + * @param sce + */ + @Override + public void contextDestroyed(ServletContextEvent sce) { + System.out.println("VIPSCore system shutting down"); + } +} diff --git a/src/main/java/no/bioforsk/vips/model/factory/ModelFactory.java b/src/main/java/no/bioforsk/vips/model/factory/ModelFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7f2b15ae79c5d4753781d4a97aef0b4b0ef23cdd --- /dev/null +++ b/src/main/java/no/bioforsk/vips/model/factory/ModelFactory.java @@ -0,0 +1,175 @@ +package no.bioforsk.vips.model.factory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.reflections.Reflections; +import no.bioforsk.vips.model.Model; + +/** + * Provides the models available. Singleton. + * + * <h2>Deploying models</h2> + * To make a model available, it must + * <ul> + * <li>Implement the {@link no.bioforsk.vips.model.Model} interface</li> + * <li> + * Be on the class path of the running application, preferably in a separate JAR. + * The guaranteed way to make it work is to put it inside the VIPSCore WAR-file, in /WEB-INF/lib + * This could be accomplished most easily by using a ZIP application, or accessing + * The VIPSCore source and building it with the JAR(s) as (a) dependenc(y/ies) + * </li> + * </ul> + * + * @copyright 2013 {@link http://www.bioforsk.no Bioforsk} + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ +public class ModelFactory { + // The singleton instance + private static ModelFactory modelFactory; + // Internal collection of models found from scanning the class path + private Map<String,Model> models; + // Internal list of model IDs and names + private Map<String,String> modelList; + + /** + * Declared private to protect from unauthorized instatiation. Part of the + * Singleton design pattern. + */ + private ModelFactory() + { + this.init(); + } + + /** + * Overriding {@link java.lang.Object}'s clone() method to prevent cloning. + * Part of the Singleton design pattern. + * @return + * @throws CloneNotSupportedException + */ + @Override + public Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + /** + * Use this method to get the singleton instance of the class + * @return an instance (the only one in the JVM) of ModelFactory + */ + public static synchronized ModelFactory getInstance(){ + if(modelFactory == null) + { + modelFactory = new ModelFactory(); + } + return modelFactory; + } + + /** + * Scans the whole class path for classes implementing the Model interface. + * Builds an inventory. + */ + private void init(){ + /* + * Reflections API insists on using at least one package prefix. So we scan + * through all TLDs. List is compiled from + * http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains + */ + String[] defaultTopDomains = { + "aero","asia","biz","cat","com","coop","info","int","jobs","mobi", + "museum","name","net","org","post","pro","tel","travel","edu","gov", + "ac","ad","ae","af","ag","ai","al","am","an","ao","aq","ar","as","at", + "au","aw","ax","az","ba","bb","bd","be","bf","bg","bh","bi","bj","bm", + "bn","bo","br","bs","bt","bv","bw","by","bz","ca","cc","cd","cf","cg", + "ch","ci","ck","cl","cm","cn","co","cr","cs","cu","cv","cx","cy","cz", + "dd","de","dj","dk","dm","do","dz","ec","ee","eg","eh","er","es","et", + "eu","fi","fj","fk","fm","fo","fr","ga","gb","gd","ge","gf","gg","gh", + "gi","gl","gm","gn","gp","gq","gr","gs","gt","gu","gw","gy","hk","hm", + "hn","hr","ht","hu","id","ie","il","im","in","io","iq","ir","is","it", + "je","jm","jo","jp","ke","kg","kh","ki","km","kn","kp","kr","kw","ky", + "kz","la","lb","lc","li","lk","lr","ls","lt","lu","lv","ly","ma","mc", + "md","me","mg","mh","mk","ml","mm","mn","mo","mp","mq","mr","ms","mt", + "mu","mv","mw","mx","my","mz","na","nc","ne","nf","ng","ni","nl","no", + "np","nr","nu","nz","om","pa","pe","pf","pg","ph","pk","pl","pm","pn", + "pr","ps","pt","pw","py","qa","re","ro","rs","ru","rw","sa","sb","sc", + "sd","se","sg","sh","si","sj","sk","sl","sm","sn","so","sr","ss","st", + "su","sv","sx","sy","sz","tc","td","tf","tg","th","tj","tk","tl","tm", + "tn","to","tp","tr","tt","tv","tw","tz","ua","ug","uk","us","uy","uz", + "va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt","yu","za","zm", + "zw", + }; + + /** + * Warning: This generates a WARNING from a filesystem/JARfile scan error + * for each TLD not found. Use application container's config to suppress + * this in log files. + */ + Reflections reflections; + Set<Class<? extends Model>> subTypes = new HashSet(); + for(String topDomain: defaultTopDomains) + { + reflections = new Reflections(topDomain); + subTypes.addAll(reflections.getSubTypesOf(Model.class)); + } + + /** + * Iterates and instantiates one object for each class + */ + this.models = new HashMap(); + for(Class<? extends Model> subType : subTypes) + { + try { + + Model model = subType.newInstance(); + models.put(model.getModelId().toString(), model); + //System.out.println("Model " + model.getModelName() + " with id=" + model.getModelId().toString() + " was found"); + } catch ( InstantiationException | IllegalAccessException ex) { + Logger.getLogger(ModelFactory.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + /** + * TODO: Add support for locale + * @return a list of all the available models in this JVM + */ + public synchronized Map<String,String> getModelList() + { + if(this.modelList == null) + { + this.modelList = new HashMap(); + for(Model model:this.models.values()) + { + this.modelList.put(model.getModelId().toString(), model.getModelName()); + } + } + return this.modelList; + } + + /** + * TODO: Add support for locale + * @param modelId the id of the Model + * @return usage instructions for the requested Model + * + */ + public String getModelUsage(String modelId) + { + return this.models.get(modelId).getModelUsage(); + } + + /** + * + * @param modelId the id of the Model + * @return a new instance of the model with the given Id + * @throws InstantiationException + * @throws IllegalAccessException + */ + public Model getModelInstance(String modelId) throws InstantiationException, IllegalAccessException + { + return this.models.get(modelId).getClass().newInstance(); + } + + +} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 1a86b769dec62a5d65c92ba15888d959a3187e5c..a45512ff83debe0f5ff5c712fe7a655446c49b46 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -1,4 +1,24 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> - +<?xml version="1.0" encoding="UTF-8"?> +<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> + <listener> + <listener-class>no.bioforsk.vips.core.startup.StartupListener</listener-class> + </listener> + <!-- This is for allowing Cross-Site requests --> + <filter> + <filter-name>CORS</filter-name> + <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> + <init-param> + <param-name>cors.supportedMethods</param-name> + <param-value>GET, POST, HEAD, PUT, DELETE, OPTIONS</param-value> + </init-param> + <init-param> + <!-- fix for http://code.google.com/p/chromium/issues/detail?id=108394 --> + <param-name>cors.supportedHeaders</param-name> + <param-value>Origin, Content-type, Accept</param-value> + </init-param> + </filter> + <filter-mapping> + <filter-name>CORS</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> </web-app>