/** * 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.resources; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import org.openrdf.OpenRDFException; import org.openrdf.model.Model; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.Rio; import org.openrdf.rio.UnsupportedRDFormatException; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.representation.ByteArrayRepresentation; import org.restlet.representation.Representation; import org.restlet.representation.Variant; import org.restlet.resource.Get; import org.restlet.resource.Post; import org.restlet.resource.ResourceException; import org.restlet.security.User; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLException; import com.github.podd.api.DanglingObjectPolicy; import com.github.podd.api.DataReferenceVerificationPolicy; import com.github.podd.api.UpdatePolicy; import com.github.podd.exception.PoddException; 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.restlet.PoddAction; import com.github.podd.restlet.RestletUtils; import com.github.podd.utils.InferredOWLOntologyID; import com.github.podd.utils.PODD; import com.github.podd.utils.PoddObjectLabel; import com.github.podd.utils.PoddWebConstants; /** * * Edit an artifact from PODD. * * @author kutila * */ public class EditArtifactResourceImpl extends AbstractPoddResourceImpl { /** * Handle an HTTP POST request submitting RDF data to update an existing artifact */ @Post("rdf|rj|json|ttl") public Representation editArtifactToRdf(final Representation entity, final Variant variant) throws ResourceException { final String artifactUri = this.getQuery().getFirstValue(PoddWebConstants.KEY_ARTIFACT_IDENTIFIER, true); if(artifactUri == null) { this.log.error("Artifact ID not submitted"); throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Artifact IRI not submitted"); } // Once we find the artifact URI, check authentication for it // immediately this.checkAuthentication(PoddAction.ARTIFACT_EDIT, PODD.VF.createURI(artifactUri)); final String versionUri = this.getQuery().getFirstValue(PoddWebConstants.KEY_ARTIFACT_VERSION_IDENTIFIER, true); if(versionUri == null) { this.log.error("Artifact Version IRI not submitted"); throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Artifact Version IRI not submitted"); } // optional multiple parameter 'objectUri' final String[] objectURIStrings = this.getQuery().getValuesArray(PoddWebConstants.KEY_OBJECT_IDENTIFIER, true); // - optional parameter 'isreplace' UpdatePolicy updatePolicy = UpdatePolicy.REPLACE_EXISTING; final String isReplaceStr = this.getQuery().getFirstValue(PoddWebConstants.KEY_EDIT_WITH_REPLACE, true); if(isReplaceStr != null && (Boolean.valueOf(isReplaceStr) == false)) { updatePolicy = UpdatePolicy.MERGE_WITH_EXISTING; } // - optional parameter 'isforce' DanglingObjectPolicy danglingObjectPolicy = DanglingObjectPolicy.REPORT; final String forceStr = this.getQuery().getFirstValue(PoddWebConstants.KEY_EDIT_WITH_FORCE, true); if(forceStr != null && Boolean.valueOf(forceStr)) { danglingObjectPolicy = DanglingObjectPolicy.FORCE_CLEAN; } // - optional parameter 'verifyfilerefs' DataReferenceVerificationPolicy fileRefVerificationPolicy = DataReferenceVerificationPolicy.DO_NOT_VERIFY; final String fileRefVerifyStr = this.getQuery().getFirstValue(PoddWebConstants.KEY_EDIT_VERIFY_FILE_REFERENCES, true); if(fileRefVerifyStr != null && Boolean.valueOf(fileRefVerifyStr)) { fileRefVerificationPolicy = DataReferenceVerificationPolicy.VERIFY; } final Collection<URI> objectUris = new ArrayList<URI>(objectURIStrings.length); for(final String nextObjectURIString : objectURIStrings) { objectUris.add(PODD.VF.createURI(nextObjectURIString)); } this.log.debug("requesting edit artifact ({}): {}, {} with isReplace {}", variant.getMediaType().getName(), artifactUri, versionUri, updatePolicy); final User user = this.getRequest().getClientInfo().getUser(); this.log.debug("authenticated user: {}", user); // - get input stream with edited RDF content InputStream inputStream = null; try { inputStream = entity.getStream(); } catch(final IOException e) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "There was a problem with the input", e); } final RDFFormat inputFormat = Rio.getParserFormatForMIMEType(entity.getMediaType().getName(), RDFFormat.RDFXML); // - prepare response final ByteArrayOutputStream output = new ByteArrayOutputStream(8096); final RDFFormat outputFormat = Rio.getWriterFormatForMIMEType(variant.getMediaType().getName(), RDFFormat.RDFXML); // - do the artifact update try { final Model model = this.getPoddArtifactManager().updateArtifact(PODD.VF.createURI(artifactUri), PODD.VF.createURI(versionUri), objectUris, inputStream, inputFormat, updatePolicy, danglingObjectPolicy, fileRefVerificationPolicy); // TODO - send detailed errors for display where possible // FIXME Change response format so that it does not resemble an // empty OWL Ontology // - write the artifact ID into response Rio.write(model, output, outputFormat); } catch(final UnmanagedArtifactVersionException e) { throw new ResourceException(Status.CLIENT_ERROR_CONFLICT, "Could not edit the given artifact", e); } catch(final UnmanagedArtifactIRIException e) { throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "Could not find the given artifact", e); } catch(final PoddException | OpenRDFException | IOException | OWLException e) { throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Could not create response", e); } return new ByteArrayRepresentation(output.toByteArray(), MediaType.valueOf(outputFormat.getDefaultMIMEType())); } /** * View the edit artifact page in HTML */ @Get("html") public Representation getEditArtifactHtml(final Representation entity) throws ResourceException { this.log.debug("getEditArtifactHtml"); // the artifact in which editing is requested final String artifactUri = this.getQuery().getFirstValue(PoddWebConstants.KEY_ARTIFACT_IDENTIFIER, true); if(artifactUri == null) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Artifact ID not submitted"); } // Podd object to be edited. NULL indicates top object is to be edited. final String objectToEdit = this.getQuery().getFirstValue(PoddWebConstants.KEY_OBJECT_IDENTIFIER, true); this.log.debug("requesting to edit artifact (HTML): {}, {}", artifactUri, objectToEdit); this.checkAuthentication(PoddAction.ARTIFACT_EDIT, PODD.VF.createURI(artifactUri)); final User user = this.getRequest().getClientInfo().getUser(); this.log.debug("authenticated user: {}", user); // validate artifact exists InferredOWLOntologyID ontologyID; try { ontologyID = this.getPoddArtifactManager().getArtifact(IRI.create(artifactUri)); } catch(final UnmanagedArtifactIRIException | UnmanagedSchemaIRIException e) { throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "Could not find the given artifact", e); } final Map<String, Object> dataModel = RestletUtils.getBaseDataModel(this.getRequest()); dataModel.put( "contentTemplate", this.getPoddApplication() .getPropertyUtil() .get(PoddWebConstants.PROPERTY_TEMPLATE_MODIFY_OBJECT, PoddWebConstants.DEFAULT_TEMPLATE_MODIFY_OBJECT)); dataModel.put("pageTitle", "Edit Artifact"); try { URI objectUri = null; // objectUri if(objectToEdit == null) { // set the top object as the object URI final List<PoddObjectLabel> topObjectLabels = this.getPoddArtifactManager().getTopObjectLabels(Arrays.asList(ontologyID)); if(topObjectLabels.size() > 0) { objectUri = topObjectLabels.get(0).getObjectURI(); } } else { objectUri = PODD.VF.createURI(objectToEdit); } if(objectUri != null) { dataModel.put("objectUri", objectUri.toString()); } // objectType final List<PoddObjectLabel> objectTypes = this.getPoddArtifactManager().getObjectTypes(ontologyID, objectUri); if(objectTypes == null || objectTypes.isEmpty()) { throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Could not determine type of object"); } // TODO: handle case where more than 1 type is found dataModel.put("objectType", objectTypes.get(0)); if(objectToEdit == null) { dataModel.put("title", "Edit Project Object"); } else { dataModel.put("title", "Edit " + objectTypes.get(0).getLabel() + " Object"); } // Parent Details final Model parentDetails = this.getPoddArtifactManager().getParentDetails(ontologyID, objectUri); if(parentDetails.size() == 1) { final Statement statement = parentDetails.iterator().next(); dataModel.put("parentUri", statement.getSubject().stringValue()); dataModel.put("parentPredicateUri", statement.getPredicate().stringValue()); } } catch(final OpenRDFException | UnmanagedSchemaIRIException | SchemaManifestException | UnsupportedRDFormatException | IOException | UnmanagedArtifactIRIException | UnmanagedArtifactVersionException | RepositoryNotFoundException e) { throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Failed to populate data model", e); } dataModel.put("artifactIri", ontologyID.getOntologyIRI().toString()); dataModel.put("versionIri", ontologyID.getVersionIRI().toString()); // Defaults to false. Set to true if multiple objects are being edited // concurrently // TODO: investigate how to use this dataModel.put("initialized", false); dataModel.put("stopRefreshKey", "Stop Refresh Key"); return RestletUtils.getHtmlRepresentation( this.getPoddApplication().getPropertyUtil() .get(PoddWebConstants.PROPERTY_TEMPLATE_BASE, PoddWebConstants.DEFAULT_TEMPLATE_BASE), dataModel, MediaType.TEXT_HTML, this.getPoddApplication().getTemplateConfiguration()); } }