/*
* Copyright 2012-2014 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.hateoas.core;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.minidev.json.JSONArray;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
/**
* {@link LinkDiscoverer} that uses {@link JsonPath} to find links inside a representation.
*
* @author Oliver Gierke
*/
public class JsonPathLinkDiscoverer implements LinkDiscoverer {
private static Method compileMethod;
private static Object emptyFilters;
static {
// Reflective bridging between JsonPath 0.9.x and 1.x
for (Method candidate : JsonPath.class.getMethods()) {
if (candidate.getName().equals("compile")) {
Class<?>[] paramTypes = candidate.getParameterTypes();
if (paramTypes.length == 2 && paramTypes[0].equals(String.class) && paramTypes[1].isArray()) {
compileMethod = candidate;
emptyFilters = Array.newInstance(paramTypes[1].getComponentType(), 0);
break;
}
}
}
Assert.state(compileMethod != null, "Unexpected JsonPath API - no compile(String, ...) method found");
}
private final String pathTemplate;
private final MediaType mediaType;
/**
* Creates a new {@link JsonPathLinkDiscoverer} using the given path template supporting the given {@link MediaType}.
* The template has to contain a single {@code %s} placeholder which will be replaced by the relation type.
*
* @param pathTemplate must not be {@literal null} or empty and contain a single placeholder.
* @param mediaType the {@link MediaType} to support.
*/
public JsonPathLinkDiscoverer(String pathTemplate, MediaType mediaType) {
Assert.hasText(pathTemplate, "Path template must not be null!");
Assert.isTrue(StringUtils.countOccurrencesOf(pathTemplate, "%s") == 1,
"Path template must contain a single placeholder!");
this.pathTemplate = pathTemplate;
this.mediaType = mediaType;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(java.lang.String, java.lang.String)
*/
@Override
public Link findLinkWithRel(String rel, String representation) {
List<Link> links = findLinksWithRel(rel, representation);
return links.isEmpty() ? null : links.get(0);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(java.lang.String, java.io.InputStream)
*/
@Override
public Link findLinkWithRel(String rel, InputStream representation) {
List<Link> links = findLinksWithRel(rel, representation);
return links.isEmpty() ? null : links.get(0);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(java.lang.String, java.lang.String)
*/
@Override
public List<Link> findLinksWithRel(String rel, String representation) {
try {
Object parseResult = getExpression(rel).read(representation);
return createLinksFrom(parseResult, rel);
} catch (InvalidPathException e) {
return Collections.emptyList();
}
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(java.lang.String, java.io.InputStream)
*/
@Override
public List<Link> findLinksWithRel(String rel, InputStream representation) {
try {
Object parseResult = getExpression(rel).read(representation);
return createLinksFrom(parseResult, rel);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the {@link JsonPath} to find links with the given relation type.
*
* @param rel
* @return
*/
private JsonPath getExpression(String rel) {
return (JsonPath) ReflectionUtils.invokeMethod(compileMethod, null, String.format(pathTemplate, rel), emptyFilters);
}
/**
* Creates {@link Link} instances from the given parse result.
*
* @param parseResult the result originating from parsing the source content using the JSON path expression.
* @param rel the relation type that was parsed for.
* @return
*/
private List<Link> createLinksFrom(Object parseResult, String rel) {
if (parseResult instanceof JSONArray) {
List<Link> links = new ArrayList<Link>();
JSONArray array = (JSONArray) parseResult;
for (Object element : array) {
links.add(new Link(element.toString(), rel));
}
return Collections.unmodifiableList(links);
}
return Collections.unmodifiableList(Arrays.asList(new Link(parseResult.toString(), rel)));
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(MediaType delimiter) {
return this.mediaType == null ? true : this.mediaType.isCompatibleWith(delimiter);
}
}