/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.data; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.restlet.engine.util.DateUtils; import org.restlet.representation.RepresentationInfo; /** * Set of conditions applying to a request. This is equivalent to the HTTP * conditional headers. * * @see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24" * >If-Match</a> * @see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25" * >If-Modified-Since</a> * @see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26" * >If-None-Match</a> * @see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27" * >If-Range</a> * @see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.28" * >If-Unmodified-Since</a> * * @author Jerome Louvel */ public final class Conditions { /** The "if-match" condition. */ private volatile List<Tag> match; /** The "if-modified-since" condition. */ private volatile Date modifiedSince; /** The "if-none-match" condition. */ private volatile List<Tag> noneMatch; /** The "if-range" condition as a Date. */ private volatile Date rangeDate; /** The "if-range" condition as an entity tag. */ private volatile Tag rangeTag; /** The "if-unmodified-since" condition */ private volatile Date unmodifiedSince; /** * Constructor. */ public Conditions() { } /** * Returns the modifiable list of tags that must be matched. Creates a new * instance if no one has been set.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Match" header. * * @return The "if-match" condition. */ public List<Tag> getMatch() { // Lazy initialization with double-check. List<Tag> m = this.match; if (m == null) { synchronized (this) { m = this.match; if (m == null) { this.match = m = new ArrayList<Tag>(); } } } return m; } /** * Returns the condition based on the modification date of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Modified-Since" header. * * @return The condition date. */ public Date getModifiedSince() { return this.modifiedSince; } /** * Returns the modifiable list of tags that mustn't match. Creates a new * instance if no one has been set.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-None-Match" header. * * @return The list of tags that mustn't match. */ public List<Tag> getNoneMatch() { // Lazy initialization with double-check. List<Tag> n = this.noneMatch; if (n == null) { synchronized (this) { n = this.noneMatch; if (n == null) { this.noneMatch = n = new ArrayList<Tag>(); } } } return n; } /** * Returns the range condition based on the modification date of the * requested variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Range" header. * * @return The range condition date. */ public Date getRangeDate() { return rangeDate; } /** * Returns the range conditional status of an entity. * * @param representationInfo * The representation information that will be tested. * @return the status of the response. */ public Status getRangeStatus(RepresentationInfo representationInfo) { return getRangeStatus( (representationInfo == null) ? null : representationInfo.getTag(), (representationInfo == null) ? null : representationInfo .getModificationDate()); } /** * Returns the range conditional status of an entity. * * @param tag * The tag of the entity. * @param modificationDate * The modification date of the entity. * @return The status of the response. */ public Status getRangeStatus(Tag tag, Date modificationDate) { Status result = Status.CLIENT_ERROR_PRECONDITION_FAILED; if (getRangeTag() != null) { boolean all = getRangeTag().equals(Tag.ALL); // If a tag exists if (tag != null) { if (all || getRangeTag().equals(tag)) { result = Status.SUCCESS_OK; } } } else if (getRangeDate() != null) { // If a modification date exists if (getRangeDate().equals(modificationDate)) { result = Status.SUCCESS_OK; } } return result; } /** * Returns the range condition based on the entity tag of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Range" header. * * @return The range entity tag. */ public Tag getRangeTag() { return rangeTag; } /** * Returns the conditional status of a variant using a given method. * * @param method * The request method. * @param entityExists * Indicates if the entity exists. * @param tag * The tag. * @param modificationDate * The modification date. * @return Null if the requested method can be performed, the status of the * response otherwise. */ public Status getStatus(Method method, boolean entityExists, Tag tag, Date modificationDate) { Status result = null; // Is the "if-Match" rule followed or not? if ((this.match != null) && !this.match.isEmpty()) { boolean matched = false; boolean failed = false; boolean all = (getMatch().size() > 0) && getMatch().get(0).equals(Tag.ALL); String statusMessage = null; if (entityExists) { // If a tag exists if (!all && (tag != null)) { // Check if it matches one of the representations already // cached by the client Tag matchTag; for (Iterator<Tag> iter = getMatch().iterator(); !matched && iter.hasNext();) { matchTag = iter.next(); matched = matchTag.equals(tag, false); } } else { matched = all; } } else { // See // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 // If none of the entity tags match, or if "*" is given and no // current entity exists, the server MUST NOT perform the // requested method failed = all; statusMessage = "A non existing resource can't match any tag."; } failed = failed || !matched; if (failed) { result = Status.CLIENT_ERROR_PRECONDITION_FAILED; if (statusMessage != null) { result = new Status(result, statusMessage); } } } // Is the "if-None-Match" rule followed or not? if ((result == null) && (this.noneMatch != null) && !this.noneMatch.isEmpty()) { boolean matched = false; if (entityExists) { // If a tag exists if (tag != null) { // Check if it matches one of the representations // already cached by the client Tag noneMatchTag; for (Iterator<Tag> iter = getNoneMatch().iterator(); !matched && iter.hasNext();) { noneMatchTag = iter.next(); matched = noneMatchTag.equals(tag, (Method.GET .equals(method) || Method.HEAD.equals(method))); } // The current representation matches one of those already // cached by the client if (matched) { // Check if the current representation has been updated // since the "if-modified-since" date. In this case, the // rule is followed. Date modifiedSince = getModifiedSince(); boolean isModifiedSince = (modifiedSince != null) && (DateUtils.after(new Date(), modifiedSince) || (modificationDate == null) || DateUtils .after(modifiedSince, modificationDate)); matched = !isModifiedSince; } } else { matched = (getNoneMatch().size() > 0) && getNoneMatch().get(0).equals(Tag.ALL); } } if (matched) { if (Method.GET.equals(method) || Method.HEAD.equals(method)) { result = Status.REDIRECTION_NOT_MODIFIED; } else { result = Status.CLIENT_ERROR_PRECONDITION_FAILED; } } } // Is the "if-Modified-Since" rule followed or not? if ((result == null) && (getModifiedSince() != null)) { Date modifiedSince = getModifiedSince(); boolean isModifiedSince = (DateUtils.after(new Date(), modifiedSince) || (modificationDate == null) || DateUtils .after(modifiedSince, modificationDate)); if (!isModifiedSince) { if (Method.GET.equals(method) || Method.HEAD.equals(method)) { result = Status.REDIRECTION_NOT_MODIFIED; } else { result = Status.CLIENT_ERROR_PRECONDITION_FAILED; } } } // Is the "if-Unmodified-Since" rule followed or not? if ((result == null) && (getUnmodifiedSince() != null)) { Date unModifiedSince = getUnmodifiedSince(); boolean isUnModifiedSince = ((unModifiedSince == null) || (modificationDate == null) || !DateUtils.before( modificationDate, unModifiedSince)); if (!isUnModifiedSince) { result = Status.CLIENT_ERROR_PRECONDITION_FAILED; } } return result; } /** * Returns the conditional status of a variant using a given method. * * @param method * The request method. * @param representationInfo * The representation information that will be tested. * @return Null if the requested method can be performed, the status of the * response otherwise. */ public Status getStatus(Method method, RepresentationInfo representationInfo) { return getStatus( method, representationInfo != null, (representationInfo == null) ? null : representationInfo .getTag(), (representationInfo == null) ? null : representationInfo.getModificationDate()); } /** * Returns the condition based on the modification date of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Unmodified-Since" header. * * @return The condition date. */ public Date getUnmodifiedSince() { return this.unmodifiedSince; } /** * Indicates if there are some conditions set. * * @return True if there are some conditions set. */ public boolean hasSome() { return (((this.match != null) && !this.match.isEmpty()) || ((this.noneMatch != null) && !this.noneMatch.isEmpty()) || (getModifiedSince() != null) || (getUnmodifiedSince() != null)); } /** * Indicates if there are some range conditions set. * * @return True if there are some range conditions set. */ public boolean hasSomeRange() { return getRangeTag() != null || getRangeDate() != null; } /** * Sets the modifiable list of tags that must be matched.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Match" header. * * @param tags * The "if-match" condition. */ public void setMatch(List<Tag> tags) { this.match = tags; } /** * Sets the condition based on the modification date of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Modified-Since" header. * * @param date * The modification date. */ public void setModifiedSince(Date date) { this.modifiedSince = DateUtils.unmodifiable(date); } /** * Sets the modifiable list of tags that mustn't match. Creates a new * instance if no one has been set.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-None-Match" header. * * @param tags * The list of tags that mustn't match. */ public void setNoneMatch(List<Tag> tags) { this.noneMatch = tags; } /** * Sets the range condition based on the modification date of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Range" header. * * @param rangeDate * The date of the range condition. */ public void setRangeDate(Date rangeDate) { this.rangeDate = rangeDate; } /** * Sets the range condition based on the entity tag of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Range" header. * * @param rangeTag * The entity tag of the range condition. */ public void setRangeTag(Tag rangeTag) { this.rangeTag = rangeTag; } /** * Sets the condition based on the modification date of the requested * variant.<br> * <br> * Note that when used with HTTP connectors, this property maps to the * "If-Unmodified-Since" header. * * @param date * The condition date. */ public void setUnmodifiedSince(Date date) { this.unmodifiedSince = DateUtils.unmodifiable(date); } }