/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.apache.stanbol.rules.web.resources; import static javax.ws.rs.core.MediaType.TEXT_HTML; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; //import static org.apache.stanbol.commons.web.base.CorsHelper.addCORSOrigin; //import static org.apache.stanbol.commons.web.base.CorsHelper.enableCORS; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.List; //import javax.servlet.ServletContext; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.OPTIONS; 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.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import org.apache.clerezza.jaxrs.utils.form.MultiPartBody; import org.apache.clerezza.commons.rdf.IRI; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.stanbol.commons.web.viewable.Viewable; //import org.apache.stanbol.commons.web.base.ContextHelper; import org.apache.stanbol.commons.web.base.format.KRFormat; import org.apache.stanbol.commons.web.base.resource.BaseStanbolResource; import org.apache.stanbol.rules.base.api.AlreadyExistingRecipeException; import org.apache.stanbol.rules.base.api.NoSuchRecipeException; import org.apache.stanbol.rules.base.api.NoSuchRuleInRecipeException; import org.apache.stanbol.rules.base.api.Recipe; import org.apache.stanbol.rules.base.api.RecipeConstructionException; import org.apache.stanbol.rules.base.api.RecipeEliminationException; import org.apache.stanbol.rules.base.api.Rule; import org.apache.stanbol.rules.base.api.RuleAdapter; import org.apache.stanbol.rules.base.api.RuleAdapterManager; import org.apache.stanbol.rules.base.api.RuleAtomCallExeption; import org.apache.stanbol.rules.base.api.RuleStore; import org.apache.stanbol.rules.base.api.UnavailableRuleObjectException; import org.apache.stanbol.rules.base.api.UnsupportedTypeForExportException; import org.apache.stanbol.rules.base.api.util.RecipeList; import org.apache.stanbol.rules.base.api.util.RuleList; import org.apache.stanbol.rules.manager.RecipeImpl; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //import com.sun.jersey.api.view.ImplicitProduces; //import com.sun.jersey.multipart.FormDataParam; /** * * @author elvio, anuzzolese * */ @Component @Service(Object.class) @Property(name="javax.ws.rs", boolValue=true) @Path("/rules") //@ImplicitProduces(MediaType.TEXT_HTML + ";qs=2") public class RulesResource extends BaseStanbolResource { private Logger log = LoggerFactory.getLogger(getClass()); @Reference private RuleStore ruleStore; @Reference private RuleAdapterManager adapterManager; /** * To get the RuleStoreImpl where are stored the rules and the recipes * * @param servletContext * {To get the context where the REST service is running.} */ // public RulesResource(@Context ServletContext servletContext) { // this.ruleStore = (RuleStore) ContextHelper.getServiceFromContext(RuleStore.class, servletContext); // this.adapterManager = (RuleAdapterManager) ContextHelper.getServiceFromContext( // RuleAdapterManager.class, servletContext); // } class RulesResourceResultData extends ResultData{ } @GET @Produces(TEXT_HTML) public Response get(@Context HttpHeaders headers) { ResponseBuilder responseBuilder = Response.ok(new Viewable("index", new RulesResourceResultData()), TEXT_HTML); // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } /** * It returns the list of recipes whose descriptions match the string passed in the description parameter.<br> * If some recipe matches the description passed a 200 code with the list of recipes is returned. * Otherwise a 404 status code is returned. * * @param description * {@link String} * @return <ul> * <li>200: The list of recipe matching the description provided is returned</li> * <li>404: No recipe matches the description provided</li> * </ul> */ @GET @Produces(value = {MediaType.APPLICATION_JSON, KRFormat.RDF_XML, KRFormat.TURTLE, KRFormat.OWL_XML, KRFormat.RDF_JSON, KRFormat.FUNCTIONAL_OWL, KRFormat.MANCHESTER_OWL, MediaType.TEXT_PLAIN}) @Path("/find/recipes") public Response findRecipes(@QueryParam("description") String description) { log.info("Searching for recipes with description like to {}.", description); RecipeList recipes = ruleStore.findRecipesByDescription(description); log.info("The recipe list is emplty? {} ", recipes.isEmpty()); if (recipes.isEmpty()) { return Response.status(Status.NOT_FOUND).build(); } return Response.ok(recipes).build(); } /** * It returns the list of rule whose names or descriptions match the string passed in the parameter.<br> * If the name parameter is not null the search will be executed on that parameter, otherwise it run on * the description parameter. If both are bound the search will be executed on both.<br/> * If some rule matches the description passed a 200 code with the list of recipes is returned. Otherwise * a 404 status code is returned. * * @param description * {@link String} * @return <ul> * <li>200: The list of recipe matching the description provided is returned</li> * <li>404: No recipe matches the description provided</li> * </ul> */ @GET @Produces(value = {MediaType.APPLICATION_JSON, KRFormat.RDF_XML, KRFormat.TURTLE, KRFormat.OWL_XML, KRFormat.RDF_JSON, KRFormat.FUNCTIONAL_OWL, KRFormat.MANCHESTER_OWL, MediaType.TEXT_PLAIN}) @Path("/find/rules") public Response findRules(@QueryParam("name") String name, @QueryParam("description") String description) { RuleList rules = new RuleList(); if (name != null && !name.isEmpty()) { rules.addAll(ruleStore.findRulesByName(name)); } else { rules.addAll(ruleStore.findRulesByDescription(description)); } if (rules.isEmpty()) { return Response.status(Status.NOT_FOUND).build(); } return Response.ok(rules).build(); } /** * Get a recipe from the rule base (that is the ontology that contains the rules and the recipe). <br/> * If the second parameter is not null then the method returns the rule in the recipe identified by that * parameter. <br/> * * curl -v -X GET http://localhost:8080/kres/rule/http * ://kres.iks-project.eu/ontology/meta/rmi.owl#ProvaParentRule * * @param uri * {A string contains the IRI full name of the rule.} * @return Return: <br/> * 200 The rule is retrieved (import declarations point to KReS Services) <br/> * 404 The rule does not exists in the manager <br/> * 500 Some error occurred * */ @GET @Path("/recipe/{recipe:.+}") @Produces(value = {KRFormat.RDF_XML, KRFormat.TURTLE, KRFormat.OWL_XML, KRFormat.RDF_JSON, KRFormat.FUNCTIONAL_OWL, KRFormat.MANCHESTER_OWL, MediaType.TEXT_PLAIN}) public Response getRule(@PathParam("recipe") String recipeID, @QueryParam("rule") String ruleID, @Context HttpHeaders headers) { Recipe recipe; Rule rule; ResponseBuilder responseBuilder; try { URI uri = new URI(recipeID); if(uri.getScheme() == null){ recipeID = "urn:" + recipeID; log.info("The recipe ID is a URI without scheme. The ID is set to " + recipeID); } recipe = ruleStore.getRecipe(new IRI(recipeID)); if (ruleID != null && !ruleID.isEmpty()) { rule = ruleStore.getRule(recipe, new IRI(ruleID)); RuleList ruleList = new RuleList(); ruleList.add(rule); recipe = new RecipeImpl(recipe.getRecipeID(), recipe.getRecipeDescription(), ruleList); } responseBuilder = Response.ok(recipe); } catch (NoSuchRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } catch (RecipeConstructionException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NO_CONTENT); } catch (NoSuchRuleInRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } catch (URISyntaxException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_ACCEPTABLE); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } @GET @Path("/recipe/{recipe:.+}") @Produces(value = {MediaType.TEXT_HTML}) public Response showRecipe(@PathParam("recipe") String recipeID, @QueryParam("rule") String ruleID, @Context HttpHeaders headers) { Recipe recipe; Rule rule; ResponseBuilder responseBuilder; try { URI uri = new URI(recipeID); if(uri.getScheme() == null){ recipeID = "urn:" + recipeID; log.info("The recipe ID is a URI without scheme. The ID is set to " + recipeID); } recipe = ruleStore.getRecipe(new IRI(recipeID)); if (ruleID != null && !ruleID.isEmpty()) { rule = ruleStore.getRule(recipe, new IRI(ruleID)); RuleList ruleList = new RuleList(); ruleList.add(rule); recipe = new RecipeImpl(recipe.getRecipeID(), recipe.getRecipeDescription(), ruleList); } responseBuilder = Response.ok(new Viewable("rules", new RulesPrettyPrintResource( uriInfo, recipe))); } catch (NoSuchRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } catch (RecipeConstructionException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NO_CONTENT); } catch (NoSuchRuleInRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } catch (URISyntaxException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_ACCEPTABLE); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } /** * This method implements a REST service that allows to create a new empty recipe in the store with a * given description.<br/> * The description parameter is OPTIONAL. * * @param recipeID * {@link String} * @param description * {@link String} - OPTIONAL * @param headers * {@link HttpHeaders} * @return <ul> * <li>200 - if the recipe is created</li> * <li>409 - if a recipe with the same identifier exists in the store</li> * </ul> */ @PUT @Consumes(MediaType.WILDCARD) @Path("/recipe/{recipe:.+}") public Response createRecipe(@PathParam("recipe") String recipeID, @QueryParam("description") String description, @Context HttpHeaders headers) { ResponseBuilder responseBuilder; try { URI uri = new URI(recipeID); if(uri.getScheme() == null){ recipeID = "urn:" + recipeID; log.info("The recipe ID is a URI without scheme. The ID is set to " + recipeID); } ruleStore.createRecipe(new IRI(recipeID), description); responseBuilder = Response.ok(); } catch (AlreadyExistingRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.CONFLICT); } catch (URISyntaxException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_ACCEPTABLE); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } @GET @Path("/recipe") @Produces(value = {KRFormat.RDF_XML, KRFormat.TURTLE, KRFormat.OWL_XML, KRFormat.RDF_JSON, KRFormat.FUNCTIONAL_OWL, KRFormat.MANCHESTER_OWL}) public Response listRecipes(@Context HttpHeaders headers) { ResponseBuilder responseBuilder = null; try { RecipeList recipeList = getListRecipes(); responseBuilder = Response.ok(recipeList); } catch (NoSuchRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } catch (RecipeConstructionException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } public RecipeList getListRecipes() throws NoSuchRecipeException, RecipeConstructionException { return ruleStore.listRecipes(); } /** * This method allows to delete a recipe or a rule from the store.<br/> * If the optional rule identifier id provided as second parameter that the rule is deleted. Otherwise it * is the whole recipe to be deleted. * * @param recipe * {@link String} * @param rule * {@link String} - OPTIONAL * @param headers * {@link HttpHeaders} * @return <ul> * <li>200 - if either the recipe or the rule is deleted</li> * <li>204 - it is not possible to delete the rule because the internal construction of the recipe * failed</li> * <li>404 - the rule does not exist</li> * <li>412 - the recipe to which the rule belongs does not exist</li> * <li>500 - if a {@link RecipeEliminationException} exception is thrown</li> * </ul> */ @DELETE @Path("/recipe/{recipe:.+}") public Response removeRecipe(@PathParam("recipe") String recipe, @QueryParam("rule") String rule, @Context HttpHeaders headers) { ResponseBuilder responseBuilder; boolean stop = false; URI uri = null; try { uri = new URI(recipe); } catch (URISyntaxException e1) { log.error(e1.getMessage(), e1); responseBuilder = Response.status(Status.NOT_ACCEPTABLE); stop = true; } if(!stop){ if(uri != null && uri.getScheme() == null){ recipe = "urn:" + recipe; log.info("The recipe ID is a URI without scheme. The ID is set to " + recipe); } log.info("The recipe ID is : " + recipe); if (rule != null && !rule.isEmpty()) { Recipe rcp; try { rcp = ruleStore.getRecipe(new IRI(recipe)); Rule rl = ruleStore.getRule(rcp, new IRI(rule)); ruleStore.removeRule(rcp, rl); } catch (NoSuchRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.PRECONDITION_FAILED); } catch (RecipeConstructionException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NO_CONTENT); } catch (NoSuchRuleInRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } } else { try { ruleStore.removeRecipe(new IRI(recipe)); } catch (RecipeEliminationException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR); } } } responseBuilder = Response.ok(); // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } /** * Add rules to a recipe. An optional description can be provided to the rules. * * @param recipe * {A string contains the IRI of the recipe to be added} * @param rules * {A string contains the rules in Stanbol syntax} * @param description * {A string contains a description of the rule} * @param headers * {The {@link HttpHeaders} * @return Return: <br/> * 200 The recipe has been added<br/> * 409 The recipe has not been added<br/> * 500 Some error occurred */ @POST @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(value = {KRFormat.TEXT_PLAIN, KRFormat.RDF_JSON}) @Path("/recipe/{recipe:.+}") public Response addRulesToRecipe(@PathParam(value = "recipe") String recipe, MultiPartBody data, @Context HttpHeaders headers) { String description = null; InputStream rules = null; if(data.getTextParameterValues("description") != null){ description = data.getTextParameterValues("description") [0]; } if(data.getFormFileParameterValues("rules") != null){ rules = new ByteArrayInputStream(data.getFormFileParameterValues("rules") [0].getContent()); } if(recipe == null || rules == null || description == null){ throw new WebApplicationException(BAD_REQUEST); } ResponseBuilder responseBuilder; Recipe rcp; try { URI uri = new URI(recipe); if(uri.getScheme() == null){ recipe = "urn:" + recipe; log.info("The recipe ID is a URI without scheme. The ID is set to " + recipe); } rcp = ruleStore.getRecipe(new IRI(recipe)); ruleStore.addRulesToRecipe(rcp, rules, description); responseBuilder = Response.ok(); } catch (NoSuchRecipeException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_FOUND); } catch (RecipeConstructionException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR); } catch (URISyntaxException e) { log.error(e.getMessage(), e); responseBuilder = Response.status(Status.NOT_ACCEPTABLE); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } /** * Return the String representation of a recipe adapted to another format, e.g., Jena rules, SPARQL * CONSTRUCTs, Clerezza, SWRL, etc. * * @param recipe * {The ID of the recipe} * @param format * {The canonical name of the class we want have back, e.g., * org.apache.stanbol.rules.base.api.Rule for Jena Rules} * @param headers * {@link HttpHeaders} * @return <ul> * <li>200: it works properly and the string representation of the recipe according to the format * provided is returned</li> * <li>204: the recipe does not exist in the store</li> * <li>403: a class exists for the format provided but there is no adapter for that</li> * <li>404: no class exists in the context for the format provided</li> * <li>406: some error occurred while converting a rule of the recipe</li> * <li>409: some atom of a rule in the recipe cannot be converted to the format provided</li> * </ul> */ @GET @Produces(value = {KRFormat.RDF_JSON}) @Path("/adapters/{recipe:.+}") public Response adaptTo(@PathParam("recipe") String recipe, @QueryParam("format") String format, @Context HttpHeaders headers) { ResponseBuilder responseBuilder = null; Class<?> classToLoad; try { // ClassLoader loader = Thread.currentThread().getContextClassLoader(); // classToLoad = loader.loadClass(format); classToLoad = Class.forName(format); URI uri = new URI(recipe); if(uri.getScheme() == null){ recipe = "urn:" + recipe; log.info("The recipe ID is a URI without scheme. The ID is set to " + recipe); } Recipe rcp = ruleStore.getRecipe(new IRI(recipe)); RuleAdapter adapter = adapterManager.getAdapter(rcp, classToLoad); Object adaptedRecipe = adapter.adaptTo(rcp, classToLoad); JSONObject jsonObject = new JSONObject(); try { jsonObject.put("recipe", rcp.getRecipeID().toString()); jsonObject.put("adaptedTo", format); jsonObject.put("result", adaptedRecipe.toString()); } catch (JSONException e) { log.error(e.getMessage(), e); } responseBuilder = Response.ok(jsonObject.toString()); } catch (ClassNotFoundException e) { responseBuilder = Response.status(Status.NOT_FOUND); log.error(e.getMessage(), e); } catch (NoSuchRecipeException e) { responseBuilder = Response.status(Status.NO_CONTENT); log.error(e.getMessage(), e); } catch (RecipeConstructionException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnavailableRuleObjectException e) { responseBuilder = Response.status(Status.NOT_ACCEPTABLE); log.error(e.getMessage(), e); } catch (RuleAtomCallExeption e) { responseBuilder = Response.status(Status.CONFLICT); log.error(e.getMessage(), e); } catch (UnsupportedTypeForExportException e) { responseBuilder = Response.status(Status.FORBIDDEN); log.error(e.getMessage(), e); } catch (URISyntaxException e) { responseBuilder = Response.status(Status.NOT_ACCEPTABLE); log.error(e.getMessage(), e); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } /** * It returns the list of available {@link RuleAdapter} instances. * * @param headers * {@link HttpHeaders} * @return <ul> * <li>A JSON array containing available adapters.<br/> * Each element of the array is an object composed by the following fields: * <ul> * <li>adapter: the canonical name of the adapter class;</li> * <li>adapter: the canonical name of the instances' class that the adapter provide as output.</li> * </ul> * </li> * <li>404: No adapter exists</li> * </ul> */ @GET @Produces(value = {KRFormat.RDF_JSON}) @Path("/adapters") public Response listAdaptersService(@Context HttpHeaders headers) { ResponseBuilder responseBuilder = null; List<RuleAdapter> adapters = getListAdapters(); if (adapters != null && !adapters.isEmpty()) { JSONObject jsonObject = new JSONObject(); JSONArray jsonArray = new JSONArray(); for (RuleAdapter adapter : adapters) { JSONObject jsonAdapter = new JSONObject(); try { jsonAdapter.put("adapter", adapter.getClass().getCanonicalName()); jsonAdapter.put("adaptTo", adapter.getExportClass().getCanonicalName()); } catch (JSONException e) { log.error(e.getMessage(), e); } jsonArray.put(jsonAdapter); } try { jsonObject.put("adapters", jsonArray); } catch (JSONException e) { log.error(e.getMessage(), e); } responseBuilder = Response.ok(jsonObject.toString()); } else { responseBuilder = Response.status(Status.NOT_FOUND); } // addCORSOrigin(servletContext, responseBuilder, headers); return responseBuilder.build(); } public List<RuleAdapter> getListAdapters() { return adapterManager.listRuleAdapters(); } @OPTIONS public Response handleCorsPreflight(@Context HttpHeaders headers) { ResponseBuilder rb = Response.ok(); // enableCORS(servletContext, rb, headers); return rb.build(); } }