/**
* PODD is an OWL ontology database used for scientific project management
*
* Copyright (C) 2009-2013 The University Of Queensland
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.podd.restlet;
import java.io.IOException;
import java.io.StringWriter;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.openrdf.OpenRDFException;
import org.openrdf.model.Model;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandler;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.Rio;
import org.openrdf.rio.UnsupportedRDFormatException;
import org.restlet.Request;
import org.restlet.data.CharacterSet;
import org.restlet.data.ClientInfo;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Parameter;
import org.restlet.data.Status;
import org.restlet.ext.freemarker.TemplateRepresentation;
import org.restlet.representation.AppendableRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.ResourceException;
import org.restlet.security.Role;
import org.restlet.security.User;
import org.semanticweb.owlapi.model.IRI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.ansell.restletutils.RestletUtilRole;
import com.github.podd.api.PoddArtifactManager;
import com.github.podd.exception.RepositoryNotFoundException;
import com.github.podd.exception.SchemaManifestException;
import com.github.podd.exception.UnmanagedArtifactIRIException;
import com.github.podd.exception.UnmanagedArtifactVersionException;
import com.github.podd.exception.UnmanagedSchemaIRIException;
import com.github.podd.utils.InferredOWLOntologyID;
import com.github.podd.utils.PODD;
import com.github.podd.utils.PoddObjectLabel;
import com.github.podd.utils.PoddObjectLabelImpl;
import com.github.podd.utils.PoddRoles;
import com.github.podd.utils.PoddUser;
import freemarker.template.Configuration;
/**
* Wraps up calls to methods that relate to Restlet items, such as Representations.
*
* @author Peter Ansell p_ansell@yahoo.com
*
* Copied from https://github.com/ansell/oas
*/
public final class RestletUtils
{
private static final Logger log = LoggerFactory.getLogger(RestletUtils.class);
public static Map<String, Object> getBaseDataModel(final Request nextRequest)
{
final ClientInfo nextClientInfo = nextRequest.getClientInfo();
final Map<String, Object> dataModel = new TreeMap<String, Object>();
dataModel.put("resourceRef", nextRequest.getResourceRef());
dataModel.put("rootRef", nextRequest.getRootRef());
dataModel.put("keywords", "podd, ontology, phenomics");
String baseUrl = nextRequest.getRootRef().toString();
if(baseUrl.endsWith("/"))
{
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
}
dataModel.put("baseUrl", baseUrl);
dataModel.put("clientInfo", nextClientInfo);
dataModel.put("isAuthenticated", nextClientInfo.isAuthenticated());
final List<Role> roles = nextClientInfo.getRoles();
final boolean isAdmin = roles.contains(PoddRoles.ADMIN.getRole());
dataModel.put("isAdmin", isAdmin);
dataModel.put("user", nextClientInfo.getUser());
final User currentUser = nextClientInfo.getUser();
if(currentUser != null)
{
dataModel.put("currentUserName", currentUser.getName());
RestletUtils.log.debug("currentUser: {}", currentUser);
RestletUtils.log.trace("currentUser.getFirstName: {}", currentUser.getFirstName());
RestletUtils.log.trace("currentUser.getLastName: {}", currentUser.getLastName());
RestletUtils.log.trace("currentUser.getName: {}", currentUser.getName());
RestletUtils.log.trace("currentUser.getIdentifier: {}", currentUser.getIdentifier());
}
else
{
RestletUtils.log.info("No currentUser logged in");
}
return dataModel;
}
/**
* Tests the parameter against a list of known true parameter values, before testing it against
* a list of known false values.
*
* @param nextParameter
* A parameter to test for its boolean value.
* @return True if the parameter looks like a true value and false otherwise.
*/
public static boolean getBooleanFromParameter(final Parameter nextParameter)
{
if(nextParameter == null)
{
throw new IllegalArgumentException("Cannot get a boolean from a null parameter");
}
final String paramValue = nextParameter.getValue();
if(paramValue == null)
{
return false;
}
boolean result = false;
// If that was true, return true
if(Boolean.valueOf(paramValue))
{
result = true;
}
else if(paramValue.equalsIgnoreCase("false"))
{
result = false;
}
else if(paramValue.equalsIgnoreCase("yes"))
{
result = true;
}
else if(paramValue.equalsIgnoreCase("no"))
{
result = false;
}
else if(paramValue.equalsIgnoreCase("y"))
{
result = true;
}
else if(paramValue.equalsIgnoreCase("n"))
{
result = false;
}
return result;
}
/**
* Returns a templated representation dedicated to HTML content.
*
* @param templateName
* The name of the template.
* @param dataModel
* The collection of data processed by the template engine.
* @param mediaType
* The media type of the representation.
* @param freemarkerConfiguration
* The FreeMarker template configuration
* @return The representation.
*/
public static Representation getHtmlRepresentation(final String templateName, final Map<String, Object> dataModel,
final MediaType mediaType, final Configuration freemarkerConfiguration) throws ResourceException
{
// The template representation is based on Freemarker.
return new TemplateRepresentation(templateName, freemarkerConfiguration, dataModel, mediaType);
}
/**
* Finds the parent details given an object, which may be null, and the artifact that it
* expected to be found in.
*
* @param artifactManager
* The artifact manager.
* @param ontologyID
* The details of the artifact.
* @param objectToView
* If this is null, the top object will be found, otherwise, the details for this
* object will be found if possible.
* @return The details for the parent object, including a label if possible and its URI.
* @throws OpenRDFException
* If there are errors getting the labels
* @throws ResourceException
* If there is more than one top object.
* @throws UnmanagedSchemaIRIException
* @throws IOException
* @throws UnsupportedRDFormatException
* @throws SchemaManifestException
* @throws UnmanagedArtifactVersionException
* @throws UnmanagedArtifactIRIException
* @throws RepositoryNotFoundException
*/
public static PoddObjectLabel getParentDetails(final PoddArtifactManager artifactManager,
final InferredOWLOntologyID ontologyID, final String objectToView) throws OpenRDFException,
ResourceException, UnmanagedSchemaIRIException, SchemaManifestException, UnsupportedRDFormatException,
IOException, UnmanagedArtifactIRIException, UnmanagedArtifactVersionException, RepositoryNotFoundException
{
PoddObjectLabel theObject = null;
if(objectToView != null && !objectToView.trim().isEmpty())
{
theObject = artifactManager.getObjectLabel(ontologyID, PODD.VF.createURI(objectToView));
}
else
{
// find and set top-object of this artifact as the object to display
final List<PoddObjectLabel> topObjectLabels = artifactManager.getTopObjectLabels(Arrays.asList(ontologyID));
if(topObjectLabels == null || topObjectLabels.size() != 1)
{
throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "There should be only 1 top object");
}
theObject = topObjectLabels.get(0);
}
return theObject;
}
public static Map<RestletUtilRole, Collection<URI>> getUsersRoles(final PoddSesameRealm realm,
final PoddUser poddUser)
{
final ConcurrentMap<RestletUtilRole, Collection<URI>> results = new ConcurrentHashMap<>();
// extract Role Mapping info (User details are ignored as multiple users are not supported
final Collection<Entry<Role, URI>> rolesWithObjectMappings = realm.getRolesWithObjectMappings(poddUser);
for(final Entry<Role, URI> entry : rolesWithObjectMappings)
{
final RestletUtilRole role = PoddRoles.getRoleByName(entry.getKey().getName());
final URI artifactUri = entry.getValue();
Collection<URI> nextObjectUris = new HashSet<>();
final Collection<URI> putIfAbsent = results.putIfAbsent(role, nextObjectUris);
if(putIfAbsent != null)
{
nextObjectUris = putIfAbsent;
}
nextObjectUris.add(artifactUri);
}
return results;
}
/**
* Retrieve the Roles that are mapped to this User, together with details of any optional mapped
* objects.
*
* @param realm
* @param poddUser
* The PODD User whose Roles are requested
*/
public static List<Entry<RestletUtilRole, PoddObjectLabel>> getUsersRoles(final PoddSesameRealm realm,
final PoddUser poddUser, final PoddArtifactManager artifactManager)
{
final Map<RestletUtilRole, Collection<URI>> roles = RestletUtils.getUsersRoles(realm, poddUser);
final List<Entry<RestletUtilRole, PoddObjectLabel>> results =
new LinkedList<Entry<RestletUtilRole, PoddObjectLabel>>();
for(final Entry<RestletUtilRole, Collection<URI>> nextEntry : roles.entrySet())
{
final RestletUtilRole nextRole = nextEntry.getKey();
for(final URI artifactUri : nextEntry.getValue())
{
PoddObjectLabel poddObjectLabel = null;
if(artifactUri != null)
{
try
{
final InferredOWLOntologyID artifact = artifactManager.getArtifact(IRI.create(artifactUri));
final List<PoddObjectLabel> topObjectLabels =
artifactManager.getTopObjectLabels(Arrays.asList(artifact));
if(!topObjectLabels.isEmpty())
{
poddObjectLabel = topObjectLabels.get(0);
}
else
{
RestletUtils.log.error("Failed to find label for artifact, returning URI as label: {}",
artifact);
poddObjectLabel = new PoddObjectLabelImpl(artifact, artifactUri, artifactUri.stringValue());
}
}
catch(OpenRDFException | UnmanagedArtifactIRIException | UnmanagedSchemaIRIException
| SchemaManifestException | UnsupportedRDFormatException | IOException
| UnmanagedArtifactVersionException | RepositoryNotFoundException e)
{
// either the artifact mapped to this Role does not
// exist, or a Label for it
// could not be retrieved
RestletUtils.log.warn("Failed to retrieve Role Mapped Object [{}]", artifactUri);
}
}
results.add(new AbstractMap.SimpleEntry<RestletUtilRole, PoddObjectLabel>(nextRole, poddObjectLabel));
}
}
return results;
}
/**
* Populate the data model with info about the parent of the current object. If the given object
* does not have a parent (i.e. is a Top Object) the data model remains unchanged.
*
* TODO: This method uses multiple API methods resulting in several SPARQL queries. Efficiency
* could be improved by either adding a new API method or modifying getParentDetails() to supply
* most of the required information.
*
* @param ontologyID
* @param objectUri
* The object whose parent details are required
* @param dataModel
* @throws OpenRDFException
* @throws UnmanagedSchemaIRIException
* @throws IOException
* @throws UnsupportedRDFormatException
* @throws SchemaManifestException
* @throws UnmanagedArtifactVersionException
* @throws UnmanagedArtifactIRIException
* @throws RepositoryNotFoundException
*/
public static Map<String, String> populateParentDetails(final PoddArtifactManager artifactManager,
final InferredOWLOntologyID ontologyID, final URI objectUri) throws OpenRDFException,
UnmanagedSchemaIRIException, SchemaManifestException, UnsupportedRDFormatException, IOException,
UnmanagedArtifactIRIException, UnmanagedArtifactVersionException, RepositoryNotFoundException
{
final Map<String, String> parentMap = new HashMap<>();
final Model parentDetails = artifactManager.getParentDetails(ontologyID, objectUri);
if(parentDetails.size() == 1)
{
final Statement statement = parentDetails.iterator().next();
final URI parentUri = (URI)statement.getSubject();
final URI parentPredicateUri = statement.getPredicate();
parentMap.put("uri", parentUri.stringValue());
// - parent's Title
String parentLabel = "Missing Title";
final PoddObjectLabel objectLabel = artifactManager.getObjectLabel(ontologyID, parentUri);
if(objectLabel != null)
{
parentLabel = objectLabel.getLabel();
}
parentMap.put("label", parentLabel);
// - parent's Type
String parentType = "Unknown Type";
final List<PoddObjectLabel> objectTypes = artifactManager.getObjectTypes(ontologyID, parentUri);
if(objectTypes.size() > 0)
{
parentType = objectTypes.get(0).getLabel();
}
parentMap.put("type", parentType);
// - parent relationship Label
String predicateLabel = "";
final PoddObjectLabel predicateLabelModel = artifactManager.getObjectLabel(ontologyID, parentPredicateUri);
if(predicateLabelModel != null)
{
predicateLabel = predicateLabelModel.getLabel();
}
parentMap.put("relationship", predicateLabel);
}
return parentMap;
}
/**
* Serialises part or all of a repository into RDF, depending on which contexts are provided.
*
* @param mimeType
* The MIME type of the serialised RDF statements.
* @param myRepository
* The repository containing the RDF statements to serialise.
* @param contexts
* 0 or more Resources identifying contexts in the repository to serialise.
* @return A Restlet Representation containing the
* @throws RepositoryException
* @throws RDFHandlerException
*/
public static Representation toRDFSerialisation(final String mimeType, final Repository myRepository,
final Resource... contexts) throws RepositoryException, RDFHandlerException
{
if(myRepository == null)
{
throw new IllegalArgumentException("Repository cannot be null");
}
// Attempt to find a writer format based on their requested mime type,
// or if that fails,
// give them RDF/XML that every RDF library can process.
final RDFFormat outputFormat = Rio.getWriterFormatForMIMEType(mimeType, RDFFormat.RDFXML);
final StringWriter writer = new StringWriter();
RepositoryConnection conn = null;
conn = myRepository.getConnection();
final RDFHandler output = Rio.createWriter(outputFormat, writer);
conn.export(output, contexts);
// TODO: find a subclass of Representation that accepts a writer
// directly, without having to
// serialise it to a string, to improve performance for large results
// sets.
final Representation result =
new AppendableRepresentation(writer.toString(), MediaType.valueOf(outputFormat.getDefaultMIMEType()),
Language.DEFAULT, CharacterSet.UTF_8);
return result;
}
/**
* Private default constructor
*/
private RestletUtils()
{
}
}