ContextResource.java
/*
* Copyright (c) 2007-2017 MetaSolutions AB
*
* Licensed 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.entrystore.rest.resources;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
import org.entrystore.AuthorizationException;
import org.entrystore.Context;
import org.entrystore.Entry;
import org.entrystore.EntryType;
import org.entrystore.GraphType;
import org.entrystore.Group;
import org.entrystore.List;
import org.entrystore.PrincipalManager.AccessProperty;
import org.entrystore.ResourceType;
import org.entrystore.User;
import org.entrystore.impl.ContextImpl;
import org.entrystore.impl.EntryNamesContext;
import org.entrystore.impl.RDFResource;
import org.entrystore.impl.StringResource;
import org.entrystore.repository.util.NS;
import org.entrystore.rest.util.JSONErrorMessages;
import org.entrystore.rest.util.RDFJSON;
import org.entrystore.rest.util.Util;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.ext.json.JsonRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.Delete;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.resource.ResourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Set;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* This Resource contains information of all contexts.
*
* @author Hannes Ebner
* @see BaseResource
*/
public class ContextResource extends BaseResource {
static Logger log = LoggerFactory.getLogger(ContextResource.class);
private String requestText = null;
@Override
public void doInit() {
try {
requestText = getRequest().getEntity().getText();
} catch (IOException e) {
requestText = null;
}
}
/**
* GET
*
* List entries in a portfolio.
*
* This URL can be requested from a Web browser etc. This method will
* execute a requests and deliver a response.
* <ul>
* <li>GET {base-uri}/{context-id}</li>
* </ul>
*
* return {@link Representation}
*/
@Get
public Representation represent() throws ResourceException {
if (context == null) {
log.debug("The given context id does not exist.");
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return new JsonRepresentation(JSONErrorMessages.errorWrongContextIDmsg);
}
if (!getPM().isUserAdminOrAdminGroup(null)) {
return unauthorizedGET();
}
JSONArray array = new JSONArray();
if (parameters.containsKey("deleted")) {
Set<URI> deletedURIs = context.getDeletedEntries().keySet();
for (URI uri : deletedURIs) {
String delURI = uri.toString();
String delID = delURI.substring(delURI.lastIndexOf("/") + 1);
array.put(delID);
}
} else if (context instanceof EntryNamesContext && parameters.containsKey("entryname")) {
Entry matchedEntry = ((EntryNamesContext) context).getEntryByName(parameters.get("entryname"));
if (matchedEntry != null) {
array.put(matchedEntry.getId());
}
} else {
Set<URI> entriesURI = context.getEntries();
for (URI u : entriesURI) {
Entry entry = context.getByEntryURI(u);
if (entry == null) {
log.warn("No entry found for this referenced URI: " + u);
continue;
}
String entryId = entry.getId();
array.put(entryId);
}
}
return new JsonRepresentation(array.toString());
}
/**
* POST
*
* Creates new entries.
*
* These URL:s can be requested from a Web browser etc. This method will
* execute these requests and deliver a response.
* <ul>
* <li>POST {base-uri}/{portfolio-id}?entryType=local&resourcetype={resourcetype}[&listURI={uri}]</li>
* <li>POST {base-uri}/{portfolio-id}?entryType=link&resource={resource-uri}[&list={listURI}]</li>
* <li>POST {base-uri}/{portfolio-id}?entryType=reference&resource={resource-uri}&metadata={metadata-uri}[&listURI={uri}]</li>
* <li>POST {base-uri}/{portfolio-id}?entryType=linkreference&resource={resource-uri}&metadata={metadata-uri}[&listURI={uri}]</li>
* </ul>
* Explanation:
* <ul>
* <li><i>base-uri</i> is a base URI that are specific for each portfolio installation.</li>
* <li><i>portfolio-id</i> is an integer that uniquely identifies a portfolio within a portfolio installation.</li>
* </ul>
*/
@Post
public void acceptRepresentation(Representation r) throws ResourceException {
try {
if (context == null) {
log.debug("The given context ID does not exist");
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
getResponse().setEntity(JSONErrorMessages.errorWrongContextIDmsg, MediaType.APPLICATION_JSON);
return;
}
String entryId = parameters.get("id");
if (entryId != null) {
if (!isEntryIdValid(entryId)) {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
Entry preExistingEntry = context.get(entryId);
if (preExistingEntry != null) {
log.debug("Entry with that ID already exists");
getResponse().setStatus(Status.CLIENT_ERROR_CONFLICT);
getResponse().setLocationRef(context.get(parameters.get("id")).getEntryURI().toString());
getResponse().setEntity(JSONErrorMessages.errorEntryWithGivenIDExists, MediaType.APPLICATION_JSON);
return;
}
}
Entry entry = null; // A variable to store the new entry in.
try {
// Local
if (!parameters.containsKey("entrytype") || parameters.get("entrytype").equalsIgnoreCase("local")) {
entry = createLocalEntry(entry);
} else {
String lT = parameters.get("entrytype");
// Link
if (lT.equalsIgnoreCase("link") && parameters.containsKey("resource")) {
entry = createLinkEntry(entry);
}
// Reference
else if (lT.equalsIgnoreCase("reference") && parameters.containsKey("resource")
&& parameters.containsKey("cached-external-metadata")) {
entry = createReferenceEntry(entry);
}
// LinkReference
else if (lT.equalsIgnoreCase("linkreference") && parameters.containsKey("resource")
&& parameters.containsKey("cached-external-metadata")) {
entry = createLinkReferenceEntry(entry);
}
}
} catch (IllegalArgumentException iae) {
log.debug(iae.getMessage());
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new JsonRepresentation(new JSONObject().put("error", iae.getMessage())));
return;
}
if (entry != null) {
ResourceType rt = getResourceType(parameters.get("informationresource"));
entry.setResourceType(rt);
String template = parameters.get("template");
if (template != null) {
URI templateEntryURI = null;
try {
templateEntryURI = new URI(template);
} catch (URISyntaxException e) {
log.warn("Ignoring template, got invalid template URI: " + e.getMessage());
}
Entry templateEntry = null;
if (templateEntryURI != null) {
templateEntry = context.getByEntryURI(templateEntryURI);
}
if (templateEntry != null && templateEntry.getLocalMetadata() != null) {
Model templateMD = templateEntry.getLocalMetadata().getGraph();
Model inheritedMD = new LinkedHashModel();
if (templateMD != null) {
ValueFactory vf = getRM().getValueFactory();
IRI oldResURI = vf.createIRI(templateEntry.getResourceURI().toString());
IRI newResURI = vf.createIRI(entry.getResourceURI().toString());
java.util.List<IRI> predicateBlackList = new ArrayList<>();
predicateBlackList.add(vf.createIRI(NS.dc, "title"));
predicateBlackList.add(vf.createIRI(NS.dcterms, "title"));
predicateBlackList.add(vf.createIRI(NS.dc, "description"));
predicateBlackList.add(vf.createIRI(NS.dcterms, "description"));
java.util.List<Value> subjectBlackList = new ArrayList<Value>();
for (Statement statement : templateMD) {
if (predicateBlackList.contains(statement.getPredicate())) {
subjectBlackList.add(statement.getObject());
continue;
}
if (subjectBlackList.contains(statement.getSubject())) {
continue;
}
if (statement.getSubject().equals(oldResURI)) {
inheritedMD.add(newResURI, statement.getPredicate(), statement.getObject(), statement.getContext());
} else {
inheritedMD.add(statement);
}
}
}
if (inheritedMD != null && !inheritedMD.isEmpty() && entry.getLocalMetadata() != null) {
Model mergedGraph = new LinkedHashModel();
mergedGraph.addAll(entry.getLocalMetadata().getGraph());
mergedGraph.addAll(inheritedMD);
entry.getLocalMetadata().setGraph(mergedGraph);
}
}
}
}
if (entry == null) {
log.debug("Cannot create an entry with provided JSON");
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(JSONErrorMessages.errorCantCreateEntry, MediaType.APPLICATION_JSON);
} else {
// Success, return 201 and the new entry ID/URI in response and header
getResponse().setStatus(Status.SUCCESS_CREATED);
getResponse().setLocationRef(entry.getEntryURI().toString());
getResponse().setEntity(new JsonRepresentation("{\"entryId\":\"" + entry.getId() + "\"}"));
getResponse().getEntity().setModificationDate(entry.getModifiedDate());
getResponse().getEntity().setTag(Util.createTag(entry.getModifiedDate()));
}
} catch (AuthorizationException e) {
unauthorizedPOST();
}
}
/**
* Creates a LinkReference entry.
* @param entry a reference to a entry object.
* @return the new created entry object.
*/
private Entry createLinkReferenceEntry(Entry entry) {
if (isGraphTypeForbidden()) {
return null;
}
try {
if (parameters.get("resource") != null
&& "linkreference".equalsIgnoreCase(parameters.get("entrytype"))) {
URI resourceURI = null;
URI metadataURI = null;
resourceURI = URI.create(URLDecoder.decode(parameters.get("resource"), UTF_8));
metadataURI = URI.create(URLDecoder.decode(parameters.get("cached-external-metadata"), UTF_8));
if (parameters.containsKey("list")) {
entry = context.createLinkReference(parameters.get("id"), resourceURI, metadataURI, new URI(parameters.get("list")));
} else {
entry = context.createLinkReference(parameters.get("id"), resourceURI, metadataURI, null);
}
if (entry != null) {
setLocalMetadataGraph(entry);
setCachedMetadataGraph(entry);
setEntryGraph(entry);
if (parameters.containsKey("graphtype")) {
GraphType gt = getGraphType(parameters.get("graphtype"));
entry.setGraphType(gt);
}
if (parameters.containsKey("list")) {
try {
URI listURI = new URI((parameters.get("list")));
((ContextImpl) context).copyACL(listURI, entry);
} catch (URISyntaxException e) {
log.warn(e.getMessage());
}
}
}
return entry;
}
} catch (Exception e) {
log.warn(e.getMessage());
}
return null;
}
/**
* Creates a Reference entry.
* @param entry a reference to a entry
* @return the new created entry
*/
private Entry createReferenceEntry(Entry entry) {
if (isGraphTypeForbidden()) {
return null;
}
try {
if ((parameters.get("resource") != null) &&
(parameters.get("cached-external-metadata") != null) &&
("reference".equalsIgnoreCase(parameters.get("entrytype")))) {
URI resourceURI = null;
URI metadataURI = null;
resourceURI = URI.create(URLDecoder.decode(parameters.get("resource"), UTF_8));
metadataURI = URI.create(URLDecoder.decode(parameters.get("cached-external-metadata"), UTF_8));
if (parameters.containsKey("list")) {
entry = context.createReference(parameters.get("id"), resourceURI, metadataURI, new URI(parameters.get("list")));
} else {
entry = context.createReference(parameters.get("id"), resourceURI, metadataURI, null);
}
ResourceType rt = getResourceType(parameters.get("informationresource"));
entry.setResourceType(rt);
if (entry != null) {
setCachedMetadataGraph(entry);
setEntryGraph(entry);
if (parameters.containsKey("graphtype")) {
GraphType bt = getGraphType(parameters.get("graphtype"));
entry.setGraphType(bt);
}
if (parameters.containsKey("list")) {
try {
URI listURI = new URI((parameters.get("list")));
((ContextImpl) context).copyACL(listURI, entry);
} catch (URISyntaxException e) {
log.warn(e.getMessage());
}
}
}
return entry;
}
} catch (URISyntaxException e) {
log.warn(e.getMessage());
}
return null;
}
private ResourceType getResourceType(String rt) {
if (rt == null || !rt.equals("false")) {
return ResourceType.InformationResource;
} else {
return ResourceType.NamedResource;
}
}
private GraphType getGraphType(String gt) {
if (gt == null || "".equals(gt)) {
return GraphType.None;
}
if (gt.equalsIgnoreCase("list")) {
return GraphType.List;
}
if (gt.equalsIgnoreCase("resultlist")) {
return GraphType.ResultList;
}
if (gt.equalsIgnoreCase("context")) {
return GraphType.Context;
}
if (gt.equalsIgnoreCase("user")) {
return GraphType.User;
}
if (gt.equalsIgnoreCase("group")) {
return GraphType.Group;
}
if (gt.equalsIgnoreCase("systemcontext")) {
return GraphType.SystemContext;
}
if (gt.equalsIgnoreCase("string")) {
return GraphType.String;
}
if (gt.equalsIgnoreCase("graph")) {
return GraphType.Graph;
}
if (gt.equalsIgnoreCase("pipeline")) {
return GraphType.Pipeline;
}
if (gt.equalsIgnoreCase("pipelineresult")) {
return GraphType.PipelineResult;
}
return GraphType.None;
}
/**
* Creates a local entry
* @param entry a reference to a entry
* @return the new created entry
*/
private Entry createLocalEntry(Entry entry) {
if (isGraphTypeForbidden()) {
return null;
}
URI listURI = null;
if (parameters.containsKey("list")) {
try {
listURI = new URI((parameters.get("list")));
} catch (URISyntaxException e) {
log.warn(e.getMessage());
}
}
GraphType bt = getGraphType(parameters.get("graphtype"));
entry = context.createResource(parameters.get("id"), bt, null, listURI);
try {
if (setResource(entry)) {
setLocalMetadataGraph(entry);
setEntryGraph(entry);
if (listURI != null) {
((ContextImpl) context).copyACL(listURI, entry);
}
return entry;
} else {
context.remove(entry.getEntryURI());
return null;
}
} catch (JSONException e) {
return null;
}
}
/**
* Sets resource to an entry.
* @param entry a reference to a entry
* @return false if there is a resource provided but it cannot be interpreted.
* @throws JSONException Exception if payload is malformed
*/
private boolean setResource(Entry entry) throws JSONException {
JSONObject jsonObj = new JSONObject();
if (requestText != null && !"".equals(requestText)) {
jsonObj = new JSONObject(requestText.replaceAll("_newId", entry.getId()));
}
//If there is no resource there is nothing to do yet.
if (!jsonObj.has("resource")) {
return true;
}
switch (entry.getGraphType()) {
case User:
jsonObj = jsonObj.getJSONObject("resource");
User user = (User) entry.getResource();
if (jsonObj.has("name")) {
if (!user.setName(jsonObj.getString("name"))) {
return false;
}
} else {
return false;
}
if (jsonObj.has("homecontext")) {
Entry homeContextEntry = getCM().get(jsonObj.getString("homecontext"));
if (homeContextEntry != null) {
user.setHomeContext((Context) homeContextEntry.getResource());
}
}
if (parameters.containsKey("groupURI")) {
Entry groupEntry;
try {
groupEntry = getCM().getEntry(new URI(parameters.get("groupURI")));
Group group = (Group) groupEntry.getResource();
group.addMember(user);
} catch (URISyntaxException e) {
log.warn(e.getMessage());
}
}
break;
case Group:
jsonObj = jsonObj.getJSONObject("resource");
Group group = (Group) entry.getResource();
if (jsonObj.has("name")) {
group.setName(jsonObj.getString("name"));
}
break;
case List:
JSONArray childrenArray = (JSONArray) jsonObj.get("resource");
List list = (List)entry.getResource();
if (childrenArray != null) {
for (int i = 0; i < childrenArray.length(); i++) {
Entry child = context.get(childrenArray.getString(i));
if (child != null) {
list.addChild(child.getEntryURI());
}
}
}
break;
case Context:
jsonObj = jsonObj.getJSONObject("resource");
Context cont = (Context) entry.getResource();
if (jsonObj.has("name")) {
getCM().setName(cont.getURI(), jsonObj.getString("name"));
}
if (jsonObj.has("quota")) {
try {
cont.setQuota(jsonObj.getLong("quota"));
} catch (JSONException jsone) {
log.warn("Unable to parse new quota value: " + jsone.getMessage());
}
}
break;
case String:
StringResource stringRes = (StringResource) entry.getResource();
stringRes.setString(jsonObj.getString("resource"));
break;
case Graph:
case Pipeline:
RDFResource RDFRes = (RDFResource) entry.getResource();
Model g = RDFJSON.rdfJsonToGraph((JSONObject) jsonObj.get("resource"));
RDFRes.setGraph(g);
break;
case PipelineResult:
case None:
break;
}
return true;
}
/**
* Creates a link entry.
* @param entry a reference to a entry
* @return the new created entry
*/
private Entry createLinkEntry(Entry entry) {
if (isGraphTypeForbidden()) {
return null;
}
//check the request
URI resourceURI = null;
resourceURI = URI.create(URLDecoder.decode(parameters.get("resource"), UTF_8));
if (parameters.containsKey("list")) {
entry = context.createLink(parameters.get("id"), resourceURI, URI.create(parameters.get("list")));
} else {
entry = context.createLink(parameters.get("id"), resourceURI, null);
}
if (entry != null) {
setLocalMetadataGraph(entry);
setEntryGraph(entry);
if (parameters.containsKey("graphtype")) {
GraphType gt = getGraphType(parameters.get("graphtype"));
entry.setGraphType(gt);
}
if (parameters.containsKey("list")) {
try {
URI listURI = new URI((parameters.get("list")));
((ContextImpl) context).copyACL(listURI, entry);
} catch (URISyntaxException ignore) {
}
}
}
return entry;
}
/**
* Extracts metadata from the request and sets it as the entrys local metadata graph.
* @param entry The entry to set the metadata on.
*/
private void setLocalMetadataGraph(Entry entry) {
if (requestText == null) {
return;
}
if (EntryType.Reference.equals(entry.getEntryType())) {
return;
}
try {
JSONObject mdObj = new JSONObject(requestText.replaceAll("_newId", entry.getId()));
if (mdObj.has("metadata")) {
JSONObject obj =(JSONObject) mdObj.get("metadata");
Model graph = null;
if ((graph = RDFJSON.rdfJsonToGraph(obj)) != null) {
entry.getLocalMetadata().setGraph(graph);
}
}
} catch (JSONException e) {
log.warn(e.getMessage());
}
}
/**
* First caching of metadata.
* @param entry The entry to set the metadata on.
*/
private void setCachedMetadataGraph(Entry entry) {
if (requestText == null) {
return;
}
if (EntryType.Reference.equals(entry.getEntryType()) ||
EntryType.LinkReference.equals(entry.getEntryType())) {
try {
JSONObject mdObj = new JSONObject(requestText.replaceAll("_newId", entry.getId()));
if (mdObj.has("cached-external-metadata")) {
JSONObject obj = (JSONObject) mdObj.get("cached-external-metadata");
Model graph = null;
if ((graph = RDFJSON.rdfJsonToGraph(obj)) != null) {
entry.getCachedExternalMetadata().setGraph(graph);
}
}
} catch (JSONException e) {
log.warn(e.getMessage());
}
}
}
/**
* Extracts entryinfo from the request and sets it as the entrys local metadata graph.
* Since it assumes this is the creation step, the Entries URIs was not available
* on the client, hence the special "_newId" entryId has been used.
* Make sure this is replaced with the new entryId first.
*
* @param entry The entry to set the metadata on.
*/
private void setEntryGraph(Entry entry) {
if (requestText == null) {
return;
}
try {
JSONObject mdObj = new JSONObject(requestText.replaceAll("_newId", entry.getId()));
if (mdObj.has("info")) {
JSONObject obj = (JSONObject) mdObj.get("info");
Model graph = RDFJSON.rdfJsonToGraph(obj);
if (graph != null) {
entry.setGraph(graph);
}
}
} catch (JSONException e) {
log.warn(e.getMessage());
}
}
@Delete
public void removeRepresentations() throws ResourceException {
try {
if (context == null) {
log.debug("Unable to find context with ID " + contextId);
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return;
}
getPM().checkAuthenticatedUserAuthorized(context.getEntry(), AccessProperty.Administer);
getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
// TODO implement the removal of all entries contained by
// this context, but not the context itself
} catch(AuthorizationException e) {
unauthorizedDELETE();
}
}
/**
* Returns false if the Graph Type provided in the parameters
* cannot be used for manually created entries
*
* @return True if Graph Type is forbidden/blacklisted.
*/
private boolean isGraphTypeForbidden() {
// Pipeline results may only be created by Pipelines
if (GraphType.PipelineResult.equals(getGraphType(parameters.get("graphtype")))) {
log.debug("Pipeline results may only be created by Pipelines");
return true;
}
return false;
}
/**
* Checks whether the provided ID only contains allowed characters.
*
* @return True if supplied ID is valid.
*/
private boolean isEntryIdValid(String id) {
return Pattern.compile("^[\\w\\-]+$").matcher(id).matches();
}
}