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.http.api;
019
020
021import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel;
022import static com.hp.hpl.jena.vocabulary.RDF.type;
023import static java.util.EnumSet.of;
024import static java.util.stream.Stream.concat;
025import static java.util.stream.Stream.empty;
026import static javax.ws.rs.core.HttpHeaders.CACHE_CONTROL;
027import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
028import static javax.ws.rs.core.Response.ok;
029import static javax.ws.rs.core.Response.status;
030import static javax.ws.rs.core.Response.temporaryRedirect;
031import static javax.ws.rs.core.Response.Status.PARTIAL_CONTENT;
032import static javax.ws.rs.core.Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE;
033import static org.apache.commons.lang3.StringUtils.isBlank;
034import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
035import static org.fcrepo.kernel.api.FedoraTypes.LDP_BASIC_CONTAINER;
036import static org.fcrepo.kernel.api.FedoraTypes.LDP_DIRECT_CONTAINER;
037import static org.fcrepo.kernel.api.FedoraTypes.LDP_INDIRECT_CONTAINER;
038import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER;
039import static org.fcrepo.kernel.api.RdfLexicon.CONTAINER;
040import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER;
041import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER;
042import static org.fcrepo.kernel.api.RdfLexicon.LDP_NAMESPACE;
043import static org.fcrepo.kernel.api.RdfLexicon.isManagedNamespace;
044import static org.fcrepo.kernel.api.RdfLexicon.isManagedPredicateURI;
045import static org.fcrepo.kernel.api.RequiredRdfContext.EMBED_RESOURCES;
046import static org.fcrepo.kernel.api.RequiredRdfContext.INBOUND_REFERENCES;
047import static org.fcrepo.kernel.api.RequiredRdfContext.LDP_CONTAINMENT;
048import static org.fcrepo.kernel.api.RequiredRdfContext.LDP_MEMBERSHIP;
049import static org.fcrepo.kernel.api.RequiredRdfContext.MINIMAL;
050import static org.fcrepo.kernel.api.RequiredRdfContext.PROPERTIES;
051import static org.fcrepo.kernel.api.RequiredRdfContext.SERVER_MANAGED;
052
053import java.io.IOException;
054import java.io.InputStream;
055import java.net.URI;
056import java.net.URISyntaxException;
057import java.util.ArrayList;
058import java.util.Date;
059import java.util.List;
060import java.util.Set;
061import java.util.function.Predicate;
062import java.util.stream.Stream;
063
064import javax.inject.Inject;
065import javax.jcr.Session;
066import javax.servlet.http.HttpServletResponse;
067import javax.ws.rs.BadRequestException;
068import javax.ws.rs.BeanParam;
069import javax.ws.rs.WebApplicationException;
070import javax.ws.rs.core.CacheControl;
071import javax.ws.rs.core.Context;
072import javax.ws.rs.core.EntityTag;
073import javax.ws.rs.core.Link;
074import javax.ws.rs.core.MediaType;
075import javax.ws.rs.core.Request;
076import javax.ws.rs.core.Response;
077
078import com.google.common.annotations.VisibleForTesting;
079import org.apache.jena.atlas.RuntimeIOException;
080import org.apache.jena.riot.Lang;
081import org.apache.jena.riot.RiotException;
082import org.fcrepo.http.commons.api.HttpHeaderInjector;
083import org.fcrepo.http.commons.api.rdf.HttpTripleUtil;
084import org.fcrepo.http.commons.domain.MultiPrefer;
085import org.fcrepo.http.commons.domain.PreferTag;
086import org.fcrepo.http.commons.domain.Range;
087import org.fcrepo.http.commons.domain.ldp.LdpPreferTag;
088import org.fcrepo.http.commons.responses.RangeRequestInputStream;
089import org.fcrepo.http.commons.responses.RdfNamespacedStream;
090import org.fcrepo.kernel.api.RdfStream;
091import org.fcrepo.kernel.api.TripleCategory;
092import org.fcrepo.kernel.api.exception.InvalidChecksumException;
093import org.fcrepo.kernel.api.exception.MalformedRdfException;
094import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
095import org.fcrepo.kernel.api.models.Container;
096import org.fcrepo.kernel.api.models.FedoraBinary;
097import org.fcrepo.kernel.api.models.FedoraResource;
098import org.fcrepo.kernel.api.models.NonRdfSourceDescription;
099import org.fcrepo.kernel.api.rdf.DefaultRdfStream;
100import org.fcrepo.kernel.api.services.policy.StoragePolicyDecisionPoint;
101import org.fcrepo.kernel.modeshape.services.TransactionServiceImpl;
102import org.glassfish.jersey.media.multipart.ContentDisposition;
103import org.jvnet.hk2.annotations.Optional;
104
105import com.fasterxml.jackson.core.JsonParseException;
106import com.hp.hpl.jena.graph.Triple;
107import com.hp.hpl.jena.rdf.model.Model;
108
109/**
110 * An abstract class that sits between AbstractResource and any resource that
111 * wishes to share the routines for building responses containing binary
112 * content.
113 *
114 * @author Mike Durbin
115 * @author ajs6f
116 */
117public abstract class ContentExposingResource extends FedoraBaseResource {
118
119    public static final MediaType MESSAGE_EXTERNAL_BODY = MediaType.valueOf("message/external-body");
120
121    @Context protected Request request;
122    @Context protected HttpServletResponse servletResponse;
123
124    @Inject
125    @Optional
126    private HttpTripleUtil httpTripleUtil;
127
128    @Inject
129    @Optional
130    private HttpHeaderInjector httpHeaderInject;
131
132    @BeanParam
133    protected MultiPrefer prefer;
134
135    @Inject
136    @Optional
137    StoragePolicyDecisionPoint storagePolicyDecisionPoint;
138
139    protected FedoraResource resource;
140
141    @Inject
142    protected  PathLockManager lockManager;
143
144    private static final Predicate<Triple> IS_MANAGED_TYPE = t -> t.getPredicate().equals(type.asNode()) &&
145            isManagedNamespace.test(t.getObject().getNameSpace());
146    private static final Predicate<Triple> IS_MANAGED_TRIPLE = IS_MANAGED_TYPE
147        .or(t -> isManagedPredicateURI.test(t.getPredicate().getURI()));
148
149    protected abstract String externalPath();
150
151    protected Response getContent(final String rangeValue,
152                                  final RdfStream rdfStream) throws IOException {
153        return getContent(rangeValue, -1, rdfStream);
154    }
155
156    /**
157     * This method returns an HTTP response with content body appropriate to the following arguments.
158     *
159     * @param rangeValue starting and ending byte offsets, see {@link Range}
160     * @param limit is the number of child resources returned in the response, -1 for all
161     * @param rdfStream to which response RDF will be concatenated
162     * @return HTTP response
163     * @throws IOException in case of error extracting content
164     */
165    protected Response getContent(final String rangeValue,
166                                  final int limit,
167                                  final RdfStream rdfStream) throws IOException {
168
169        final RdfNamespacedStream outputStream;
170
171        if (resource() instanceof FedoraBinary) {
172
173            final MediaType mediaType = MediaType.valueOf(((FedoraBinary) resource()).getMimeType());
174
175            if (MESSAGE_EXTERNAL_BODY.isCompatible(mediaType) && mediaType.getParameters().containsKey(
176                    "access-type") && mediaType.getParameters().get("access-type").equals("URL") && mediaType
177                    .getParameters().containsKey("URL")) {
178                return temporaryRedirect(URI.create(mediaType.getParameters().get("URL"))).build();
179            }
180
181            return getBinaryContent(rangeValue);
182        } else {
183            outputStream = new RdfNamespacedStream(
184                    new DefaultRdfStream(rdfStream.topic(), concat(rdfStream,
185                        getResourceTriples(limit))),
186                    namespaceService.getNamespaces(session()));
187            if (prefer != null) {
188                prefer.getReturn().addResponseHeaders(servletResponse);
189            }
190        }
191        servletResponse.addHeader("Vary", "Accept, Range, Accept-Encoding, Accept-Language");
192
193        return ok(outputStream).build();
194    }
195
196    protected RdfStream getResourceTriples() {
197        return getResourceTriples(-1);
198    }
199
200    /**
201     * This method returns a stream of RDF triples associated with this target resource
202     *
203     * @param limit is the number of child resources returned in the response, -1 for all
204     * @return {@link RdfStream}
205     */
206    protected RdfStream getResourceTriples(final int limit) {
207        // use the thing described, not the description, for the subject of descriptive triples
208        if (resource() instanceof NonRdfSourceDescription) {
209            resource = resource().getDescribedResource();
210        }
211        final PreferTag returnPreference;
212
213        if (prefer != null && prefer.hasReturn()) {
214            returnPreference = prefer.getReturn();
215        } else if (prefer != null && prefer.hasHandling()) {
216            returnPreference = prefer.getHandling();
217        } else {
218            returnPreference = PreferTag.emptyTag();
219        }
220
221        final LdpPreferTag ldpPreferences = new LdpPreferTag(returnPreference);
222
223        final Predicate<Triple> tripleFilter = ldpPreferences.prefersServerManaged() ? x -> true :
224            IS_MANAGED_TRIPLE.negate();
225
226        final List<Stream<Triple>> streams = new ArrayList<>();
227
228
229        if (returnPreference.getValue().equals("minimal")) {
230            streams.add(getTriples(of(PROPERTIES, MINIMAL)).filter(tripleFilter));
231
232            if (ldpPreferences.prefersServerManaged()) {
233                streams.add(getTriples(of(SERVER_MANAGED, MINIMAL)));
234            }
235        } else {
236            streams.add(getTriples(PROPERTIES).filter(tripleFilter));
237
238            // Additional server-managed triples about this resource
239            if (ldpPreferences.prefersServerManaged()) {
240                streams.add(getTriples(SERVER_MANAGED));
241            }
242
243            // containment triples about this resource
244            if (ldpPreferences.prefersContainment()) {
245                if (limit == -1) {
246                    streams.add(getTriples(LDP_CONTAINMENT));
247                } else {
248                    streams.add(getTriples(LDP_CONTAINMENT).limit(limit));
249                }
250            }
251
252            // LDP container membership triples for this resource
253            if (ldpPreferences.prefersMembership()) {
254                streams.add(getTriples(LDP_MEMBERSHIP));
255            }
256
257            // Include inbound references to this object
258            if (ldpPreferences.prefersReferences()) {
259                streams.add(getTriples(INBOUND_REFERENCES));
260            }
261
262            // Embed the children of this object
263            if (ldpPreferences.prefersEmbed()) {
264                streams.add(getTriples(EMBED_RESOURCES));
265            }
266        }
267
268        final RdfStream rdfStream = new DefaultRdfStream(
269                asNode(resource()), streams.stream().reduce(empty(), Stream::concat));
270
271        if (httpTripleUtil != null && ldpPreferences.prefersServerManaged()) {
272            return httpTripleUtil.addHttpComponentModelsForResourceToStream(rdfStream, resource(), uriInfo,
273                    translator());
274        }
275
276        return rdfStream;
277    }
278
279    /**
280     * Get the binary content of a datastream
281     *
282     * @param rangeValue the range value
283     * @return Binary blob
284     * @throws IOException if io exception occurred
285     */
286    protected Response getBinaryContent(final String rangeValue)
287            throws IOException {
288            final FedoraBinary binary = (FedoraBinary)resource();
289
290            // we include an explicit etag, because the default behavior is to use the JCR node's etag, not
291            // the jcr:content node digest. The etag is only included if we are not within a transaction.
292            final String txId = TransactionServiceImpl.getCurrentTransactionId(session());
293            if (txId == null) {
294                checkCacheControlHeaders(request, servletResponse, binary, session());
295            }
296            final CacheControl cc = new CacheControl();
297            cc.setMaxAge(0);
298            cc.setMustRevalidate(true);
299            Response.ResponseBuilder builder;
300
301            if (rangeValue != null && rangeValue.startsWith("bytes")) {
302
303                final Range range = Range.convert(rangeValue);
304
305                final long contentSize = binary.getContentSize();
306
307                final String endAsString;
308
309                if (range.end() == -1) {
310                    endAsString = Long.toString(contentSize - 1);
311                } else {
312                    endAsString = Long.toString(range.end());
313                }
314
315                final String contentRangeValue =
316                        String.format("bytes %s-%s/%s", range.start(),
317                                endAsString, contentSize);
318
319                if (range.end() > contentSize ||
320                        (range.end() == -1 && range.start() > contentSize)) {
321
322                    builder = status(REQUESTED_RANGE_NOT_SATISFIABLE)
323                            .header("Content-Range", contentRangeValue);
324                } else {
325                    @SuppressWarnings("resource")
326                    final RangeRequestInputStream rangeInputStream =
327                            new RangeRequestInputStream(binary.getContent(), range.start(), range.size());
328
329                    builder = status(PARTIAL_CONTENT).entity(rangeInputStream)
330                            .header("Content-Range", contentRangeValue);
331                }
332
333            } else {
334                @SuppressWarnings("resource")
335                final InputStream content = binary.getContent();
336                builder = ok(content);
337            }
338
339
340            // we set the content-type explicitly to avoid content-negotiation from getting in the way
341            return builder.type(binary.getMimeType())
342                    .cacheControl(cc)
343                    .build();
344
345        }
346
347    protected RdfStream getTriples(final Set<? extends TripleCategory> x) {
348        return getTriples(resource(), x);
349    }
350
351    protected RdfStream getTriples(final FedoraResource resource, final Set<? extends TripleCategory> x) {
352        return resource.getTriples(translator(), x);
353    }
354
355    protected RdfStream getTriples(final TripleCategory x) {
356        return getTriples(resource(), x);
357    }
358
359    protected RdfStream getTriples(final FedoraResource resource, final TripleCategory x) {
360        return resource.getTriples(translator(), x);
361    }
362
363    protected URI getUri(final FedoraResource resource) {
364        try {
365            final String uri = translator().reverse().convert(resource).getURI();
366            return new URI(uri);
367        } catch (final URISyntaxException e) {
368            throw new BadRequestException(e);
369        }
370    }
371
372    protected FedoraResource resource() {
373        if (resource == null) {
374            resource = getResourceFromPath(externalPath());
375        }
376        return resource;
377    }
378
379    protected void addResourceLinkHeaders(final FedoraResource resource) {
380        addResourceLinkHeaders(resource, false);
381    }
382
383    protected void addResourceLinkHeaders(final FedoraResource resource, final boolean includeAnchor) {
384        if (resource instanceof NonRdfSourceDescription) {
385            final URI uri = getUri(resource.getDescribedResource());
386            final Link link = Link.fromUri(uri).rel("describes").build();
387            servletResponse.addHeader("Link", link.toString());
388        } else if (resource instanceof FedoraBinary) {
389            final URI uri = getUri(resource.getDescription());
390            final Link.Builder builder = Link.fromUri(uri).rel("describedby");
391
392            if (includeAnchor) {
393                builder.param("anchor", getUri(resource).toString());
394            }
395            servletResponse.addHeader("Link", builder.build().toString());
396        }
397    }
398
399    /**
400     * Add any resource-specific headers to the response
401     * @param resource the resource
402     */
403    protected void addResourceHttpHeaders(final FedoraResource resource) {
404        if (resource instanceof FedoraBinary) {
405
406            final FedoraBinary binary = (FedoraBinary)resource;
407            final ContentDisposition contentDisposition = ContentDisposition.type("attachment")
408                    .fileName(binary.getFilename())
409                    .creationDate(binary.getCreatedDate())
410                    .modificationDate(binary.getLastModifiedDate())
411                    .size(binary.getContentSize())
412                    .build();
413
414            servletResponse.addHeader("Content-Type", binary.getMimeType());
415            servletResponse.addHeader("Content-Length", String.valueOf(binary.getContentSize()));
416            servletResponse.addHeader("Accept-Ranges", "bytes");
417            servletResponse.addHeader("Content-Disposition", contentDisposition.toString());
418        }
419
420        servletResponse.addHeader("Link", "<" + LDP_NAMESPACE + "Resource>;rel=\"type\"");
421
422        if (resource instanceof FedoraBinary) {
423            servletResponse.addHeader("Link", "<" + LDP_NAMESPACE + "NonRDFSource>;rel=\"type\"");
424        } else if (resource instanceof Container) {
425            servletResponse.addHeader("Link", "<" + CONTAINER.getURI() + ">;rel=\"type\"");
426            if (resource.hasType(LDP_BASIC_CONTAINER)) {
427                servletResponse.addHeader("Link", "<" + BASIC_CONTAINER.getURI() + ">;rel=\"type\"");
428            } else if (resource.hasType(LDP_DIRECT_CONTAINER)) {
429                servletResponse.addHeader("Link", "<" + DIRECT_CONTAINER.getURI() + ">;rel=\"type\"");
430            } else if (resource.hasType(LDP_INDIRECT_CONTAINER)) {
431                servletResponse.addHeader("Link", "<" + INDIRECT_CONTAINER.getURI() + ">;rel=\"type\"");
432            } else {
433                servletResponse.addHeader("Link", "<" + BASIC_CONTAINER.getURI() + ">;rel=\"type\"");
434            }
435        } else {
436            servletResponse.addHeader("Link", "<" + LDP_NAMESPACE + "RDFSource>;rel=\"type\"");
437        }
438        if (httpHeaderInject != null) {
439            httpHeaderInject.addHttpHeaderToResponseStream(servletResponse, uriInfo, resource());
440        }
441
442    }
443
444    /**
445     * Evaluate the cache control headers for the request to see if it can be served from
446     * the cache.
447     *
448     * @param request the request
449     * @param servletResponse the servlet response
450     * @param resource the fedora resource
451     * @param session the session
452     */
453    protected static void checkCacheControlHeaders(final Request request,
454                                                   final HttpServletResponse servletResponse,
455                                                   final FedoraResource resource,
456                                                   final Session session) {
457        evaluateRequestPreconditions(request, servletResponse, resource, session, true);
458        addCacheControlHeaders(servletResponse, resource, session);
459    }
460
461    /**
462     * Add ETag and Last-Modified cache control headers to the response
463     * <p>
464     * Note: In this implementation, the HTTP headers for ETags and Last-Modified dates are swapped
465     * for fedora:Binary resources and their descriptions. Here, we are drawing a distinction between
466     * the HTTP resource and the LDP resource. As an HTTP resource, the last-modified header should
467     * reflect when the resource at the given URL was last changed. With fedora:Binary resources and
468     * their descriptions, this is a little complicated, for the descriptions have, as their subjects,
469     * the binary itself. And the fedora:lastModified property produced by that NonRdfSourceDescription
470     * refers to the last-modified date of the binary -- not the last-modified date of the
471     * NonRdfSourceDescription.
472     * </p>
473     * @param servletResponse the servlet response
474     * @param resource the fedora resource
475     * @param session the session
476     */
477    protected static void addCacheControlHeaders(final HttpServletResponse servletResponse,
478                                                 final FedoraResource resource,
479                                                 final Session session) {
480
481        final String txId = TransactionServiceImpl.getCurrentTransactionId(session);
482        if (txId != null) {
483            // Do not add caching headers if in a transaction
484            return;
485        }
486
487        final EntityTag etag;
488        final Date date;
489
490        // See note about this code in the javadoc above.
491        if (resource instanceof FedoraBinary) {
492            // Use a strong ETag for LDP-NR
493            etag = new EntityTag(resource.getDescription().getEtagValue());
494            date = resource.getDescription().getLastModifiedDate();
495        } else {
496            // Use a weak ETag for the LDP-RS
497            etag = new EntityTag(resource.getDescribedResource().getEtagValue(), true);
498            date = resource.getDescribedResource().getLastModifiedDate();
499        }
500
501        if (!etag.getValue().isEmpty()) {
502            servletResponse.addHeader("ETag", etag.toString());
503        }
504
505        if (date != null) {
506            servletResponse.addDateHeader("Last-Modified", date.getTime());
507        }
508    }
509
510    /**
511     * Evaluate request preconditions to ensure the resource is the expected state
512     * @param request the request
513     * @param servletResponse the servlet response
514     * @param resource the resource
515     * @param session the session
516     */
517    protected static void evaluateRequestPreconditions(final Request request,
518                                                       final HttpServletResponse servletResponse,
519                                                       final FedoraResource resource,
520                                                       final Session session) {
521        evaluateRequestPreconditions(request, servletResponse, resource, session, false);
522    }
523
524    @VisibleForTesting
525    static void evaluateRequestPreconditions(final Request request,
526                                                     final HttpServletResponse servletResponse,
527                                                     final FedoraResource resource,
528                                                     final Session session,
529                                                     final boolean cacheControl) {
530
531        final String txId = TransactionServiceImpl.getCurrentTransactionId(session);
532        if (txId != null) {
533            // Force cache revalidation if in a transaction
534            servletResponse.addHeader(CACHE_CONTROL, "must-revalidate");
535            servletResponse.addHeader(CACHE_CONTROL, "max-age=0");
536            return;
537        }
538
539        final EntityTag etag;
540        final Date date;
541        final Date roundedDate = new Date();
542
543        // See the related note about the next block of code in the
544        // ContentExposingResource::addCacheControlHeaders method
545        if (resource instanceof FedoraBinary) {
546            // Use a strong ETag for the LDP-NR
547            etag = new EntityTag(resource.getDescription().getEtagValue());
548            date = resource.getDescription().getLastModifiedDate();
549        } else {
550            // Use a strong ETag for the LDP-RS when validating If-(None)-Match headers
551            etag = new EntityTag(resource.getDescribedResource().getEtagValue());
552            date = resource.getDescribedResource().getLastModifiedDate();
553        }
554
555        if (date != null) {
556            roundedDate.setTime(date.getTime() - date.getTime() % 1000);
557        }
558
559        Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
560        if ( builder == null ) {
561            builder = request.evaluatePreconditions(roundedDate);
562        }
563
564        if (builder != null && cacheControl ) {
565            final CacheControl cc = new CacheControl();
566            cc.setMaxAge(0);
567            cc.setMustRevalidate(true);
568            // here we are implicitly emitting a 304
569            // the exception is not an error, it's genuinely
570            // an exceptional condition
571            builder = builder.cacheControl(cc).lastModified(date).tag(etag);
572        }
573        if (builder != null) {
574            throw new WebApplicationException(builder.build());
575        }
576    }
577
578    protected static MediaType getSimpleContentType(final MediaType requestContentType) {
579        return requestContentType != null ? new MediaType(requestContentType.getType(), requestContentType.getSubtype())
580                : APPLICATION_OCTET_STREAM_TYPE;
581    }
582
583    protected static boolean isRdfContentType(final String contentTypeString) {
584        return contentTypeToLang(contentTypeString) != null;
585    }
586
587    protected void replaceResourceBinaryWithStream(final FedoraBinary result,
588                                                   final InputStream requestBodyStream,
589                                                   final ContentDisposition contentDisposition,
590                                                   final MediaType contentType,
591                                                   final String checksum) throws InvalidChecksumException {
592        final URI checksumURI = checksumURI(checksum);
593        final String originalFileName = contentDisposition != null ? contentDisposition.getFileName() : "";
594        final String originalContentType = contentType != null ? contentType.toString() : "";
595
596        result.setContent(requestBodyStream,
597                originalContentType,
598                checksumURI,
599                originalFileName,
600                storagePolicyDecisionPoint);
601    }
602
603    protected void replaceResourceWithStream(final FedoraResource resource,
604                                             final InputStream requestBodyStream,
605                                             final MediaType contentType,
606                                             final RdfStream resourceTriples) throws MalformedRdfException {
607        final Lang format = contentTypeToLang(contentType.toString());
608
609        final Model inputModel = createDefaultModel();
610        try {
611            inputModel.read(requestBodyStream, getUri(resource).toString(), format.getName().toUpperCase());
612
613        } catch (final RiotException e) {
614            throw new BadRequestException("RDF was not parsable: " + e.getMessage(), e);
615
616        } catch (final RuntimeIOException e) {
617            if (e.getCause() instanceof JsonParseException) {
618                throw new MalformedRdfException(e.getCause());
619            }
620            throw new RepositoryRuntimeException(e);
621        }
622
623        resource.replaceProperties(translator(), inputModel, resourceTriples);
624    }
625
626    protected void patchResourcewithSparql(final FedoraResource resource,
627            final String requestBody,
628            final RdfStream resourceTriples) {
629        resource.getDescribedResource().updateProperties(translator(), requestBody, resourceTriples);
630    }
631
632    /**
633     * Create a checksum URI object.
634     **/
635    private static URI checksumURI( final String checksum ) {
636        if (!isBlank(checksum)) {
637            return URI.create(checksum);
638        }
639        return null;
640    }
641}