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}