/* * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.rest.webmvc.support; import static org.springframework.util.StringUtils.*; import lombok.EqualsAndHashCode; import java.util.Optional; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.rest.webmvc.PersistentEntityResource; import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; /** * A value object to represent ETags. * * @author Oliver Gierke */ @EqualsAndHashCode public final class ETag { public static final ETag NO_ETAG = new ETag(null); private final String value; /** * Creates a new {@link ETag} from the given value. * * @param value can be {@literal null}. */ private ETag(String value) { this.value = trimTrailingCharacter(trimLeadingCharacter(value, '"'), '"'); } /** * Creates a new {@link ETag} for the given {@link String} value. Falls back to {@link #NO_ETAG} in case * {@literal null} is provided. * * @param value the source ETag value, must not be {@literal null}. * @return */ public static ETag from(String value) { return new ETag(value); } public static ETag from(Optional<String> value) { return value.map(ETag::new).orElse(NO_ETAG); } /** * Creates a new {@link ETag} for the given {@link PersistentEntityResource}. * * @param resource must not be {@literal null}. * @return */ public static ETag from(PersistentEntityResource resource) { Assert.notNull(resource, "PersistentEntityResource must not be null!"); return from(resource.getPersistentEntity(), resource.getContent()); } /** * Creates a new {@link ETag} from the given {@link PersistentEntity} and target bean. * * @param entity must not be {@literal null}. * @param bean must not be {@literal null}. * @return */ public static ETag from(PersistentEntity<?, ? extends PersistentProperty<?>> entity, Object bean) { return getVersionInformation(entity, bean).map(ETag::from).orElse(NO_ETAG); } /** * Verifies the ETag to be created for the given target bean with the current one and raises a * {@link ETagDoesntMatchException} in case they don't match. * * @param entity must not be {@literal null}. * @param target can be {@literal null}. * @throws ETagDoesntMatchException in case the calculated {@link ETag} for the given bean does not match the current * one. */ public void verify(PersistentEntity<?, ?> entity, Object target) { if (this == NO_ETAG || target == null) { return; } if (!this.equals(from(entity, target))) { throw new ETagDoesntMatchException(target, this); } } /** * Returns whether the {@link ETag} matches the given {@link PersistentEntity} and target. A more dissenting way of * checking matches as it does not match if the ETag is {@link #NO_ETAG}. * * @param entity must not be {@literal null}. * @param target can be {@literal null}. * @return */ public boolean matches(PersistentEntity<?, ?> entity, Object target) { if (this == NO_ETAG || target == null) { return false; } return this.equals(from(entity, target)); } /** * Adds the current {@link ETag} to the given headers. * * @param headers must not be {@literal null}. * @return the {@link HttpHeaders} with the ETag header been set if the current {@link ETag} instance is not * {@link #NO_ETAG}. */ public HttpHeaders addTo(HttpHeaders headers) { Assert.notNull(headers, "HttpHeaders must not be null!"); String stringValue = toString(); if (stringValue == null) { return headers; } headers.setETag(stringValue); return headers; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return value == null ? null : "\"".concat(value).concat("\""); } /** * Returns the quoted version property of a domain object, returns null if it doesn't contains the property * * @param entity * @param bean * @return */ private static Optional<String> getVersionInformation(PersistentEntity<?, ? extends PersistentProperty<?>> entity, Object bean) { Assert.notNull(entity, "PersistentEntity must not be null!"); Assert.notNull(bean, "Target bean must not be null!"); PersistentPropertyAccessor accessor = entity.getPropertyAccessor(bean); return entity.getVersionProperty()// .flatMap(it -> accessor.getProperty(it))// .map(Object::toString); } }