1   package eu.fbk.knowledgestore.server.http.jaxrs;
2   
3   import java.util.List;
4   import java.util.Set;
5   
6   import javax.ws.rs.Consumes;
7   import javax.ws.rs.FormParam;
8   import javax.ws.rs.GET;
9   import javax.ws.rs.HttpMethod;
10  import javax.ws.rs.POST;
11  import javax.ws.rs.Path;
12  import javax.ws.rs.Produces;
13  import javax.ws.rs.QueryParam;
14  import javax.ws.rs.core.GenericType;
15  import javax.ws.rs.core.Response;
16  import javax.ws.rs.core.Response.Status;
17  
18  import com.google.common.collect.Sets;
19  
20  import org.codehaus.enunciate.jaxrs.TypeHint;
21  import org.openrdf.model.URI;
22  
23  import eu.fbk.knowledgestore.Operation;
24  import eu.fbk.knowledgestore.OperationException;
25  import eu.fbk.knowledgestore.Outcome;
26  import eu.fbk.knowledgestore.data.Data;
27  import eu.fbk.knowledgestore.data.Stream;
28  import eu.fbk.knowledgestore.internal.jaxrs.Protocol;
29  import eu.fbk.knowledgestore.internal.rdf.RDFUtil;
30  
31  /**
32   * Provides the KnowledgeStore SPARQL endpoint.
33   * <p style="color: red">
34   * DOCUMENTATION COMING SOON
35   * </p>
36   */
37  @Path("/" + Protocol.PATH_SPARQL)
38  public class Sparql extends Resource {
39  
40      @GET
41      @Produces(Protocol.MIME_TYPES_ALL)
42      @TypeHint(Stream.class)
43      public Response get(
44              @QueryParam(Protocol.PARAMETER_DEFAULT_GRAPH) final List<String> defaultGraphs,
45              @QueryParam(Protocol.PARAMETER_NAMED_GRAPH) final List<String> namedGraphs,
46              @QueryParam(Protocol.PARAMETER_QUERY) final String query) throws OperationException {
47          return query(query, defaultGraphs, namedGraphs);
48      }
49  
50      @POST
51      @Consumes("application/x-www-form-urlencoded")
52      @Produces(Protocol.MIME_TYPES_ALL)
53      @TypeHint(Stream.class)
54      public Response postURLencoded(
55              @FormParam(Protocol.PARAMETER_DEFAULT_GRAPH) final List<String> defaultGraphs,
56              @FormParam(Protocol.PARAMETER_NAMED_GRAPH) final List<String> namedGraphs,
57              @FormParam(Protocol.PARAMETER_QUERY) final String query) throws OperationException {
58          return query(query, defaultGraphs, namedGraphs);
59      }
60  
61      @POST
62      @Consumes("application/sparql-query")
63      @Produces(Protocol.MIME_TYPES_ALL)
64      @TypeHint(Stream.class)
65      public Response postDirect(
66              @QueryParam(Protocol.PARAMETER_DEFAULT_GRAPH) final List<String> defaultGraphs,
67              @QueryParam(Protocol.PARAMETER_NAMED_GRAPH) final List<String> namedGraphs,
68              final String query) throws OperationException {
69          return query(query, defaultGraphs, namedGraphs);
70      }
71  
72      private Response query(final String query, final List<String> defaultGraphs,
73              final List<String> namedGraphs) throws OperationException {
74  
75          // Check mandatory parameter
76          checkNotNull(query, Outcome.Status.ERROR_INVALID_INPUT, "Missing query");
77  
78          // Prepare the SPARQL operation, returning an error if parameters are wrong
79          final String form;
80          final Operation.Sparql operation;
81          try {
82              form = RDFUtil.detectSparqlForm(query);
83              operation = getSession().sparql(query) //
84                      .timeout(getTimeout()) //
85                      .defaultGraphs(parseGraphURIs(defaultGraphs)) //
86                      .namedGraphs(parseGraphURIs(namedGraphs));
87          } catch (final RuntimeException ex) {
88              throw new OperationException(newOutcome(Outcome.Status.ERROR_INVALID_INPUT, "%s",
89                      ex.getMessage()), ex);
90          }
91  
92          // Select correct MIME type via negotiation, validate preconditions and handle probes
93          final GenericType<?> type;
94          if (form.equals("construct") || form.equals("describe")) {
95              init(false, Protocol.MIME_TYPES_RDF, null, null);
96              type = Protocol.STREAM_OF_STATEMENTS;
97          } else if (form.equals("select")) {
98              init(false, Protocol.MIME_TYPES_SPARQL_TUPLE, null, null);
99              type = Protocol.STREAM_OF_TUPLES;
100         } else {
101             init(false, Protocol.MIME_TYPES_SPARQL_BOOLEAN, null, null);
102             type = Protocol.STREAM_OF_BOOLEANS;
103         }
104 
105         // Setup the result stream based on the selected type. Note that an empty result is
106         // selected for HEAD methods, as only headers will be returned.
107         Stream<?> entity;
108         if (getMethod().equals(HttpMethod.HEAD)) {
109             entity = Stream.create();
110         } else if (type == Protocol.STREAM_OF_STATEMENTS) {
111             entity = operation.execTriples();
112         } else if (type == Protocol.STREAM_OF_TUPLES) {
113             entity = operation.execTuples();
114         } else if (type == Protocol.STREAM_OF_BOOLEANS) {
115             entity = Stream.create(operation.execBoolean());
116         } else {
117             throw new Error("Unexpected type: " + type);
118         }
119 
120         // Build and return the SPARQL response
121         return newResponseBuilder(Status.OK, closeOnCompletion(entity), type).build();
122     }
123 
124     private static Set<URI> parseGraphURIs(final List<String> strings) {
125         if (strings == null || strings.isEmpty()) {
126             return null;
127         }
128         final Set<URI> uris = Sets.newHashSetWithExpectedSize(strings.size());
129         for (final String string : strings) {
130             uris.add(Data.getValueFactory().createURI(string));
131         }
132         return uris;
133     }
134 
135 }