| /* |
| * 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. |
| */ |
| package org.apache.stanbol.enhancer.web.topic.resource; |
| |
| import static javax.ws.rs.core.MediaType.TEXT_HTML; |
| |
| 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.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.UriInfo; |
| |
| import org.apache.clerezza.commons.rdf.ImmutableGraph; |
| import org.apache.clerezza.commons.rdf.IRI; |
| import org.apache.felix.scr.annotations.Activate; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Property; |
| import org.apache.felix.scr.annotations.Service; |
| import org.apache.stanbol.commons.web.viewable.Viewable; |
| import org.apache.stanbol.commons.web.base.resource.BaseStanbolResource; |
| import org.apache.stanbol.enhancer.servicesapi.EnhancementEngine; |
| import org.apache.stanbol.enhancer.servicesapi.rdf.OntologicalClasses; |
| import org.apache.stanbol.enhancer.servicesapi.rdf.Properties; |
| import org.apache.stanbol.enhancer.topic.api.ClassifierException; |
| import org.apache.stanbol.enhancer.topic.api.TopicClassifier; |
| import org.apache.stanbol.enhancer.topic.api.training.TrainingSet; |
| import org.apache.stanbol.enhancer.topic.api.training.TrainingSetException; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.service.component.ComponentContext; |
| |
| /** |
| * RESTful interface for classification models: register concept hierarchies, |
| * introspect model state and trigger training if a training set is provided. |
| * |
| */ |
| @Component |
| @Service(Object.class) |
| @Property(name="javax.ws.rs", boolValue=true) |
| @Path("/topic/model") |
| public final class TopicModelResource extends BaseStanbolResource { |
| |
| private BundleContext bundleContext; |
| |
| @Activate |
| protected void activate(ComponentContext context) { |
| bundleContext = context.getBundleContext(); |
| } |
| |
| |
| @Path("{classifier}") |
| public ClassifierResource getClassifier(@PathParam(value = "classifier") String classifierName, |
| @Context UriInfo uriInfo) throws InvalidSyntaxException { |
| this.uriInfo = uriInfo; |
| ServiceReference[] references = bundleContext.getServiceReferences(TopicClassifier.class.getName(), |
| String.format("(%s=%s)", EnhancementEngine.PROPERTY_NAME, classifierName)); |
| if (references == null || references.length == 0) { |
| throw new WebApplicationException(Response.Status.NOT_FOUND); |
| } |
| return new ClassifierResource((TopicClassifier) bundleContext.getService(references[0])); |
| } |
| |
| public class ClassifierResource extends ResultData { |
| final TopicClassifier classifier; |
| |
| public ClassifierResource(TopicClassifier classifier) { |
| this.classifier = classifier; |
| } |
| |
| |
| public TopicClassifier getClassifier() { |
| return classifier; |
| } |
| |
| @GET |
| @Produces(TEXT_HTML) |
| public Response get(@Context HttpHeaders headers) { |
| ResponseBuilder rb = Response.ok(new Viewable("index", this)); |
| rb.header(HttpHeaders.CONTENT_TYPE, TEXT_HTML + "; charset=utf-8"); |
| return rb.build(); |
| } |
| |
| // TODO: make it possible to fetch concept descriptions (with broader and narrower links) using the GET |
| // verb |
| @POST |
| @Path("concept") |
| @Consumes(MediaType.WILDCARD) |
| public Response addConcept(@QueryParam(value = "id") String concept, |
| @QueryParam(value = "primary_topic") String primaryTopicUri, |
| @QueryParam(value = "broader") List<String> broaderConcepts, |
| @Context HttpHeaders headers) throws ClassifierException { |
| classifier.addConcept(concept, primaryTopicUri, broaderConcepts); |
| ResponseBuilder rb = Response.ok(); |
| return rb.build(); |
| } |
| |
| @DELETE |
| @Path("concept") |
| @Consumes(MediaType.WILDCARD) |
| public Response remoteConcept(@QueryParam(value = "id") String concept, @Context HttpHeaders headers) throws ClassifierException { |
| if (concept != null && !concept.isEmpty()) { |
| classifier.removeConcept(concept); |
| } else { |
| classifier.removeAllConcepts(); |
| } |
| // TODO: count the number of deleted entries and return is a text entity |
| ResponseBuilder rb = Response.ok(); |
| return rb.build(); |
| } |
| |
| @OPTIONS |
| @Path("performance") |
| public Response handleCorsPreflightOnPerformance(@Context HttpHeaders headers) { |
| ResponseBuilder res = Response.ok(); |
| return res.build(); |
| } |
| |
| // TODO: make it possible to fetch performance reports and evaluation running state using the GET verb |
| @POST |
| @Path("performance") |
| @Consumes(MediaType.WILDCARD) |
| public Response updatePerformance(@QueryParam(value = "incremental") Boolean incremental, |
| @Context HttpHeaders headers) throws TrainingSetException, |
| ClassifierException { |
| if (incremental == null) { |
| incremental = Boolean.TRUE; |
| } |
| int updated = classifier.updatePerformanceEstimates(incremental); |
| ResponseBuilder rb = Response.ok(String.format( |
| "Successfully updated the performance estimates of %d concept(s).\n", updated)); |
| return rb.build(); |
| } |
| |
| // TODO: make it possible to fetch training set statistics and training state using the GET verb |
| @POST |
| @Path("trainer") |
| @Consumes(MediaType.WILDCARD) |
| public Response updateModel(@QueryParam(value = "incremental") Boolean incremental, |
| @Context HttpHeaders headers) throws TrainingSetException, |
| ClassifierException { |
| if (incremental == null) { |
| incremental = Boolean.TRUE; |
| } |
| int updated = classifier.updateModel(incremental); |
| ResponseBuilder rb = Response.ok(String.format( |
| "Successfully updated the statistical model(s) of %d concept(s).\n", updated)); |
| return rb.build(); |
| } |
| |
| // TODO: make it possible browse the training set content on the GET verb using a subresource |
| @POST |
| @Path("trainingset") |
| @Consumes(MediaType.TEXT_PLAIN) |
| public Response registerExample(@QueryParam(value = "example_id") String exampleId, |
| @QueryParam(value = "concept") List<String> concepts, |
| String textContent, |
| @Context HttpHeaders headers) throws TrainingSetException, |
| ClassifierException { |
| ResponseBuilder rb; |
| if (!classifier.isUpdatable()) { |
| rb = Response.status(Response.Status.BAD_REQUEST).entity( |
| String.format("Classifier %s is not updateble.\n", classifier.getName())); |
| } else { |
| TrainingSet trainingSet = classifier.getTrainingSet(); |
| exampleId = trainingSet.registerExample(exampleId, textContent, concepts); |
| // TODO: make example GETable resources and return a 201 to it instead of a simple message. |
| rb = Response.ok(String.format( |
| "Successfully added or updated example '%s' in training set '%s'.\n", exampleId, |
| trainingSet.getName())); |
| } |
| return rb.build(); |
| } |
| |
| // TODO make the following a DELETE method on the example sub-resources them-selves once we have a GET for |
| // them |
| @DELETE |
| @Path("trainingset") |
| @Consumes(MediaType.WILDCARD) |
| public Response removeExample(@QueryParam(value = "example_id") List<String> exampleIds, |
| @Context HttpHeaders headers) throws TrainingSetException, |
| ClassifierException { |
| ResponseBuilder rb; |
| if (!classifier.isUpdatable()) { |
| rb = Response.status(Response.Status.BAD_REQUEST).entity( |
| String.format("Classifier %s is not updateble.\n", classifier.getName())); |
| } else { |
| TrainingSet trainingSet = classifier.getTrainingSet(); |
| if (exampleIds != null && !exampleIds.isEmpty()) { |
| for (String exampleId : exampleIds) { |
| trainingSet.registerExample(exampleId, null, null); |
| } |
| } else { |
| // implement a way to cleanup a complete training set? or is it too dangerous and we should |
| // return an error instead? |
| } |
| rb = Response.ok(String.format("Successfully deleted examples in training set '%s'.\n", |
| trainingSet.getName())); |
| } |
| return rb.build(); |
| } |
| |
| /** |
| * Simple RDF / SKOS importer that loads the complete model in memory |
| * for easy parsing and then does graph introspection to find the |
| * concepts to load into the model. |
| * |
| * If a scalable implementation is required, one should probably use a |
| * transient triple store and pass it the raw RDF stream instead of |
| * using the naive GraphReader JAX-RS provider. |
| */ |
| @POST |
| @Consumes(MediaType.WILDCARD) |
| public Response importConceptsFromRDF(@QueryParam(value = "concept_class") String conceptClassUri, |
| @QueryParam(value = "broader_property") String broaderPropertyUri, |
| ImmutableGraph graph, |
| @Context HttpHeaders headers) throws ClassifierException { |
| IRI conceptClass = OntologicalClasses.SKOS_CONCEPT; |
| IRI broaderProperty = Properties.SKOS_BROADER; |
| if (conceptClassUri != null && !conceptClassUri.isEmpty()) { |
| conceptClass = new IRI(conceptClassUri); |
| } |
| if (broaderPropertyUri != null && !broaderPropertyUri.isEmpty()) { |
| broaderProperty = new IRI(broaderPropertyUri); |
| } |
| int imported = classifier.importConceptsFromGraph(graph, conceptClass, broaderProperty); |
| ResponseBuilder rb; |
| if (imported == 0) { |
| rb = Response.status(Response.Status.BAD_REQUEST).entity( |
| String.format("Could not find any instances of '%s' in payload.\n", |
| conceptClass.getUnicodeString())); |
| } else { |
| rb = Response.ok(String.format("Imported %d instance of '%s'.\n", imported, |
| conceptClass.getUnicodeString())); |
| } |
| return rb.build(); |
| } |
| } |
| } |