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