001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.kernel.api.utils; 007 008import static org.apache.jena.rdf.model.ResourceFactory.createProperty; 009import static org.apache.jena.vocabulary.RDF.Init.type; 010import static org.fcrepo.kernel.api.RdfLexicon.CREATED_BY; 011import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE; 012import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_BY; 013import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE; 014import static org.fcrepo.kernel.api.RdfLexicon.isManagedPredicate; 015import static org.fcrepo.kernel.api.RdfLexicon.isRelaxablePredicate; 016import static org.fcrepo.kernel.api.RdfLexicon.restrictedType; 017 018import java.util.Calendar; 019 020import com.google.common.net.MediaType; 021import org.fcrepo.kernel.api.RdfLexicon; 022import org.fcrepo.kernel.api.exception.MalformedRdfException; 023import org.fcrepo.kernel.api.exception.RelaxableServerManagedPropertyException; 024import org.fcrepo.kernel.api.exception.ServerManagedPropertyException; 025import org.fcrepo.kernel.api.exception.ServerManagedTypeException; 026 027import org.apache.jena.datatypes.xsd.XSDDateTime; 028import org.apache.jena.graph.Triple; 029import org.apache.jena.rdf.model.Property; 030import org.apache.jena.rdf.model.RDFNode; 031import org.apache.jena.rdf.model.Resource; 032import org.apache.jena.rdf.model.Statement; 033 034/** 035 * Some server managed triples can have the prohibition on user-management overridden. While 036 * the server still updates them implicitly, it may be possible in some cases for a user 037 * request to override them. 038 * 039 * @author Mike Durbin 040 * @author whikloj 041 */ 042public class RelaxedPropertiesHelper { 043 044 /** 045 * Gets the created date (if any) that was included in the statements. 046 * @param resource the resource we are looking for properties of 047 * @return the date that should be set for the CREATED_DATE or null if it should be 048 * untouched 049 */ 050 public static Calendar getCreatedDate(final Resource resource) { 051 final Iterable<Statement> iter = getIterable(resource, CREATED_DATE); 052 return extractSingleCalendarValue(iter, CREATED_DATE); 053 } 054 055 /** 056 * Gets the created by user (if any) that is included within the statements. 057 * @param resource the resource we are looking for properties of 058 * @return the string that should be set for the CREATED_BY or null if it should be 059 * untouched 060 */ 061 public static String getCreatedBy(final Resource resource) { 062 final Iterable<Statement> iter = getIterable(resource, CREATED_BY); 063 return extractSingleStringValue(iter, CREATED_BY); 064 } 065 066 /** 067 * Gets the modified date (if any) that was included within the statements. 068 * @param resource the resource we are looking for properties of 069 * @return the date that should be set for the LAST_MODIFIED_DATE or null if it should be 070 * untouched 071 */ 072 public static Calendar getModifiedDate(final Resource resource) { 073 final Iterable<Statement> iter = getIterable(resource, LAST_MODIFIED_DATE); 074 return extractSingleCalendarValue(iter, LAST_MODIFIED_DATE); 075 } 076 077 /** 078 * Gets the modified by user (if any) that was included within the statements. 079 * @param resource the resource we are looking for properties of 080 * @return the string that should be set for the MODIFIED_BY or null if it should be 081 * untouched 082 */ 083 public static String getModifiedBy(final Resource resource) { 084 final Iterable<Statement> iter = getIterable(resource, LAST_MODIFIED_BY); 085 return extractSingleStringValue(iter, LAST_MODIFIED_BY); 086 } 087 088 private static String extractSingleStringValue(final Iterable<Statement> statements, 089 final Property predicate) { 090 String textValue = null; 091 for (final Statement added : statements) { 092 if (textValue == null) { 093 textValue = added.getObject().asLiteral().getString(); 094 } else { 095 throw new MalformedRdfException(predicate + " may only appear once!"); 096 } 097 } 098 return textValue; 099 } 100 101 private static Calendar extractSingleCalendarValue(final Iterable<Statement> statements, final Property predicate) { 102 Calendar cal = null; 103 for (final Statement added : statements) { 104 if (cal == null) { 105 try { 106 cal = RelaxedPropertiesHelper.parseExpectedXsdDateTimeValue(added.getObject()); 107 } catch (final IllegalArgumentException e) { 108 throw new MalformedRdfException("Expected a xsd:datetime for " + predicate, e); 109 } 110 } else { 111 throw new MalformedRdfException(predicate + " may only appear once!"); 112 } 113 } 114 return cal; 115 } 116 117 /** 118 * Parses an RDFNode that is expected to be a literal of type xsd:dateTime into a Java Calendar 119 * object. 120 * @param node a node representing an xsd:dateTime literal 121 * @return a Calendar representation of the expressed dateTime 122 */ 123 private static Calendar parseExpectedXsdDateTimeValue(final RDFNode node) { 124 final Object value = node.asLiteral().getValue(); 125 if (value instanceof XSDDateTime) { 126 return ((XSDDateTime) value).asCalendar(); 127 } else { 128 throw new IllegalArgumentException("Expected an xsd:dateTime!"); 129 } 130 } 131 132 private static Iterable<Statement> getIterable(final Resource resource, final Property predicate) { 133 final var iterator = resource.listProperties(predicate); 134 return () -> iterator; 135 } 136 137 /** 138 * Several tests for invalid or disallowed RDF statements. 139 * @param triple the triple to check. 140 */ 141 public static void checkTripleForDisallowed(final Triple triple) { 142 if (triple.getPredicate().equals(type().asNode()) && !triple.getObject().isVariable() && 143 !triple.getObject().isURI()) { 144 // The object of a rdf:type triple is not a variable and not a URI. 145 throw new MalformedRdfException( 146 String.format("Invalid rdf:type: %s", triple.getObject())); 147 } else if (restrictedType.test(triple)) { 148 // The object of a rdf:type triple has a restricted namespace. 149 throw new ServerManagedTypeException( 150 String.format("The server managed type (%s) cannot be modified by the client.", 151 triple.getObject())); 152 } else if (isManagedPredicate.test(createProperty(triple.getPredicate().getURI()))) { 153 // The predicate is server managed. 154 final var message = String.format("The server managed predicate (%s) cannot be modified by the client.", 155 triple.getPredicate()); 156 if (isRelaxablePredicate.test(createProperty(triple.getPredicate().getURI()))) { 157 // It is a relaxable predicate so throw the appropriate exception. 158 throw new RelaxableServerManagedPropertyException(message); 159 } 160 throw new ServerManagedPropertyException(message); 161 } else if (triple.getPredicate().toString().equals(RdfLexicon.HAS_MIME_TYPE.getURI())) { 162 final String mimetype = triple.getObject().toString(false); 163 if (!mimetype.startsWith("?")) { 164 try { 165 MediaType.parse(mimetype); 166 } catch (final Exception ex) { 167 throw new MalformedRdfException("Invalid value for '" + RdfLexicon.HAS_MIME_TYPE + 168 "' encountered : " + mimetype, ex); 169 } 170 171 } 172 } 173 } 174 175 // Prevent instantiation 176 private RelaxedPropertiesHelper() { 177 178 } 179}