001/* 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.http.api; 017 018 019import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel; 020import static com.hp.hpl.jena.vocabulary.RDF.type; 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.riot.RDFLanguages.contentTypeToLang; 033 034import static org.fcrepo.kernel.api.FedoraTypes.LDP_BASIC_CONTAINER; 035import static org.fcrepo.kernel.api.FedoraTypes.LDP_DIRECT_CONTAINER; 036import static org.fcrepo.kernel.api.FedoraTypes.LDP_INDIRECT_CONTAINER; 037import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 038import static org.fcrepo.kernel.api.RdfLexicon.CONTAINER; 039import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER; 040import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER; 041import static org.fcrepo.kernel.api.RdfLexicon.LDP_NAMESPACE; 042import static org.fcrepo.kernel.api.RdfLexicon.isManagedNamespace; 043import static org.fcrepo.kernel.api.RdfLexicon.isManagedPredicateURI; 044import static org.fcrepo.kernel.api.RequiredRdfContext.EMBED_RESOURCES; 045import static org.fcrepo.kernel.api.RequiredRdfContext.INBOUND_REFERENCES; 046import static org.fcrepo.kernel.api.RequiredRdfContext.LDP_CONTAINMENT; 047import static org.fcrepo.kernel.api.RequiredRdfContext.LDP_MEMBERSHIP; 048import static org.fcrepo.kernel.api.RequiredRdfContext.MINIMAL; 049import static org.fcrepo.kernel.api.RequiredRdfContext.PROPERTIES; 050import static org.fcrepo.kernel.api.RequiredRdfContext.SERVER_MANAGED; 051import static org.fcrepo.kernel.modeshape.utils.NamespaceTools.getNamespaces; 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 com.fasterxml.jackson.core.JsonParseException; 079import org.apache.jena.atlas.RuntimeIOException; 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.exception.InvalidChecksumException; 090import org.fcrepo.kernel.api.exception.MalformedRdfException; 091import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 092import org.fcrepo.kernel.api.models.Container; 093import org.fcrepo.kernel.api.models.FedoraBinary; 094import org.fcrepo.kernel.api.models.FedoraResource; 095import org.fcrepo.kernel.api.models.NonRdfSource; 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 String contentTypeString = ((FedoraBinary) resource()).getMimeType(); 171 172 final Lang lang = contentTypeToLang(contentTypeString); 173 174 if (!contentTypeString.equals("text/plain") && lang != null) { 175 176 final String format = lang.getName().toUpperCase(); 177 178 try (final InputStream content = ((FedoraBinary) resource()).getContent()) { 179 180 final Model inputModel = createDefaultModel() 181 .read(content, (resource()).toString(), format); 182 outputStream = new RdfNamespacedStream( 183 new DefaultRdfStream(rdfStream.topic(), concat(rdfStream, 184 DefaultRdfStream.fromModel(rdfStream.topic(), inputModel))), 185 getNamespaces(session())); 186 } 187 188 189 } else { 190 191 final MediaType mediaType = MediaType.valueOf(contentTypeString); 192 if (MESSAGE_EXTERNAL_BODY.isCompatible(mediaType) 193 && mediaType.getParameters().containsKey("access-type") 194 && mediaType.getParameters().get("access-type").equals("URL") 195 && mediaType.getParameters().containsKey("URL") ) { 196 try { 197 return temporaryRedirect(new URI(mediaType.getParameters().get("URL"))).build(); 198 } catch (final URISyntaxException e) { 199 throw new RepositoryRuntimeException(e); 200 } 201 } 202 return getBinaryContent(rangeValue); 203 } 204 205 } else { 206 outputStream = new RdfNamespacedStream( 207 new DefaultRdfStream(rdfStream.topic(), concat(rdfStream, 208 getResourceTriples(limit))), 209 getNamespaces(session())); 210 if (prefer != null) { 211 prefer.getReturn().addResponseHeaders(servletResponse); 212 } 213 } 214 servletResponse.addHeader("Vary", "Accept, Range, Accept-Encoding, Accept-Language"); 215 216 return ok(outputStream).build(); 217 } 218 219 protected RdfStream getResourceTriples() { 220 return getResourceTriples(-1); 221 } 222 223 /** 224 * This method returns a stream of RDF triples associated with this target resource 225 * 226 * @param limit is the number of child resources returned in the response, -1 for all 227 * @return {@link RdfStream} 228 */ 229 protected RdfStream getResourceTriples(final int limit) { 230 // use the thing described, not the description, for the subject of descriptive triples 231 if (resource() instanceof NonRdfSourceDescription) { 232 resource = ((NonRdfSourceDescription) resource()).getDescribedResource(); 233 } 234 final PreferTag returnPreference; 235 236 if (prefer != null && prefer.hasReturn()) { 237 returnPreference = prefer.getReturn(); 238 } else if (prefer != null && prefer.hasHandling()) { 239 returnPreference = prefer.getHandling(); 240 } else { 241 returnPreference = PreferTag.emptyTag(); 242 } 243 244 final LdpPreferTag ldpPreferences = new LdpPreferTag(returnPreference); 245 246 final Predicate<Triple> tripleFilter = ldpPreferences.prefersServerManaged() ? x -> true : 247 IS_MANAGED_TRIPLE.negate(); 248 249 final List<Stream<Triple>> streams = new ArrayList<>(); 250 251 252 if (returnPreference.getValue().equals("minimal")) { 253 streams.add(getTriples(of(PROPERTIES, MINIMAL)).filter(tripleFilter)); 254 255 if (ldpPreferences.prefersServerManaged()) { 256 streams.add(getTriples(of(SERVER_MANAGED, MINIMAL))); 257 } 258 } else { 259 streams.add(getTriples(PROPERTIES).filter(tripleFilter)); 260 261 // Additional server-managed triples about this resource 262 if (ldpPreferences.prefersServerManaged()) { 263 streams.add(getTriples(SERVER_MANAGED)); 264 } 265 266 // containment triples about this resource 267 if (ldpPreferences.prefersContainment()) { 268 if (limit == -1) { 269 streams.add(getTriples(LDP_CONTAINMENT)); 270 } else { 271 streams.add(getTriples(LDP_CONTAINMENT).limit(limit)); 272 } 273 } 274 275 // LDP container membership triples for this resource 276 if (ldpPreferences.prefersMembership()) { 277 streams.add(getTriples(LDP_MEMBERSHIP)); 278 } 279 280 // Include inbound references to this object 281 if (ldpPreferences.prefersReferences()) { 282 streams.add(getTriples(INBOUND_REFERENCES)); 283 } 284 285 // Embed the children of this object 286 if (ldpPreferences.prefersEmbed()) { 287 streams.add(getTriples(EMBED_RESOURCES)); 288 } 289 } 290 291 final RdfStream rdfStream = new DefaultRdfStream( 292 asNode(resource()), streams.stream().reduce(empty(), Stream::concat)); 293 294 if (httpTripleUtil != null && ldpPreferences.prefersServerManaged()) { 295 return httpTripleUtil.addHttpComponentModelsForResourceToStream(rdfStream, resource(), uriInfo, 296 translator()); 297 } 298 299 return rdfStream; 300 } 301 302 /** 303 * Get the binary content of a datastream 304 * 305 * @param rangeValue the range value 306 * @return Binary blob 307 * @throws IOException if io exception occurred 308 */ 309 protected Response getBinaryContent(final String rangeValue) 310 throws IOException { 311 final FedoraBinary binary = (FedoraBinary)resource(); 312 313 // we include an explicit etag, because the default behavior is to use the JCR node's etag, not 314 // the jcr:content node digest. The etag is only included if we are not within a transaction. 315 final String txId = TransactionServiceImpl.getCurrentTransactionId(session()); 316 if (txId == null) { 317 checkCacheControlHeaders(request, servletResponse, binary, session()); 318 } 319 final CacheControl cc = new CacheControl(); 320 cc.setMaxAge(0); 321 cc.setMustRevalidate(true); 322 Response.ResponseBuilder builder; 323 324 if (rangeValue != null && rangeValue.startsWith("bytes")) { 325 326 final Range range = Range.convert(rangeValue); 327 328 final long contentSize = binary.getContentSize(); 329 330 final String endAsString; 331 332 if (range.end() == -1) { 333 endAsString = Long.toString(contentSize - 1); 334 } else { 335 endAsString = Long.toString(range.end()); 336 } 337 338 final String contentRangeValue = 339 String.format("bytes %s-%s/%s", range.start(), 340 endAsString, contentSize); 341 342 if (range.end() > contentSize || 343 (range.end() == -1 && range.start() > contentSize)) { 344 345 builder = status(REQUESTED_RANGE_NOT_SATISFIABLE) 346 .header("Content-Range", contentRangeValue); 347 } else { 348 @SuppressWarnings("resource") 349 final RangeRequestInputStream rangeInputStream = 350 new RangeRequestInputStream(binary.getContent(), range.start(), range.size()); 351 352 builder = status(PARTIAL_CONTENT).entity(rangeInputStream) 353 .header("Content-Range", contentRangeValue); 354 } 355 356 } else { 357 @SuppressWarnings("resource") 358 final InputStream content = binary.getContent(); 359 builder = ok(content); 360 } 361 362 363 // we set the content-type explicitly to avoid content-negotiation from getting in the way 364 return builder.type(binary.getMimeType()) 365 .cacheControl(cc) 366 .build(); 367 368 } 369 370 protected RdfStream getTriples(final Set<? extends TripleCategory> x) { 371 return getTriples(resource(), x); 372 } 373 374 protected RdfStream getTriples(final FedoraResource resource, final Set<? extends TripleCategory> x) { 375 return resource.getTriples(translator(), x); 376 } 377 378 protected RdfStream getTriples(final TripleCategory x) { 379 return getTriples(resource(), x); 380 } 381 382 protected RdfStream getTriples(final FedoraResource resource, final TripleCategory x) { 383 return resource.getTriples(translator(), x); 384 } 385 386 protected URI getUri(final FedoraResource resource) { 387 try { 388 final String uri = translator().reverse().convert(resource).getURI(); 389 return new URI(uri); 390 } catch (final URISyntaxException e) { 391 throw new BadRequestException(e); 392 } 393 } 394 395 protected FedoraResource resource() { 396 if (resource == null) { 397 resource = getResourceFromPath(externalPath()); 398 } 399 return resource; 400 } 401 402 protected void addResourceLinkHeaders(final FedoraResource resource) { 403 addResourceLinkHeaders(resource, false); 404 } 405 406 protected void addResourceLinkHeaders(final FedoraResource resource, final boolean includeAnchor) { 407 if (resource instanceof NonRdfSourceDescription) { 408 final URI uri = getUri(((NonRdfSourceDescription) resource).getDescribedResource()); 409 final Link link = Link.fromUri(uri).rel("describes").build(); 410 servletResponse.addHeader("Link", link.toString()); 411 } else if (resource instanceof FedoraBinary) { 412 final URI uri = getUri(((FedoraBinary) resource).getDescription()); 413 final Link.Builder builder = Link.fromUri(uri).rel("describedby"); 414 415 if (includeAnchor) { 416 builder.param("anchor", getUri(resource).toString()); 417 } 418 servletResponse.addHeader("Link", builder.build().toString()); 419 } 420 } 421 422 /** 423 * Add any resource-specific headers to the response 424 * @param resource the resource 425 */ 426 protected void addResourceHttpHeaders(final FedoraResource resource) { 427 if (resource instanceof FedoraBinary) { 428 429 final FedoraBinary binary = (FedoraBinary)resource; 430 final ContentDisposition contentDisposition = ContentDisposition.type("attachment") 431 .fileName(binary.getFilename()) 432 .creationDate(binary.getCreatedDate()) 433 .modificationDate(binary.getLastModifiedDate()) 434 .size(binary.getContentSize()) 435 .build(); 436 437 servletResponse.addHeader("Content-Type", binary.getMimeType()); 438 servletResponse.addHeader("Content-Length", String.valueOf(binary.getContentSize())); 439 servletResponse.addHeader("Accept-Ranges", "bytes"); 440 servletResponse.addHeader("Content-Disposition", contentDisposition.toString()); 441 } 442 443 servletResponse.addHeader("Link", "<" + LDP_NAMESPACE + "Resource>;rel=\"type\""); 444 445 if (resource instanceof NonRdfSource) { 446 servletResponse.addHeader("Link", "<" + LDP_NAMESPACE + "NonRDFSource>;rel=\"type\""); 447 } else if (resource instanceof Container) { 448 servletResponse.addHeader("Link", "<" + CONTAINER.getURI() + ">;rel=\"type\""); 449 if (resource.hasType(LDP_BASIC_CONTAINER)) { 450 servletResponse.addHeader("Link", "<" + BASIC_CONTAINER.getURI() + ">;rel=\"type\""); 451 } else if (resource.hasType(LDP_DIRECT_CONTAINER)) { 452 servletResponse.addHeader("Link", "<" + DIRECT_CONTAINER.getURI() + ">;rel=\"type\""); 453 } else if (resource.hasType(LDP_INDIRECT_CONTAINER)) { 454 servletResponse.addHeader("Link", "<" + INDIRECT_CONTAINER.getURI() + ">;rel=\"type\""); 455 } else { 456 servletResponse.addHeader("Link", "<" + BASIC_CONTAINER.getURI() + ">;rel=\"type\""); 457 } 458 } else { 459 servletResponse.addHeader("Link", "<" + LDP_NAMESPACE + "RDFSource>;rel=\"type\""); 460 } 461 if (httpHeaderInject != null) { 462 httpHeaderInject.addHttpHeaderToResponseStream(servletResponse, uriInfo, resource()); 463 } 464 465 } 466 467 /** 468 * Evaluate the cache control headers for the request to see if it can be served from 469 * the cache. 470 * 471 * @param request the request 472 * @param servletResponse the servlet response 473 * @param resource the fedora resource 474 * @param session the session 475 */ 476 protected static void checkCacheControlHeaders(final Request request, 477 final HttpServletResponse servletResponse, 478 final FedoraResource resource, 479 final Session session) { 480 evaluateRequestPreconditions(request, servletResponse, resource, session, true); 481 addCacheControlHeaders(servletResponse, resource, session); 482 } 483 484 /** 485 * Add ETag and Last-Modified cache control headers to the response 486 * @param servletResponse the servlet response 487 * @param resource the fedora resource 488 * @param session the session 489 */ 490 protected static void addCacheControlHeaders(final HttpServletResponse servletResponse, 491 final FedoraResource resource, 492 final Session session) { 493 494 final String txId = TransactionServiceImpl.getCurrentTransactionId(session); 495 if (txId != null) { 496 // Do not add caching headers if in a transaction 497 return; 498 } 499 500 final FedoraResource mutableResource = resource instanceof NonRdfSourceDescription 501 ? ((NonRdfSourceDescription) resource).getDescribedResource() : resource; 502 final EntityTag etag = new EntityTag(mutableResource.getEtagValue()); 503 final Date date = mutableResource.getLastModifiedDate(); 504 505 if (!etag.getValue().isEmpty()) { 506 servletResponse.addHeader("ETag", etag.toString()); 507 } 508 509 if (date != null) { 510 servletResponse.addDateHeader("Last-Modified", date.getTime()); 511 } 512 } 513 514 /** 515 * Evaluate request preconditions to ensure the resource is the expected state 516 * @param request the request 517 * @param servletResponse the servlet response 518 * @param resource the resource 519 * @param session the session 520 */ 521 protected static void evaluateRequestPreconditions(final Request request, 522 final HttpServletResponse servletResponse, 523 final FedoraResource resource, 524 final Session session) { 525 evaluateRequestPreconditions(request, servletResponse, resource, session, false); 526 } 527 528 private static void evaluateRequestPreconditions(final Request request, 529 final HttpServletResponse servletResponse, 530 final FedoraResource resource, 531 final Session session, 532 final boolean cacheControl) { 533 534 final String txId = TransactionServiceImpl.getCurrentTransactionId(session); 535 if (txId != null) { 536 // Force cache revalidation if in a transaction 537 servletResponse.addHeader(CACHE_CONTROL, "must-revalidate"); 538 servletResponse.addHeader(CACHE_CONTROL, "max-age=0"); 539 return; 540 } 541 542 final EntityTag etag = new EntityTag(resource.getEtagValue()); 543 final Date date = resource.getLastModifiedDate(); 544 final Date roundedDate = new Date(); 545 546 if (date != null) { 547 roundedDate.setTime(date.getTime() - date.getTime() % 1000); 548 } 549 550 Response.ResponseBuilder builder = request.evaluatePreconditions(etag); 551 if ( builder != null ) { 552 builder = builder.entity("ETag mismatch"); 553 } else { 554 builder = request.evaluatePreconditions(roundedDate); 555 if ( builder != null ) { 556 builder = builder.entity("Date mismatch"); 557 } 558 } 559 560 if (builder != null && cacheControl ) { 561 final CacheControl cc = new CacheControl(); 562 cc.setMaxAge(0); 563 cc.setMustRevalidate(true); 564 // here we are implicitly emitting a 304 565 // the exception is not an error, it's genuinely 566 // an exceptional condition 567 builder = builder.cacheControl(cc).lastModified(date).tag(etag); 568 } 569 if (builder != null) { 570 throw new WebApplicationException(builder.build()); 571 } 572 } 573 574 protected static MediaType getSimpleContentType(final MediaType requestContentType) { 575 return requestContentType != null ? new MediaType(requestContentType.getType(), requestContentType.getSubtype()) 576 : APPLICATION_OCTET_STREAM_TYPE; 577 } 578 579 protected static boolean isRdfContentType(final String contentTypeString) { 580 return contentTypeToLang(contentTypeString) != null; 581 } 582 583 protected void replaceResourceBinaryWithStream(final FedoraBinary result, 584 final InputStream requestBodyStream, 585 final ContentDisposition contentDisposition, 586 final MediaType contentType, 587 final String checksum) throws InvalidChecksumException { 588 final URI checksumURI = checksumURI(checksum); 589 final String originalFileName = contentDisposition != null ? contentDisposition.getFileName() : ""; 590 final String originalContentType = contentType != null ? contentType.toString() : ""; 591 592 result.setContent(requestBodyStream, 593 originalContentType, 594 checksumURI, 595 originalFileName, 596 storagePolicyDecisionPoint); 597 } 598 599 protected void replaceResourceWithStream(final FedoraResource resource, 600 final InputStream requestBodyStream, 601 final MediaType contentType, 602 final RdfStream resourceTriples) throws MalformedRdfException { 603 final Lang format = contentTypeToLang(contentType.toString()); 604 605 final Model inputModel = createDefaultModel(); 606 try { 607 inputModel.read(requestBodyStream, getUri(resource).toString(), format.getName().toUpperCase()); 608 609 } catch (final RiotException e) { 610 throw new BadRequestException("RDF was not parsable: " + e.getMessage(), e); 611 612 } catch (final RuntimeIOException e) { 613 if (e.getCause() instanceof JsonParseException) { 614 throw new MalformedRdfException(e.getCause()); 615 } 616 throw new RepositoryRuntimeException(e); 617 } 618 619 resource.replaceProperties(translator(), inputModel, resourceTriples); 620 } 621 622 protected void patchResourcewithSparql(final FedoraResource resource, 623 final String requestBody, 624 final RdfStream resourceTriples) { 625 if (resource instanceof NonRdfSourceDescription) { 626 // update the described resource instead 627 ((NonRdfSourceDescription) resource).getDescribedResource() 628 .updateProperties(translator(), requestBody, resourceTriples); 629 } else { 630 resource.updateProperties(translator(), requestBody, resourceTriples); 631 } 632 } 633 634 /** 635 * Create a checksum URI object. 636 **/ 637 private static URI checksumURI( final String checksum ) { 638 if (!isBlank(checksum)) { 639 return URI.create(checksum); 640 } 641 return null; 642 } 643}