Skip to content

Commit 0e3f1f8

Browse files
author
izzyblues
committed
Implemented DELETE on APIs (issue #12); Core API separated by REST interface(issue #5); Implemented Cloning of APIs (issue #28)
1 parent df8ac92 commit 0e3f1f8

15 files changed

+494
-309
lines changed

basil/src/main/java/uk/ac/open/kmi/basil/AbstractResource.java

+11-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package uk.ac.open.kmi.basil;
22

3+
import uk.ac.open.kmi.basil.core.ApiManager;
4+
import uk.ac.open.kmi.basil.core.ApiManagerImpl;
35
import uk.ac.open.kmi.basil.store.Store;
46

57
import javax.servlet.ServletContext;
@@ -11,7 +13,6 @@
1113
import javax.ws.rs.core.UriInfo;
1214
import java.net.HttpURLConnection;
1315
import java.net.URI;
14-
import java.util.regex.Pattern;
1516

1617
public class AbstractResource {
1718

@@ -23,6 +24,14 @@ public class AbstractResource {
2324

2425
@Context
2526
protected ServletContext context;
27+
private ApiManager apiManager;
28+
29+
protected ApiManager getApiManager() {
30+
if (apiManager == null) {
31+
apiManager = new ApiManagerImpl(getDataStore());
32+
}
33+
return apiManager;
34+
}
2635

2736
protected final ResponseBuilder addHeaders(ResponseBuilder builder,
2837
String id) {
@@ -64,19 +73,7 @@ protected final String getParameterOrHeader(String parameter,
6473
return value;
6574
}
6675

67-
protected Store getDataStore() {
76+
private Store getDataStore() {
6877
return (Store) context.getAttribute(BasilApplication.Registry.Store);
6978
}
70-
71-
protected boolean isValidId(String id) {
72-
return Pattern.matches("([^/]+)", id);
73-
}
74-
75-
protected boolean isValidName(String name) {
76-
return Pattern.matches("([^/]+)", name);
77-
}
78-
79-
protected boolean isValidExtension(String extentsion) {
80-
return Pattern.matches("(\\.[\\-a-zA-Z0-9]+)?", extentsion);
81-
}
8279
}

basil/src/main/java/uk/ac/open/kmi/basil/ApiDocsResource.java

+4-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.json.simple.JSONObject;
55
import uk.ac.open.kmi.basil.doc.Doc;
66
import uk.ac.open.kmi.basil.sparql.Specification;
7-
import uk.ac.open.kmi.basil.store.Store;
87
import uk.ac.open.kmi.basil.swagger.SwaggerJsonBuilder;
98
import uk.ac.open.kmi.basil.swagger.SwaggerUIBuilder;
109

@@ -29,11 +28,8 @@ public Response get(@PathParam("id") String id,
2928
@ApiParam(value = "Accepted Media Type", allowableValues = "application/json,text/html")
3029
@HeaderParam("Accept") String accept) {
3130
try {
32-
if (!isValidId(id)) {
33-
return Response.status(400).build();
34-
}
35-
Store store = getDataStore();
36-
if (!store.existsSpec(id)) {
31+
32+
if (getApiManager().getSpecification(id) == null) {
3733
return Response.status(404).build();
3834
}
3935
if (accept.contains("text/html")) {
@@ -42,8 +38,8 @@ public Response get(@PathParam("id") String id,
4238
addHeaders(builder, id);
4339
return builder.build();
4440
}else if (accept.contains("application/json") || accept.contains("*/*")) {
45-
Specification specification = store.loadSpec(id);
46-
Doc docs = store.loadDoc(id);
41+
Specification specification = getApiManager().getSpecification(id);
42+
Doc docs = getApiManager().getDoc(id);
4743
JSONObject o = SwaggerJsonBuilder.build(id, specification, docs, requestUri.getBaseUri().toString());
4844
ResponseBuilder builder = Response.ok(o.toJSONString());
4945
addHeaders(builder, id);

basil/src/main/java/uk/ac/open/kmi/basil/ApiResource.java

+99-89
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import com.hp.hpl.jena.graph.Node;
44
import com.hp.hpl.jena.graph.NodeFactory;
55
import com.hp.hpl.jena.graph.Triple;
6-
import com.hp.hpl.jena.query.*;
6+
import com.hp.hpl.jena.query.QuerySolution;
7+
import com.hp.hpl.jena.query.ResultSet;
8+
import com.hp.hpl.jena.query.ResultSetFormatter;
79
import com.hp.hpl.jena.rdf.model.Model;
810
import com.hp.hpl.jena.rdf.model.RDFNode;
911
import com.hp.hpl.jena.sparql.core.DatasetGraph;
@@ -25,10 +27,9 @@
2527
import org.json.simple.JSONObject;
2628
import org.slf4j.Logger;
2729
import org.slf4j.LoggerFactory;
28-
import uk.ac.open.kmi.basil.sparql.QueryParameter;
30+
import uk.ac.open.kmi.basil.core.InvocationResult;
31+
import uk.ac.open.kmi.basil.core.exceptions.SpecificationParsingException;
2932
import uk.ac.open.kmi.basil.sparql.Specification;
30-
import uk.ac.open.kmi.basil.sparql.VariablesBinder;
31-
import uk.ac.open.kmi.basil.store.Store;
3233
import uk.ac.open.kmi.basil.view.Items;
3334
import uk.ac.open.kmi.basil.view.View;
3435
import uk.ac.open.kmi.basil.view.Views;
@@ -39,11 +40,15 @@
3940
import javax.ws.rs.core.Response;
4041
import javax.ws.rs.core.Response.ResponseBuilder;
4142
import javax.ws.rs.core.Variant;
43+
import java.io.IOException;
4244
import java.io.StringWriter;
4345
import java.io.UnsupportedEncodingException;
4446
import java.net.HttpURLConnection;
4547
import java.net.URI;
46-
import java.util.*;
48+
import java.util.Arrays;
49+
import java.util.Iterator;
50+
import java.util.List;
51+
import java.util.Map;
4752

4853
@Path("{id}")
4954
@Api(value = "/basil", description = "BASIL operations")
@@ -55,45 +60,7 @@ public class ApiResource extends AbstractResource {
5560
private Response performQuery(String id,
5661
MultivaluedMap<String, String> parameters, String extension) {
5762
try {
58-
if (!isValidId(id)) {
59-
return Response.status(400).build();
60-
}
61-
if (!extension.equals("") && !isValidExtension(extension)) {
62-
return Response.status(400).build();
63-
}
64-
Store store = getDataStore();
65-
if (!store.existsSpec(id)) {
66-
return Response.status(404).build();
67-
}
68-
69-
Specification specification = store.loadSpec(id);
70-
VariablesBinder binder = new VariablesBinder(specification);
71-
72-
List<String> missing = new ArrayList<String>();
73-
for (QueryParameter qp : specification.getParameters()) {
74-
if (parameters.containsKey(qp.getName())) {
75-
List<String> values = parameters.get(qp.getName());
76-
binder.bind(qp.getName(), values.get(0));
77-
} else if (!qp.isOptional()) {
78-
missing.add(qp.getName());
79-
}
80-
}
81-
82-
if (!missing.isEmpty()) {
83-
StringBuilder ms = new StringBuilder();
84-
ms.append("Missing mandatory query parameters: ");
85-
for (String p : missing) {
86-
ms.append(p);
87-
ms.append("\t");
88-
}
89-
ms.append("\n");
90-
return Response.status(400).entity(ms.toString()).build();
91-
}
92-
93-
Query q = binder.toQuery();
94-
QueryExecution qe = QueryExecutionFactory.sparqlService(
95-
specification.getEndpoint(), q);
96-
Object entity = null;
63+
InvocationResult r = getApiManager().invokeApi(id, parameters);
9764

9865
MediaType type = null;
9966
// If we have an extension
@@ -104,24 +71,19 @@ private Response performQuery(String id,
10471

10572
// No extension, check if the extension is the name of a view
10673
if (type == null) {
107-
Views views = store.loadViews(id);
74+
Views views = getApiManager().listViews(id);
10875
if (views.exists(extension)) {
10976
View view = views.byName(extension);
11077
StringWriter writer = new StringWriter();
11178
Items data = null;
112-
if (q.isSelectType()) {
113-
data = Items.create(qe.execSelect());
114-
} else if (q.isConstructType()) {
115-
data = Items.create(qe.execConstructTriples());
116-
} else if (q.isAskType()) {
117-
data = Items.create(qe.execAsk());
118-
} else if (q.isDescribeType()) {
119-
data = Items.create(qe.execDescribeTriples());
120-
} else {
121-
return Response
122-
.serverError()
123-
.entity("Unsupported query type: "
124-
+ q.getQueryType()).build();
79+
if (r.getResult() instanceof ResultSet) {
80+
data = Items.create((ResultSet) r.getResult());
81+
} else if (r.getResult() instanceof Model) {
82+
data = Items.create((((Model) r.getResult()).getGraph().find(null, null, null)));
83+
} else if (r.getResult() instanceof Boolean) {
84+
data = Items.create((Boolean) r.getResult());
85+
} else if (r.getResult() instanceof List) {
86+
data = Items.create((List<Map<String, String>>) r.getResult());
12587
}
12688
view.getEngine().exec(writer, view.getTemplate(), data);
12789
// Yeah!
@@ -146,22 +108,15 @@ private Response performQuery(String id,
146108
return buildNotAcceptable();
147109
}
148110

149-
if (q.isSelectType()) {
150-
entity = prepareEntity(type, qe.execSelect());
151-
} else if (q.isConstructType()) {
152-
entity = prepareEntity(type, qe.execConstruct(), q
153-
.getPrefixMapping().getNsPrefixMap());
154-
} else if (q.isAskType()) {
155-
entity = prepareEntity(type, qe.execAsk());
156-
} else if (q.isDescribeType()) {
157-
entity = prepareEntity(type, qe.execDescribe(), q
111+
Object entity = null;
112+
if (r.getResult() instanceof ResultSet) {
113+
entity = prepareEntity(type, (ResultSet) r.getResult());
114+
} else if (r.getResult() instanceof Model) {
115+
entity = prepareEntity(type, (Model) r.getResult(), r.getQuery()
158116
.getPrefixMapping().getNsPrefixMap());
159-
} else {
160-
return Response.serverError()
161-
.entity("Unsupported query type: " + q.getQueryType())
162-
.build();
117+
} else if (r.getResult() instanceof Boolean) {
118+
entity = prepareEntity(type, (Boolean) r.getResult());
163119
}
164-
165120
// If entity is null then format is not acceptable
166121
// ie we don't have an implementation of that object/type map
167122
if (entity == null) {
@@ -719,10 +674,25 @@ public Response replaceSpec(
719674
@ApiParam(value = "SPARQL query that substitutes the API specification", required = true)
720675
String body) {
721676
log.trace("Called PUT with id: {}", id);
722-
if (!isValidId(id)) {
723-
return Response.status(400).build();
677+
try {
678+
getApiManager().replaceSpecification(id, body);
679+
680+
ResponseBuilder response;
681+
URI spec = requestUri.getBaseUriBuilder().path(id).path("spec").build();
682+
log.info("Replaced spec at: {}", spec);
683+
response = Response.ok(spec).entity(
684+
"Replaced: " + spec.toString() + "\n");
685+
686+
addHeaders(response, id);
687+
688+
return response.build();
689+
} catch (SpecificationParsingException e) {
690+
return Response.status(HttpURLConnection.HTTP_BAD_REQUEST)
691+
.header(Headers.Error, e.getMessage()).build();
692+
} catch (IOException e) {
693+
return Response.status(HttpURLConnection.HTTP_INTERNAL_ERROR)
694+
.header(Headers.Error, e.getMessage()).build();
724695
}
725-
return new SpecificationResource().doPUT(id, body);
726696
}
727697

728698
/**
@@ -734,9 +704,6 @@ public Response replaceSpec(
734704
@GET
735705
public Response redirectToSpec(
736706
@PathParam(value = "id") String id) {
737-
if (!isValidId(id)) {
738-
return Response.status(400).build();
739-
}
740707
ResponseBuilder builder = Response.status(303);
741708
addHeaders(builder, id);
742709
return builder.location(requestUri.getBaseUriBuilder().path(id).path("spec").build()).build();
@@ -760,16 +727,11 @@ public Response getSpec(
760727
@PathParam(value = "id") String id) {
761728
log.trace("Called GET spec with id: {}", id);
762729
try {
763-
if (!isValidId(id)) {
764-
return Response.status(400).build();
765-
}
766730

767-
Store store = getDataStore();
768-
if (!store.existsSpec(id)) {
731+
Specification spec = getApiManager().getSpecification(id);
732+
if (spec == null) {
769733
return Response.status(Response.Status.NOT_FOUND).build();
770734
}
771-
772-
Specification spec = store.loadSpec(id);
773735
ResponseBuilder response = Response.ok();
774736
response.header(Headers.Endpoint, spec.getEndpoint());
775737
addHeaders(response, id);
@@ -800,11 +762,59 @@ public Response deleteSpec(
800762
@ApiParam(value = "ID of the API specification", required = true)
801763
@PathParam(value = "id") String id) {
802764
log.trace("Called DELETE spec with id: {}", id);
803-
if (!isValidId(id)) {
804-
return Response.status(400).build();
765+
try {
766+
getApiManager().deleteApi(id);
767+
URI spec = requestUri.getBaseUriBuilder().path(id).path("spec").build();
768+
ResponseBuilder response;
769+
response = Response.ok().entity(
770+
"Deleted: " + spec.toString());
771+
addHeaders(response, id);
772+
773+
return response.build();
774+
} catch (IOException e) {
775+
throw new WebApplicationException(Response
776+
.status(HttpURLConnection.HTTP_INTERNAL_ERROR)
777+
.entity(e.getMessage()).build());
805778
}
806-
// XXX Not Implemented
807-
return Response.status(501).entity("Not implemented yet\n").build();
808779

809780
}
781+
782+
/**
783+
* Gets a new clone of an API.
784+
*
785+
* @param id
786+
* @return
787+
*/
788+
@GET
789+
@Path("clone")
790+
@Produces("text/plain")
791+
@ApiOperation(value = "Get a clone of an API")
792+
@ApiResponses(value = {@ApiResponse(code = 404, message = "Specification not found"),
793+
@ApiResponse(code = 200, message = "API clones"),
794+
@ApiResponse(code = 500, message = "Internal error")})
795+
public Response getClone(
796+
@ApiParam(value = "ID of the API specification", required = true)
797+
@PathParam(value = "id") String id) {
798+
log.trace("Called GET clone with id: {}", id);
799+
try {
800+
801+
String newId = getApiManager().cloneSpecification(id);
802+
if (newId == null) {
803+
return Response.status(Response.Status.NOT_FOUND).build();
804+
}
805+
ResponseBuilder response;
806+
URI spec = requestUri.getBaseUriBuilder().path(newId).path("spec").build();
807+
log.info("Cloned spec at: {}", spec);
808+
response = Response.ok(spec).entity(
809+
"Cloned at: " + spec.toString() + "\n");
810+
addHeaders(response, newId);
811+
812+
return response.build();
813+
} catch (Exception e) {
814+
log.error("An error occurred", e);
815+
throw new WebApplicationException(Response
816+
.status(HttpURLConnection.HTTP_INTERNAL_ERROR)
817+
.entity(e.getMessage()).build());
818+
}
819+
}
810820
}

0 commit comments

Comments
 (0)