package cz.cuni.mff.d3s.been.swrepoclient; import static cz.cuni.mff.d3s.been.swrepository.HeaderNames.ARTIFACT_IDENTIFIER_HEADER_NAME; import static cz.cuni.mff.d3s.been.swrepository.HeaderNames.BPK_IDENTIFIER_HEADER_NAME; import static cz.cuni.mff.d3s.been.swrepository.UrlPaths.*; import static cz.cuni.mff.d3s.been.swrepository.Versions.SNAPSHOT_SUFFIX; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBException; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.maven.artifact.Artifact; import org.codehaus.jackson.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; import cz.cuni.mff.d3s.been.bpk.ArtifactIdentifier; import cz.cuni.mff.d3s.been.bpk.Bpk; import cz.cuni.mff.d3s.been.bpk.BpkIdentifier; import cz.cuni.mff.d3s.been.core.jaxb.BindingParser; import cz.cuni.mff.d3s.been.core.jaxb.ConvertorException; import cz.cuni.mff.d3s.been.core.jaxb.XSD; import cz.cuni.mff.d3s.been.core.task.TaskContextDescriptor; import cz.cuni.mff.d3s.been.core.task.TaskDescriptor; import cz.cuni.mff.d3s.been.datastore.*; import cz.cuni.mff.d3s.been.util.JSONUtils; import cz.cuni.mff.d3s.been.util.JsonException; class HttpSwRepoClient implements SwRepoClient { /** * HTTP implementation specific log for the sw repo client */ private static final Logger log = LoggerFactory.getLogger(HttpSwRepoClient.class); /** * Hostname where the software repository resides */ private final String hostname; /** * Port on which the software repository listens */ private final Integer port; /** * Data store to use for caching */ private final SoftwareStore softwareCache; /** * JSON utilities for serialization */ private final JSONUtils jsonUtils; /** * Constructs new software repository client * * @param hostname * hostname on which the software repository is running * @param port * port on which the software repository is running * @param softwareCache * initialized software cache */ HttpSwRepoClient(String hostname, Integer port, SoftwareStore softwareCache) { this.hostname = hostname; this.port = port; this.softwareCache = softwareCache; this.jsonUtils = JSONUtils.newInstance(); } // -------------------------------------- // API IMPLEMENTATION METHODS // -------------------------------------- @Override public void putArtifact(ArtifactIdentifier artifactIdentifier, InputStream artifactInputStream) throws SwRepositoryClientException { if (artifactIdentifier == null) { String msg = "Failed to upload Artifact - artifact meta-info was null."; log.error(msg); throw new SwRepositoryClientException(msg); } Header header = new Header(ARTIFACT_IDENTIFIER_HEADER_NAME, artifactIdentifier); doPutStream(ARTIFACT_URI, artifactInputStream, header); } @Override public Artifact getArtifact(ArtifactIdentifier artifactIdentifier) { StoreReader artifactReader = softwareCache.getArtifactReader(artifactIdentifier); if (artifactReader != null) { return new ArtifactFromStore(artifactIdentifier, artifactReader); } else { return getArtifactByHTTP(artifactIdentifier); } } @Override public Bpk getBpk(BpkIdentifier bpkIdentifier) { if (bpkIdentifier.getVersion().endsWith(SNAPSHOT_SUFFIX)) { return getBpkByHTTP(bpkIdentifier); } StoreReader bpkReader = softwareCache.getBpkReader(bpkIdentifier); if (bpkReader != null) { return new BpkFromStore(bpkReader, bpkIdentifier); } else { return getBpkByHTTP(bpkIdentifier); } } @Override public Bpk getBpkNoCache(final BpkIdentifier bpkIdentifier) { Header header = new Header(BPK_IDENTIFIER_HEADER_NAME, bpkIdentifier); final InputStream is = doGetInputStream(BPK_URI, header); if (is == null) { return null; } return new Bpk() { @Override public BpkIdentifier getBpkIdentifier() { return bpkIdentifier; } @Override public InputStream getInputStream() throws IOException { return is; } }; } @Override public void putBpk(BpkIdentifier bpkMetaInfo, InputStream bpkInputStream) throws SwRepositoryClientException { if (bpkMetaInfo == null) { String msg = "Failed to upload BPK - package meta-info was null."; log.error(msg); throw new SwRepositoryClientException(msg); } Header header = new Header(BPK_IDENTIFIER_HEADER_NAME, bpkMetaInfo); doPutStream(BPK_URI, bpkInputStream, header); } @Override public Collection<BpkIdentifier> listBpks() { return doGetObject(BPK_LIST_URI, new TypeReference<List<BpkIdentifier>>() {}); } @Override public Map<String, TaskContextDescriptor> listTaskContextDescriptors(BpkIdentifier bpkIdentifier) { Header header = new Header(BPK_IDENTIFIER_HEADER_NAME, bpkIdentifier); // 1st argument = TD filename, 2nd argument = TD json Map<String, String> jsonDescriptors = doGetObject( TASK_CONTEXT_DESCRIPTOR_LIST_URI, new TypeReference<Map<String, String>>() {}, header); Map<String, TaskContextDescriptor> convertedDescriptors = new HashMap<>(); if (jsonDescriptors != null) { for (Map.Entry<String, String> entry : jsonDescriptors.entrySet()) { BindingParser<TaskContextDescriptor> parser; try { parser = XSD.TASK_CONTEXT_DESCRIPTOR.createParser(TaskContextDescriptor.class); convertedDescriptors.put(entry.getKey(), parser.parse(new ByteArrayInputStream(entry.getValue().getBytes()))); } catch (SAXException | ConvertorException | JAXBException e) { log.error(String.format("Failed to convert task context descriptor %s", entry.getKey()), e); } } } return convertedDescriptors; } @Override public Map<String, TaskDescriptor> listTaskDescriptors(BpkIdentifier bpkIdentifier) { Header header = new Header(BPK_IDENTIFIER_HEADER_NAME, bpkIdentifier); // 1st argument = TD filename, 2nd argument = TD json Map<String, String> jsonDescriptors = doGetObject( TASK_DESCRIPTOR_LIST_URI, new TypeReference<Map<String, String>>() {}, header); Map<String, TaskDescriptor> convertedDescriptors = new HashMap<>(); if (jsonDescriptors != null) { for (Map.Entry<String, String> entry : jsonDescriptors.entrySet()) { BindingParser<TaskDescriptor> parser; try { parser = XSD.TASK_DESCRIPTOR.createParser(TaskDescriptor.class); convertedDescriptors.put(entry.getKey(), parser.parse(new ByteArrayInputStream(entry.getValue().getBytes()))); } catch (SAXException | ConvertorException | JAXBException e) { log.error(String.format("Failed to convert task descriptor %s", entry.getKey()), e); } } } return convertedDescriptors; } // ===================================== // PRIVATE METHODS // ===================================== /** * Synthesize the URI of the software repository from internals * * @return the URI of the repository * @throws URISyntaxException * When some of the internals are malformed */ private URI createRepoUri() throws URISyntaxException { URIBuilder uriBuilder = new URIBuilder(); uriBuilder.setHost(hostname); uriBuilder.setPort(port); uriBuilder.setScheme("http"); return uriBuilder.build(); } /** * Ask the repository for a Maven artifact by HTTP * * @return found artifact or null when artifact was not found */ private Artifact getArtifactByHTTP(ArtifactIdentifier artifactIdentifier) { Header header = new Header(ARTIFACT_IDENTIFIER_HEADER_NAME, artifactIdentifier); InputStream is = doGetInputStream(ARTIFACT_URI, header); if (is == null) { return null; } StorePersister sp = softwareCache.getArtifactPersister(artifactIdentifier); try { sp.dump(is); } finally { IOUtils.closeQuietly(is); } StoreReader sr = softwareCache.getArtifactReader(artifactIdentifier); if (sr == null) { return null; } else { return new ArtifactFromStore(artifactIdentifier, sr); } } /** * Ask the repository for a BPK by HTTP */ private Bpk getBpkByHTTP(BpkIdentifier bpkIdentifier) { Header header = new Header(BPK_IDENTIFIER_HEADER_NAME, bpkIdentifier); InputStream is = doGetInputStream(BPK_URI, header); if (is == null) { return null; } StorePersister sp = softwareCache.getBpkPersister(bpkIdentifier); try { sp.dump(is); } finally { IOUtils.closeQuietly(is); } StoreReader sr = softwareCache.getBpkReader(bpkIdentifier); if (sr == null) { return null; } else { return new BpkFromStore(softwareCache.getBpkReader(bpkIdentifier), bpkIdentifier); } } /** * Do GET request on software repository server and return deserialized object * of given type.. * * @param abstractUri * abstract part of uri for get request * @param type * type reference of object which will be returned * @param headers * request headers * @param <T> * type of object which will be returned * @return deserialized object of expected type or null if object couldn't be * deserialized from some reason */ private <T> T doGetObject(String abstractUri, TypeReference<T> type, Header... headers) { try (InputStream is = doGetInputStream(abstractUri, headers)) { if (is == null) { return null; } String jsonString = IOUtils.toString(is); return jsonUtils.deserialize(jsonString, type); } catch (IOException e) { log.error( "Failed to GET item from software repository - I/O exception occurs when reading response input stream to string", e); return null; } catch (JsonException e) { log.error( "Failed to GET item from software repository - cannot deserialize return value from json string to object", e); return null; } } /** * Do GET request on software repository server and return response input * stream * * @param abstractUri * abstract part of uri for get request * @param headers * request headers * @return input stream from http response */ private InputStream doGetInputStream(String abstractUri, Header... headers) { String uri; try { uri = createRepoUri() + abstractUri; } catch (URISyntaxException e) { log.error("Failed to GET item from software repository - unable to synthesize GET request URI", e); return null; } HttpGet request = new HttpGet(uri); try { for (Header header : headers) { request.addHeader(header.key, jsonUtils.serialize(header.value)); } } catch (JsonException e) { log.error( "Failed to GET item from software repository - cannot serialize request header object to json string", e); return null; } HttpClient httpCli = new DefaultHttpClient(); HttpResponse response; try { response = httpCli.execute(request); } catch (ClientProtocolException e) { log.error("Failed to GET item from software repository - http protocol error", e); return null; } catch (IOException e) { log.error("Failed to GET item from software repository - I/O error or connection re-set", e); return null; } if (response.getStatusLine().getStatusCode() / 100 != 2) { log.error( "Failed to GET item from software repository - server refusal: '{}'", response.getStatusLine().getReasonPhrase()); return null; } try { return response.getEntity().getContent(); } catch (IOException e) { log.error("Failed to GET item from software repository - content stream cannot be opened", e); return null; } } /** * Do PUT request with given object stream as body message. * * @param abstractUri * abstract part of uri for get request * @param objectStreamToPut * stream which will be added to body message * @param headers * request headers * @throws SwRepositoryClientException * if PUT operation cannot be performed from some reason. Reason can * be one of following; * <ul> * <li>'objectStreamToPut' is null</li> * <li>PUT request URI was not correctly synthesized</li> * <li>Request headers was could not correctly serialized</li> * <li>http protocol error or connection reset</li> * <li>response status was not of type 2xx</li> * </ul> */ private void doPutStream(String abstractUri, InputStream objectStreamToPut, Header... headers) throws SwRepositoryClientException { if (objectStreamToPut == null) { String msg = "Failed to PUT item to software repository - object given to send was null"; log.error(msg); throw new SwRepositoryClientException(msg); } String uri; try { uri = createRepoUri() + abstractUri; } catch (URISyntaxException e) { String msg = "Failed to PUT item to software repository - unable to synthesize PUT request URI"; log.error(msg, e); throw new SwRepositoryClientException(msg, e); } HttpPut request = new HttpPut(uri); try { for (Header header : headers) { request.addHeader(header.key, jsonUtils.serialize(header.value)); } } catch (JsonException e) { String msg = "Failed to PUT item to software repository - cannot serialize request header object to json string"; log.error(msg, e); throw new SwRepositoryClientException(msg, e); } InputStreamEntity sentEntity = new InputStreamEntity(objectStreamToPut, -1); request.setEntity(sentEntity); // entity closes the stream HttpClient httpCli = new DefaultHttpClient(); HttpResponse response; try { response = httpCli.execute(request); } catch (ClientProtocolException e) { String msg = "Failed to PUT item to software repository - http protocol error"; log.error(msg, e); throw new SwRepositoryClientException(msg, e); } catch (IOException e) { String msg = "Failed to PUT item to software repository - I/O error or connection re-set"; log.error(msg, e); throw new SwRepositoryClientException(msg, e); } if ((response.getStatusLine().getStatusCode() / 100) != 2) { String msg = String.format( "Failed to PUT item to software repository - server error: '%s'", response.getStatusLine().getReasonPhrase()); log.error(msg); throw new SwRepositoryClientException(msg); } } /** * Internal representation of http header. */ private static final class Header { public final String key; public final Object value; public Header(String key, Object value) { this.key = key; this.value = value; } } }