/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.rest.internal.jsonhome;
import com.damnhandy.uri.template.UriTemplate;
import com.damnhandy.uri.template.UriTemplateBuilder;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.rest.internal.RestErrorCode;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The JSON-HOME representation of a REST resource as defined by the
* <a href="http://tools.ietf.org/html/draft-nottingham-json-home-03#section-3">IETF draft</a>.
*
* @see org.seedstack.seed.rest.internal.jsonhome.JsonHome
*/
public class Resource {
private final String rel;
private String href;
private String hrefTemplate;
private Map<String, String> pathParams = new HashMap<>();
private Map<String, String> queryParams = new HashMap<>();
private Hints hints;
/**
* Resource constructor.
*
* @param rel the relation type
* @param href the href (can be an href-template)
* @param hints the the resource hints
*/
public Resource(String rel, String href, @Nullable Hints hints) {
checkNotNull(rel, "The rel must not be null");
checkNotNull(href, "The href must not be null");
this.rel = rel;
this.href = href;
this.hints = hints;
}
/**
* Resource constructor.
*
* @param rel the the relation type
* @param hrefTemplate the href template
* @param pathParams the href variables
* @param hints the the resource hints
*/
public Resource(String rel, String hrefTemplate, @Nullable Map<String, String> pathParams, @Nullable Map<String, String> queryParams, @Nullable Hints hints) {
checkNotNull(rel, "The rel must not be null");
checkNotNull(hrefTemplate, "The href must not be null");
this.rel = rel;
this.hrefTemplate = hrefTemplate;
if (this.pathParams != null) {
this.pathParams = pathParams;
}
if (this.queryParams != null) {
this.queryParams = queryParams;
}
this.hints = hints;
}
/**
* Returns the relation type.
*
* @return rel
*/
public String rel() {
return rel;
}
/**
* Returns the href. It can be empty if the path is templated.
*
* @return href
*/
public String href() {
return href;
}
/**
* Return the href template. It's empty unless the path is templated.
*
* @return hrefTemplate
*/
public String hrefTemplate() {
UriTemplateBuilder uriTemplateBuilder = UriTemplate.buildFromTemplate(hrefTemplate);
if (!queryParams.isEmpty()) {
uriTemplateBuilder = uriTemplateBuilder.query(queryParams.keySet().toArray(new String[queryParams.keySet().size()]));
}
return uriTemplateBuilder.build().getTemplate();
}
/**
* Return the hrefVars. It's empty unless the path is templated.
*
* @return hrefVars
*/
public Map<String, String> hrefVars() {
Map<String, String> map = new HashMap<>(pathParams);
map.putAll(queryParams);
return map;
}
/**
* Returns hints about the resource.
*
* @return the hints
*/
public Hints hints() {
return hints;
}
/**
* Indicates whether the href is templated.
*
* @return true if the href is templated, false otherwise
*/
public boolean templated() {
return hrefTemplate != null && !"".equals(hrefTemplate);
}
/**
* Merges the current resource with another resource instance.
* The merge objects must represent the same resource but can
* come from multiple methods.
*
* @param resource the resource object to merge
*/
public void merge(Resource resource) {
if (resource == null) {
return;
}
checkRel(resource);
checkHrefs(resource);
if (resource.templated()) {
this.pathParams.putAll(resource.pathParams);
this.queryParams.putAll(resource.queryParams);
}
if (resource.hints() != null) {
this.hints.merge(resource.hints());
}
}
private void checkRel(Resource resource) {
if (!rel.equals(resource.rel())) {
throw SeedException.createNew(RestErrorCode.CANNOT_MERGE_RESOURCE_WITH_DIFFERENT_REL)
.put("oldRel", rel).put("newRel", resource.rel());
}
}
private void checkHrefs(Resource resource) {
if (resource == null) {
throw new IllegalArgumentException("Resource should not be null");
}
if (this.templated() != resource.templated()) {
throw SeedException.createNew(RestErrorCode.MULTIPLE_PATH_FOR_THE_SAME_REL)
.put("rel", rel)
.put("oldHref", this.hrefTemplate != null ? hrefTemplate : href)
.put("newHref", resource.hrefTemplate != null ? resource.hrefTemplate : resource.href);
}
if (this.templated()) {
if (!resource.hrefTemplate.equals(this.hrefTemplate)) {
throw SeedException.createNew(RestErrorCode.MULTIPLE_PATH_FOR_THE_SAME_REL)
.put("rel", rel)
.put("oldHref", this.hrefTemplate)
.put("newHref", resource.hrefTemplate);
}
} else {
if (!resource.href().equals(this.href())) {
throw SeedException.createNew(RestErrorCode.MULTIPLE_PATH_FOR_THE_SAME_REL)
.put("rel", rel)
.put("oldHref", this.href)
.put("newHref", resource.href);
}
}
}
/**
* Serializes the resource into a map.
*
* @return the resource map
*/
public Map<String, Object> toRepresentation () {
Map<String, Object> representation = new HashMap<>();
if (templated()) {
representation.put("href-template", hrefTemplate);
representation.put("href-vars", hrefVars());
} else {
representation.put("href", href);
}
if (hints != null) {
Map<String, Object> hintsRepresentation = hints.toRepresentation();
if (!hintsRepresentation.isEmpty()) {
representation.put("hints", hintsRepresentation);
}
}
return representation;
}
@Override
public String toString () {
return "Resource{" +
"rel='" + rel + '\'' +
", href='" + href + '\'' +
", hrefTemplate='" + hrefTemplate + '\'' +
", hrefVars=" + hrefVars() +
", hints=" + hints +
'}';
}
}