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