All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.hateoas.client.JsonPathLinkDiscoverer Maven / Gradle / Ivy

/*
 * Copyright 2012-2022 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
 *
 *      https://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.client;

import net.minidev.json.JSONArray;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Links;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;

import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;

/**
 * {@link LinkDiscoverer} that uses {@link JsonPath} to find links inside a representation.
 *
 * @author Oliver Gierke
 * @author Greg Turnquist
 */
public class JsonPathLinkDiscoverer implements LinkDiscoverer {

	private final String pathTemplate;
	private final List mediaTypes;

	/**
	 * 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 mediaTypes the {@link MediaType}s to support.
	 */
	public JsonPathLinkDiscoverer(String pathTemplate, MediaType... mediaTypes) {

		Assert.hasText(pathTemplate, "Path template must not be null!");
		Assert.notNull(mediaTypes, "Primary MediaType must not be null!");

		this.pathTemplate = pathTemplate;
		this.mediaTypes = Arrays.asList(mediaTypes);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.lang.String)
	 */
	@Override
	public Optional findLinkWithRel(LinkRelation relation, String representation) {
		return firstOrEmpty(findLinksWithRel(relation, representation));
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.hateoas.LinkDiscoverer#findLinkWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream)
	 */
	@Override
	public Optional findLinkWithRel(LinkRelation relation, InputStream representation) {
		return firstOrEmpty(findLinksWithRel(relation, representation));
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.lang.String)
	 */
	@Override
	public Links findLinksWithRel(LinkRelation relation, String representation) {

		Assert.notNull(relation, "LinkRelation must not be null!");

		try {
			Object parseResult = getExpression(relation).read(representation);
			return createLinksFrom(parseResult, relation);
		} catch (InvalidPathException e) {
			return Links.NONE;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.hateoas.LinkDiscoverer#findLinksWithRel(org.springframework.hateoas.LinkRelation, java.io.InputStream)
	 */
	@Override
	public Links findLinksWithRel(LinkRelation relation, InputStream representation) {

		Assert.notNull(relation, "LinkRelation must not be null!");

		try {

			Object parseResult = getExpression(relation).read(representation);
			return createLinksFrom(parseResult, relation);

		} catch (IOException o_O) {
			throw new RuntimeException(o_O);
		} catch (PathNotFoundException o_O) {
			return Links.NONE;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
	 */
	@Override
	public boolean supports(@NonNull MediaType delimiter) {

		return this.mediaTypes.stream() //
				.anyMatch(mediaType -> mediaType.isCompatibleWith(delimiter));
	}

	/**
	 * Callback for each {@link LinkDiscoverer} to extract relevant attributes and generate a {@link Link}.
	 *
	 * @param element
	 * @param rel
	 * @return link
	 */
	protected Link extractLink(Object element, LinkRelation rel) {
		return Link.of(element.toString(), rel);
	}

	/**
	 * Returns the {@link JsonPath} to find links with the given relation type.
	 *
	 * @param rel
	 * @return
	 */
	private JsonPath getExpression(LinkRelation rel) {
		return JsonPath.compile(String.format(pathTemplate, rel.value()));
	}

	/**
	 * 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 Links createLinksFrom(Object parseResult, LinkRelation rel) {

		if (JSONArray.class.isInstance(parseResult)) {

			JSONArray jsonArray = (JSONArray) parseResult;

			return jsonArray.stream() //
					.flatMap(it -> JSONArray.class.isInstance(it) ? ((JSONArray) it).stream() : Stream.of(it)) //
					.map(it -> extractLink(it, rel)) //
					.collect(Links.collector());
		}

		return Links.of(Map.class.isInstance(parseResult) //
				? extractLink(parseResult, rel) //
				: Link.of(parseResult.toString(), rel));
	}

	private static  Optional firstOrEmpty(Iterable source) {

		Iterator iterator = source.iterator();

		return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy