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