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