/*
* Copyright 2012 Guido Steinacker
*
* 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 de.otto.jsonhome.model;
import java.util.*;
import static de.otto.jsonhome.model.Documentation.emptyDocs;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableSet;
import static java.util.EnumSet.copyOf;
import static java.util.EnumSet.noneOf;
/**
* Hints are used to describe a resource: the allowed HTTP methods, the supported representations,
* required preconditions of an operation, the current status and possibly some documentation.
* <p>
* All these information are optional. You should not completely rely on the information of the hints.
* For example, only because the hints do no contain a HTTP method, this must not necessarily mean that
* the method is not allowed for a resource.
* <p>
* However, the JsonHomeGenerator will fortunately always find useful and correct hints...
* <p>
* This implementation is immutable.
*
* @author Guido Steinacker
* @since 30.09.12
* @see <a href="http://tools.ietf.org/html/draft-nottingham-json-home-02#section-5">http://tools.ietf.org/html/draft-nottingham-json-home-02#section-5</a>
*/
public final class Hints {
public static final Hints EMPTY_HINTS = hints(
EnumSet.noneOf(Allow.class), Collections.<String>emptyList()
);
private final Set<Allow> allows;
private final List<String> representations;
private final List<String> acceptPut;
private final List<String> acceptPost;
private final List<String> acceptPatch;
private final List<String> acceptRanges;
private final List<String> preferences;
private final List<Precondition> preconditionReq;
private final List<Authentication> authReq;
private final Status status;
private final Documentation docs;
/**
* Returns an empty Hints instance.
*
* @return EMPTY_HINTS
*/
public static Hints emptyHints() {
return EMPTY_HINTS;
}
/**
* Creates hints with information about allowed HTTP methods and the supported representations of the resource.
* <p>
* The status is set to Status.OK, all other fields will be empty (but not null).
*
* @param allows the allowed HTTP methods
* @param representations the representations of the resource
* @return Hints
*/
public static Hints hints(final Set<Allow> allows,
final List<String> representations) {
return hints(allows,
representations,
Collections.<String>emptyList(),
Collections.<String>emptyList(),
Collections.<String>emptyList(),
Collections.<String>emptyList(),
Collections.<String>emptyList(),
Collections.<Precondition>emptyList(),
Collections.<Authentication>emptyList(),
Status.OK,
emptyDocs());
}
public static Hints hints(final Set<Allow> allows,
final List<String> representations,
final List<String> acceptPut,
final List<String> acceptPost,
final List<String> acceptPatch,
final List<String> acceptRanges,
final List<String> preferences,
final List<Precondition> preconditionReq,
final List<Authentication> authReq,
final Status status,
final Documentation docs) {
return new Hints(
allows,
representations, acceptPut, acceptPost, acceptPatch,
acceptRanges,
preferences,
preconditionReq,
authReq,
status,
docs);
}
private Hints(final Set<Allow> allows,
final List<String> representations,
final List<String> acceptPut,
final List<String> acceptPost,
final List<String> acceptPatch,
final List<String> acceptRanges,
final List<String> preferences,
final List<Precondition> preconditionReq,
final List<Authentication> authReq,
final Status status,
final Documentation docs) {
if (!acceptPost.isEmpty() && !allows.contains(Allow.POST)) {
throw new IllegalArgumentException("POST is not allowed but accept-post is provided.");
}
if (!acceptPut.isEmpty() && !allows.contains(Allow.PUT)) {
throw new IllegalArgumentException("PUT is not allowed but accept-put is provided.");
}
if (!acceptPatch.isEmpty() && !allows.contains(Allow.PATCH)) {
throw new IllegalArgumentException("PATCH is not allowed but accept-patch is provided.");
}
this.allows = unmodifiableSet(copyOf(allows));
this.representations = unmodifiableList(new ArrayList<String>(representations));
this.acceptPut = acceptPut;
this.acceptPost = acceptPost;
this.acceptPatch = acceptPatch;
this.acceptRanges = unmodifiableList(acceptRanges);
this.preferences = unmodifiableList(preferences);
this.preconditionReq = unmodifiableList(new ArrayList<Precondition>(preconditionReq));
this.authReq = unmodifiableList(new ArrayList<Authentication>(authReq));
this.status = status != null ? status : Status.OK;
this.docs = docs != null ? docs : emptyDocs();
}
/**
* @return the list of allowed HTTP methods.
* @see <a href="http://tools.ietf.org/html/draft-nottingham-json-home-02#section-5.1">http://tools.ietf.org/html/draft-nottingham-json-home-02#section-5.1</a>
*/
public Set<Allow> getAllows() {
return allows;
}
/**
* @return the list of representations supported for this resource link.
* @see <a href="http://tools.ietf.org/html/draft-nottingham-json-home-02#section-5.2">http://tools.ietf.org/html/draft-nottingham-json-home-02#section-5.2</a>
*/
public List<String> getRepresentations() {
return representations;
}
/**
* @return the accept-put hint, declaring the accepted representations of a HTTP PUT request.
*/
public List<String> getAcceptPut() {
return acceptPut;
}
/**
* @return the accept-post hint, declaring the accepted representations of a HTTP POST request.
*/
public List<String> getAcceptPost() {
return acceptPost;
}
/**
* @return the accept-patch hint, declaring the accepted representations of a HTTP PATCH request.
*/
public List<String> getAcceptPatch() {
return acceptPatch;
}
/**
* @return the accept-ranges hint, declaring the accepted range requests .
*/
public List<String> getAcceptRanges() {
return acceptRanges;
}
/**
* @return the preferences supported by the hinted resource.
*/
public List<String> getPreferences() {
return preferences;
}
/**
* @return the required preconditions.
*/
public List<Precondition> getPreconditionReq() {
return preconditionReq;
}
/**
* @return the required authentication.
*/
public List<Authentication> getAuthReq() {
return authReq;
}
/**
* @return Status specifies whether the resource is OK, DEPRECATED or GONE.
*/
public Status getStatus() {
return status;
}
/**
* Human-readable documentation of a ResourceLink.
*
* @return Documentation
*/
public Documentation getDocs() {
return docs;
}
/**
* Merges the hints of two resource links..
*
* @param other the hints of the other resource link
* @return a new, merged Hints instance
*/
public Hints mergeWith(final Hints other) {
final EnumSet<Allow> allows = this.allows.isEmpty() ? noneOf(Allow.class) : copyOf(this.allows);
allows.addAll(other.getAllows());
final Set<String> representations = new LinkedHashSet<String>(this.representations);
representations.addAll(other.getRepresentations());
final Set<String> acceptPut = new LinkedHashSet<String>(this.acceptPut);
acceptPut.addAll(other.getAcceptPut());
final Set<String> acceptPost = new LinkedHashSet<String>(this.acceptPost);
acceptPost.addAll(other.getAcceptPost());
final Set<String> acceptPatch = new LinkedHashSet<String>(this.acceptPatch);
acceptPatch.addAll(other.getAcceptPatch());
final Set<String> acceptRanges = new LinkedHashSet<String>(this.acceptRanges);
acceptRanges.addAll(other.getAcceptRanges());
final Set<String> preferences = new LinkedHashSet<String>(this.preferences);
preferences.addAll(other.getPreferences());
final Set<Precondition> preconditionReq = new LinkedHashSet<Precondition>(this.preconditionReq);
preconditionReq.addAll(other.getPreconditionReq());
final List<Authentication> mergedAuth = mergeAuthReq(other.getAuthReq());
return hints(
allows,
new ArrayList<String>(representations),
new ArrayList<String>(acceptPut),
new ArrayList<String>(acceptPost),
new ArrayList<String>(acceptPatch),
new ArrayList<String>(acceptRanges),
new ArrayList<String>(preferences),
new ArrayList<Precondition>(preconditionReq),
mergedAuth,
status.mergeWith(other.getStatus()),
docs.mergeWith(other.getDocs())
);
}
private List<Authentication> mergeAuthReq(final List<Authentication> otherAuthReq) {
final Map<String, Set<String>> authReq = new TreeMap<String, Set<String>>();
for (final Authentication auth : this.authReq) {
authReq.put(auth.getScheme(), new TreeSet<String>(auth.getRealms()));
}
for (final Authentication auth : otherAuthReq) {
if (authReq.containsKey(auth.getScheme())) {
authReq.get(auth.getScheme()).addAll(auth.getRealms());
} else {
authReq.put(auth.getScheme(), new TreeSet<String>(auth.getRealms()));
}
}
final List<Authentication> mergedAuth = new ArrayList<Authentication>();
for (final String scheme : authReq.keySet()) {
mergedAuth.add(Authentication.authReq(scheme, new ArrayList<String>(authReq.get(scheme))));
}
return mergedAuth;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Hints hints = (Hints) o;
if (acceptPatch != null ? !acceptPatch.equals(hints.acceptPatch) : hints.acceptPatch != null) return false;
if (acceptPost != null ? !acceptPost.equals(hints.acceptPost) : hints.acceptPost != null) return false;
if (acceptPut != null ? !acceptPut.equals(hints.acceptPut) : hints.acceptPut != null) return false;
if (acceptRanges != null ? !acceptRanges.equals(hints.acceptRanges) : hints.acceptRanges != null) return false;
if (allows != null ? !allows.equals(hints.allows) : hints.allows != null) return false;
if (authReq != null ? !authReq.equals(hints.authReq) : hints.authReq != null) return false;
if (docs != null ? !docs.equals(hints.docs) : hints.docs != null) return false;
if (preconditionReq != null ? !preconditionReq.equals(hints.preconditionReq) : hints.preconditionReq != null)
return false;
if (preferences != null ? !preferences.equals(hints.preferences) : hints.preferences != null) return false;
if (representations != null ? !representations.equals(hints.representations) : hints.representations != null)
return false;
if (status != hints.status) return false;
return true;
}
@Override
public int hashCode() {
int result = allows != null ? allows.hashCode() : 0;
result = 31 * result + (representations != null ? representations.hashCode() : 0);
result = 31 * result + (acceptPut != null ? acceptPut.hashCode() : 0);
result = 31 * result + (acceptPost != null ? acceptPost.hashCode() : 0);
result = 31 * result + (acceptPatch != null ? acceptPatch.hashCode() : 0);
result = 31 * result + (acceptRanges != null ? acceptRanges.hashCode() : 0);
result = 31 * result + (preferences != null ? preferences.hashCode() : 0);
result = 31 * result + (preconditionReq != null ? preconditionReq.hashCode() : 0);
result = 31 * result + (authReq != null ? authReq.hashCode() : 0);
result = 31 * result + (status != null ? status.hashCode() : 0);
result = 31 * result + (docs != null ? docs.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Hints{" +
"allows=" + allows +
", representations=" + representations +
", acceptPut=" + acceptPut +
", acceptPost=" + acceptPost +
", acceptPatch=" + acceptPatch +
", acceptRanges=" + acceptRanges +
", preferences=" + preferences +
", preconditionReq=" + preconditionReq +
", authReq=" + authReq +
", status=" + status +
", docs=" + docs +
'}';
}
}