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