package com.adidas.dam.marvin.client;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import com.adidas.dam.marvin.client.exception.MarvinClientException;
import com.adidas.dam.marvin.client.query.FileFilter;
import com.adidas.dam.marvin.client.query.Query;
import com.adidas.dam.marvin.client.service.ArticleService;
import com.adidas.dam.marvin.client.service.AssetService;
import com.adidas.dam.marvin.client.service.FileService;
import com.adidas.dam.marvin.domain.Article;
import com.adidas.dam.marvin.domain.ProductImageAsset;
/**
* Client to request meta data as well as images through the
* Marvin REST interface.
*
* @author Daniel Eichten <daniel.eichten@adidas-group.com>
*/
@Component
public class MarvinClient {
@SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(MarvinClient.class);
private static ApplicationContext ctx;
private static String baseUrl;
private static String username;
private static String password;
@Autowired
private ArticleService articleService;
@Autowired
private AssetService assetService;
@Autowired
private FileService fileService;
/**
* Used to retrieve an instance of the Marvin Client appropriate
* to the passed parameters. If parameter changes (so as they do
* during the first call) it will create a completely new
* application context and return the reference on the same
* "singleton" MarvinClient. The Spring container will take care
* that subsequent calls with the same params will always return
* a reference to the same instance.
*
* @param baseUrl The base url to Marvins REST interface including
* version and client identifier with or without trailing slash.
* @param username The username to be used to authenticate against
* the service.
* @param password The password of the user that will be used to
* authenticate with the service.
* @return A reference to a singleton MarvinClient on success.
* @throws MarvinClientException If the passed URL is not valid or
* a MarvinClient object could not be created.
*/
public static synchronized MarvinClient getInstance(
String baseUrl, String username, String password)
throws MarvinClientException {
if (ctx == null || !(MarvinClient.baseUrl.equals(baseUrl) &&
MarvinClient.username.equals(username) &&
MarvinClient.password.equals(password))) {
try {
URL url = new URL(baseUrl);
if (url.getPath().endsWith("/")) {
url = new URL(baseUrl.substring(0, baseUrl.length()-1));
}
ctx = new SpringApplicationBuilder()
.showBanner(true)
.headless(true)
.logStartupInfo(false)
.web(false)
.registerShutdownHook(true)
.sources(ClientConfiguration.class)
.run("--baseUrl=" + url.toString(),
"--username=" + username,
"--password=" + password);
} catch (MalformedURLException mue) {
throw new MarvinClientException(mue);
}
}
final MarvinClient marvinClient = ctx.getBean(MarvinClient.class);
if (marvinClient == null) {
throw new MarvinClientException("Creation of Marvin Client not possible.");
}
return marvinClient;
}
/**
* Retrieves {@link Article} information from the configured base URL and
* returns a {@link List} of {@link Article}s matching the {@link Query} criteria.
*
* @param query {@link Query} to be used to retrieve {@link Article}s
* @return {@link List} of {@link Article}s on success – may be empty if no
* {@link Article} matches.
* @throws MarvinClientException On connection or retrieval issues.
*/
public List<Article> getArticles(Query query) throws MarvinClientException {
return articleService.find(query);
}
/**
* Counts number of {@link Article}s matching the given {@link Query} criteria.
* Please note that this does not consider paging but will always return the number
* or overall matching elements.
*
* @param query {@link Query} to be used to count {@link Article}s
* @return Number of {@link Article}s matching.
* @throws MarvinClientException On connection or retrieval issues.
*/
public long countArticles(Query query) throws MarvinClientException {
return articleService.count(query);
}
/**
* Retrieves {@link ProductImageAsset} information from the configured base URL
* and returns a {@link List} of {@link ProductImageAsset}s matching the
* given {@link Query} criteria.
*
* @param query {@link Query} to be used to retrieve {@link ProductImageAsset}s
* @return {@link List} of {@link ProductImageAsset}s on success – may be empty if no
* {@link ProductImageAsset} matches.
* @throws MarvinClientException On connection or retrieval issues.
*/
public List<ProductImageAsset> getAssets(Query query) throws MarvinClientException {
return assetService.find(query);
}
/**
* Counts number of {@link ProductImageAsset}s matching the given {@link Query} criteria.
* Please note that this does not consider paging but will always return the number
* or overall matching elements.
*
* @param query {@link Query} to be used to count {@link ProductImageAsset}s
* @return Number of {@link ProductImageAsset}s matching.
* @throws MarvinClientException On connection or retrieval issues.
*/
public long countAssets(Query query) throws MarvinClientException {
return assetService.count(query);
}
/**
* Tries to retrieve an image from Marvin using the given {@link FileFilter} expression
* and returns an {@link InputStream} to read the data.
*
* @param filter {@link FileFilter} expression to be used to retrieve an image.
* @param asset {@link ProductImageAsset} to be retrieved. Should be resolved by calling
* {@link #getAssets} before.
* @return {@link InputStream} containing the image
* @throws MarvinClientException On connection or retrieval issues or if image does not exist.
*/
public InputStream getImage(FileFilter filter, ProductImageAsset asset)
throws MarvinClientException {
return new ByteArrayInputStream(fileService.getFile(filter, asset.getLatestFileId()));
}
/**
* In comparison to the synchronous {@link #getImage} which blocks the execution of the caller
* {@link getImageAsync} uses Spring's {@link Async} annotation and returns the {@link InputStream}
* wrapped in a {@link Future} object and immediately returns to allow further execution of the
* calling thread.
*
* @param filter {@link FileFilter} expression to be used to retrieve an image.
* @param asset {@link ProductImageAsset} to be retrieved. Should be resolved by calling
* {@link #getAssets} before.
* @return {@link InputStream} containing the image wrapped into a {@link Future} object.
* @throws MarvinClientException On connection or retrieval issues or if image does not exist.
*/
@Async
public Future<InputStream> getImageAsync(FileFilter filter, ProductImageAsset asset)
throws MarvinClientException {
return new AsyncResult<InputStream>(getImage(filter, asset));
}
}