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.services; 019 020import static org.slf4j.LoggerFactory.getLogger; 021 022import java.util.Collection; 023import java.util.stream.Collectors; 024 025import javax.inject.Inject; 026import javax.ws.rs.core.MediaType; 027 028import org.fcrepo.kernel.api.ContainmentIndex; 029import org.fcrepo.kernel.api.Transaction; 030import org.fcrepo.kernel.api.models.FedoraResource; 031import org.fcrepo.kernel.api.rdf.LdpTriplePreferences; 032import org.fcrepo.kernel.api.services.MembershipService; 033 034import org.apache.commons.codec.digest.DigestUtils; 035import org.slf4j.Logger; 036import org.springframework.stereotype.Component; 037 038/** 039 * Service for computing etags for request responses 040 * 041 * @author bbpennel 042 */ 043@Component 044public class EtagService { 045 private static final Logger LOGGER = getLogger(EtagService.class); 046 047 @Inject 048 private ContainmentIndex containmentIndex; 049 050 @Inject 051 private MembershipService membershipService; 052 053 /** 054 * Produces etag for a request for an RDF resource. It is based on factors related to the 055 * current state of the resource, as well as request options which change the 056 * representation of the resource. 057 * 058 * @param transaction transaction 059 * @param resource resource 060 * @param prefers LDP preference headers for the request 061 * @param acceptableMediaTypes collection of acceptable media types for the response 062 * @return etag for the request 063 */ 064 public String getRdfResourceEtag(final Transaction transaction, final FedoraResource resource, 065 final LdpTriplePreferences prefers, 066 final Collection<MediaType> acceptableMediaTypes) { 067 // Start etag based on the current state of the fedora resource, using the state token 068 final StringBuilder etag = new StringBuilder(resource.getStateToken()); 069 070 // Factor in the requested mimetype(s) 071 final String mimetype = acceptableMediaTypes.stream() 072 .map(MediaType::toString) 073 .sorted() 074 .collect(Collectors.joining(";")); 075 addComponent(etag, mimetype); 076 077 // Factor in preferences which change which triples are included in the response 078 etag.append('|'); 079 if (prefers.displayContainment()) { 080 final var lastUpdated = containmentIndex.containmentLastUpdated(transaction, resource.getFedoraId()); 081 if (lastUpdated != null) { 082 etag.append(lastUpdated); 083 } 084 } 085 etag.append('|'); 086 if (prefers.displayMembership()) { 087 final var lastUpdated = membershipService.getLastUpdatedTimestamp(transaction, resource.getFedoraId()); 088 if (lastUpdated != null) { 089 etag.append(lastUpdated); 090 } 091 } 092 addComponent(etag, prefers.displayEmbed()); 093 addComponent(etag, prefers.displayUserRdf()); 094 addComponent(etag, prefers.displayReferences()); 095 addComponent(etag, prefers.displayServerManaged()); 096 097 // Compute a digest of all these components to use as the etag 098 final String etagMd5 = DigestUtils.md5Hex(etag.toString()).toUpperCase(); 099 LOGGER.debug("Produced etag {} for {} from {}", etagMd5, resource.getId(), etag); 100 101 return etagMd5; 102 } 103 104 private void addComponent(final StringBuilder etag, final Object component) { 105 etag.append('|').append(component); 106 } 107}