DiscoveredResource.java

/*
 * Copyright 2012-present 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.cloud.client.hypermedia;

import java.net.URI;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.client.Traverson;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

/**
 * A REST resource that is defined by a service reference and a traversal operation within
 * that service.
 *
 * @author Oliver Gierke
 */
public class DiscoveredResource implements RemoteResource {

	private final ServiceInstanceProvider provider;

	private final TraversalDefinition traversal;

	private final Logger log = LoggerFactory.getLogger(DiscoveredResource.class);

	private RestOperations restOperations = new RestTemplate();

	private Link link = null;

	public DiscoveredResource(ServiceInstanceProvider provider, TraversalDefinition traversal) {
		this.provider = provider;
		this.traversal = traversal;
	}

	public ServiceInstanceProvider getProvider() {
		return this.provider;
	}

	public TraversalDefinition getTraversal() {
		return this.traversal;
	}

	public RestOperations getRestOperations() {
		return this.restOperations;
	}

	/**
	 * Configures the {@link RestOperations} to use to execute the traversal and verifying
	 * HEAD calls.
	 * @param restOperations Can be {@literal null}; resorts to a default
	 * {@link RestTemplate} in that case.
	 */
	public void setRestOperations(RestOperations restOperations) {
		this.restOperations = restOperations == null ? new RestTemplate() : restOperations;
	}

	@Override
	public Link getLink() {
		return this.link;
	}

	public void setLink(Link link) {
		this.link = link;
	}

	/**
	 * Verifies the link to the current.
	 */
	public void verifyOrDiscover() {
		this.link = this.link == null ? discoverLink() : verify(this.link);
	}

	/**
	 * Verifies the given {@link Link} by issuing an HTTP HEAD request to the resource.
	 * @param link Must not be {@literal null}.
	 * @return - link to the resource
	 */
	private Link verify(Link link) {

		Assert.notNull(link, "Link must not be null!");

		try {

			String uri = link.expand().getHref();

			this.log.debug("Verifying link pointing to {}...", uri);
			this.restOperations.headForHeaders(uri);
			this.log.debug("Successfully verified link!");

			return link;

		}
		catch (RestClientException e) {

			this.log.debug("Verification failed, marking as outdated!");
			return null;
		}
	}

	private Link discoverLink() {

		try {

			ServiceInstance service = this.provider.getServiceInstance();

			if (service == null) {
				return null;
			}

			URI uri = service.getUri();
			String serviceId = service.getServiceId();

			this.log.debug("Discovered {} system at {}. Discovering resource...", serviceId, uri);

			Traverson traverson = new Traverson(uri, MediaTypes.HAL_JSON);
			Link link = this.traversal.buildTraversal(traverson).asTemplatedLink();

			this.log.debug("Found link pointing to {}.", link.getHref());

			return link;

		}
		catch (RuntimeException exception) {

			this.link = null;
			this.log.debug("Target system unavailable. Got: ", exception.getMessage());

			return null;
		}
	}

}