Fix download of large artifacts 97/2897/1
authorSerban Jora <sj2381@att.com>
Thu, 20 Sep 2018 20:57:29 +0000 (16:57 -0400)
committerSerban Jora <sj2381@att.com>
Thu, 20 Sep 2018 21:00:51 +0000 (17:00 -0400)
Change-Id: Ia970dab51bf5ff9a9429252aa64da695af284657
Issue-ID: ACUMOS-1751
Signed-off-by: Serban Jora <sj2381@att.com>
13 files changed:
gateway/src/main/java/org/acumos/federation/gateway/adapter/PeerGateway.java
gateway/src/main/java/org/acumos/federation/gateway/adapter/onap/ONAP.java
gateway/src/main/java/org/acumos/federation/gateway/common/Clients.java
gateway/src/main/java/org/acumos/federation/gateway/common/FederationClient.java
gateway/src/main/java/org/acumos/federation/gateway/config/InterfaceConfiguration.java
gateway/src/main/java/org/acumos/federation/gateway/config/InterfaceConfigurationBuilder.java
gateway/src/main/java/org/acumos/federation/gateway/config/NexusConfiguration.java
gateway/src/main/java/org/acumos/federation/gateway/controller/CatalogController.java
gateway/src/main/java/org/acumos/federation/gateway/service/ContentService.java
gateway/src/main/java/org/acumos/federation/gateway/service/ServiceException.java
gateway/src/main/java/org/acumos/federation/gateway/service/impl/ContentServiceImpl.java
gateway/src/main/java/org/acumos/federation/gateway/service/impl/ContentServiceLocalImpl.java
gateway/src/test/java/org/acumos/federation/gateway/test/PeerGatewayTest.java

index a17a013..7f8ece5 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.acumos.federation.gateway.adapter;
 
+import java.io.Closeable;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -378,6 +380,11 @@ public class PeerGateway {
                                                                log.error(EELFLoggerDelegate.errorLogger,
                                                                                        "Failed to store artifact content to local repo", sx);
                                                        }
+                                                       finally {
+                                                               if (artifactContent instanceof Closeable) {
+                                                                       ((Closeable)artifactContent).close();
+                                                               }
+                                                       }
                                                }
                                        }
 
index 35fa9aa..07acf3d 100644 (file)
@@ -562,7 +562,7 @@ public class ONAP {
                        String theAcumosSolutionId, String theAcumosRevisionId, MLPArtifact theAcumosArtifact)
                                                                                                                                                                                                                                                                                                                                                        throws Exception {
                        if (this.peer.isLocal()) {
-                               return clients.getNexusClient().getArtifact(theAcumosArtifact.getUri()).toByteArray();
+                               return clients.getNexusClient().getForObject(theAcumosArtifact.getUri(), byte[].class);
                        }
                        else { //non-local peer
                                ByteArrayOutputStream bos = new ByteArrayOutputStream();
index 10bfa74..bddfadc 100644 (file)
@@ -30,7 +30,7 @@ import org.acumos.federation.gateway.config.EELFLoggerDelegate;
 import org.acumos.federation.gateway.config.FederationInterfaceConfiguration;
 import org.acumos.federation.gateway.config.LocalInterfaceConfiguration;
 import org.acumos.federation.gateway.config.NexusConfiguration;
-import org.acumos.nexus.client.NexusArtifactClient;
+import org.springframework.web.client.RestTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -75,7 +75,7 @@ public class Clients {
        }
 
        /** */
-       public NexusArtifactClient getNexusClient() {
+       public RestTemplate getNexusClient() {
                return nexusConfig.getNexusClient();
        }
 
index 1ab9892..852320c 100644 (file)
 
 package org.acumos.federation.gateway.common;
 
+import java.io.Closeable;
+import java.io.InputStream;
+import java.io.IOException;
+
 import java.lang.invoke.MethodHandles;
 import java.net.URI;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.lang.exception.ExceptionUtils;
+
 import org.acumos.cds.domain.MLPArtifact;
 import org.acumos.cds.domain.MLPDocument;
 import org.acumos.cds.domain.MLPPeer;
@@ -36,14 +42,19 @@ import org.acumos.federation.gateway.util.Utils;
 import org.apache.http.client.HttpClient;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.core.io.Resource;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.util.Base64Utils;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.RequestEntity;
 import org.springframework.http.ResponseEntity;
-import org.springframework.util.Base64Utils;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.ResourceAccessException;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 
@@ -53,6 +64,8 @@ public class FederationClient extends AbstractClient {
 
        private static final EELFLoggerDelegate log = EELFLoggerDelegate.getLogger(MethodHandles.lookup().lookupClass());
 
+       private HttpClient client;
+
        /**
         * @param theTarget
         *            Target
@@ -61,10 +74,12 @@ public class FederationClient extends AbstractClient {
         */
        public FederationClient(String theTarget, HttpClient theClient) {
                super(theTarget, theClient);
+               this.client = theClient;
        }
 
        public FederationClient(String theTarget, HttpClient theClient, ObjectMapper theMapper) {
                super(theTarget, theClient, theMapper);
+               this.client = theClient;
        }
 
        /**
@@ -305,7 +320,7 @@ public class FederationClient extends AbstractClient {
         */
        public Resource getArtifactContent(String theSolutionId, String theRevisionId, String theArtifactId)
                                                                                                                                                                                                                                                                                                                                                        throws HttpStatusCodeException {
-               return download(API.ARTIFACT_CONTENT.buildUri(this.baseUrl, theSolutionId, theRevisionId, theArtifactId));
+               return download2(API.ARTIFACT_CONTENT.buildUri(this.baseUrl, theSolutionId, theRevisionId, theArtifactId));
        }
 
        /**
@@ -354,11 +369,11 @@ public class FederationClient extends AbstractClient {
         */
        public Resource getDocumentContent(String theSolutionId, String theRevisionId, String theDocumentId)
                                                                                                                                                                                                                                                                                                                                                throws HttpStatusCodeException {
-               return download(API.DOCUMENT_CONTENT.buildUri(this.baseUrl, theSolutionId, theRevisionId, theDocumentId));
+               return download2(API.DOCUMENT_CONTENT.buildUri(this.baseUrl, theSolutionId, theRevisionId, theDocumentId));
        }
 
        protected Resource download(URI theUri) throws HttpStatusCodeException {
-               log.info(EELFLoggerDelegate.debugLogger, "Query for {}", theUri);
+               log.info(EELFLoggerDelegate.debugLogger, "Query for download {}", theUri);
                ResponseEntity<Resource> response = null;
                RequestEntity<Void> request = RequestEntity
                                                                                                                                        .get(theUri)
@@ -389,6 +404,61 @@ public class FederationClient extends AbstractClient {
                }
        }
 
+       /**
+        * Important: the Resource returned by this method MUST BE CLOSED by whoever uses it.
+        */
+       protected StreamingResource download2(URI theUri) throws HttpStatusCodeException {
+               log.info(EELFLoggerDelegate.debugLogger, "Query for download {}", theUri);
+               ClientHttpResponse response = null;
+               try {
+                       ClientHttpRequest request =     new HttpComponentsClientHttpRequestFactory(this.client)
+                                                                                                                                               .createRequest(theUri, HttpMethod.GET);
+                       request.getHeaders().setAccept(Collections.singletonList(MediaType.ALL));
+                       response = request.execute();
+                       HttpStatus status = HttpStatus.valueOf(response.getRawStatusCode());
+                       if (!status.is2xxSuccessful())
+                               throw new HttpClientErrorException(status, response.getStatusText());
+               
+                       log.info(EELFLoggerDelegate.debugLogger, "Query for download got response {}", response);
+       
+                       return new StreamingResource(response);
+               }
+               catch (IOException ex) {
+                       throw new ResourceAccessException("I/O error for " + theUri + ": " + ex.getMessage(), ex);
+               }
+       }
+
+       public static class StreamingResource extends InputStreamResource
+                                                                                                                                                               implements Closeable {
+
+               private static final EELFLoggerDelegate log = EELFLoggerDelegate.getLogger(MethodHandles.lookup().lookupClass());
+               private ClientHttpResponse response;
+
+               StreamingResource(ClientHttpResponse theResponse) throws IOException {
+                       super(theResponse.getBody());
+                       this.response = theResponse;
+               }
+
+               @Override
+               public InputStream getInputStream() throws IOException, IllegalStateException {
+                       log.info(EELFLoggerDelegate.debugLogger, "Download input stream access at {}",ExceptionUtils.getStackTrace(new RuntimeException("Input stream access")) );
+                       return super.getInputStream();
+               }
+
+               @Override
+               public long contentLength() throws java.io.IOException {
+                       return this.response.getHeaders().getContentLength();
+               }
+
+               @Override
+               public void close()  throws IOException {
+                       log.info(EELFLoggerDelegate.debugLogger, "Streaming resource closed");
+                       this.response.close();
+               }
+       }
+
+
+
        /**
         * @return Register self with the peer this client points to.
         * @throws HttpStatusCodeException
index 84f4336..16b4b5e 100644 (file)
@@ -31,6 +31,7 @@ import java.security.KeyStoreException;
 import java.security.cert.X509Certificate;
 import java.util.Enumeration;
 import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
 
 import javax.annotation.PostConstruct;
 import javax.net.ssl.SSLContext;
@@ -183,6 +184,22 @@ public class InterfaceConfiguration {
                return certEntry.getSubjectX500Principal().getName();
        }
 
+       @Override
+       public String toString() {
+               return new StringBuilder()
+                       .append(super.toString())
+                       .append('(')
+                       .append(this.address)
+                       .append(',')
+                       .append(this.server)
+                       .append(',')
+                       .append(this.client)
+                       .append(',')
+                       .append(this.ssl)
+                       .append(')')
+                       .toString();
+       }
+
        /**
         * Loads the specified key store
         * @return the key store
@@ -244,6 +261,13 @@ public class InterfaceConfiguration {
                private String username;
                private String passwd;
 
+               public Client() {}
+
+               public Client(String theUsername, String thePassword) {
+                       setUsername(theUsername);
+                       setPassword(thePassword);
+               }
+
                public String getUsername() {
                        return this.username;
                }
@@ -253,13 +277,24 @@ public class InterfaceConfiguration {
                }
 
                public String getPassword() {
-                       return this.username;
+                       return this.passwd;
                }
 
                public void setPassword(String thePassword) {
                        this.passwd = thePassword;
                }
 
+               @Override
+               public String toString() {
+                       return new StringBuilder()
+                               .append(super.toString())
+                               .append('(')
+                               .append(this.username)
+                               .append(',')
+                               .append(this.passwd)
+                               .append(')')
+                               .toString();
+               }
        }
 
        /**
@@ -276,6 +311,15 @@ public class InterfaceConfiguration {
                        this.port = thePort;
                }
 
+               @Override
+               public String toString() {
+                       return new StringBuilder()
+                               .append(super.toString())
+                               .append('(')
+                               .append(this.port)
+                               .append(')')
+                               .toString();
+               }
        }
 
        /**
@@ -374,19 +418,6 @@ public class InterfaceConfiguration {
                }
        }
 
-       public String toString() {
-               return new StringBuilder("")
-                       .append(super.toString())
-                       .append("(")
-                       .append(this.address)
-                       .append(",")
-                       .append(this.server)
-                       .append(",")
-                       .append(this.ssl)
-                       .append(")")
-                       .toString();
-       }
-
        /**
         * Configure the existing/default/a servlet container with the configuration
         * information of this interface.
@@ -459,7 +490,7 @@ public class InterfaceConfiguration {
                        this.resourceLoader = new DefaultResourceLoader();
 
                if (this.ssl == null) {
-                       log.info(EELFLoggerDelegate.debugLogger, "No ssl config was provided");
+                       log.trace(EELFLoggerDelegate.debugLogger, "No ssl config was provided");
                }
                else {
                        KeyStore keyStore = loadKeyStore(),
@@ -492,7 +523,7 @@ public class InterfaceConfiguration {
                if (sslContext != null) {
                        sslSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1.2" }, null,
                                        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
-                       log.info(EELFLoggerDelegate.debugLogger, "SSL connection factory configured");
+                       log.trace(EELFLoggerDelegate.debugLogger, "SSL connection factory configured");
                }
 
                RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
@@ -513,9 +544,10 @@ public class InterfaceConfiguration {
                if (hasClient()) {
                        credsProvider = new BasicCredentialsProvider();
                        credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(this.client.getUsername(), this.client.getPassword()));
-                       log.info(EELFLoggerDelegate.debugLogger, "Credentials configured");
-               } else {
-                       log.info(EELFLoggerDelegate.debugLogger, "No credentials were provided");
+                       log.trace(EELFLoggerDelegate.debugLogger, "Credentials configured");
+               }
+               else {
+                       log.trace(EELFLoggerDelegate.debugLogger, "No credentials were provided");
                }
 
                HttpClientBuilder clientBuilder = HttpClients.custom();
index e9de797..05c0984 100644 (file)
 
 package org.acumos.federation.gateway.config;
 
+import java.net.UnknownHostException;
+
+/**
+ * Facilitate the construction of interface configurations.
+ */
 public class InterfaceConfigurationBuilder
 // <A extends HttpClientConfigurationBuilder<A,T>,
 // T extends HttpClientConfiguration>
@@ -39,11 +44,40 @@ public class InterfaceConfigurationBuilder
                return this.config;
        }
 
+       public static InterfaceConfigurationBuilder buildFrom(InterfaceConfiguration theConfig) {
+               try {
+                       return
+                               new InterfaceConfigurationBuilder()
+                                       .withAddress(theConfig.getAddress())
+                                       .withClient(theConfig.getClient())
+                                       .withServer(theConfig.getServer())
+                                       .withSSL(theConfig.getSSL());
+               }
+               catch (UnknownHostException uhx) {
+                       throw new RuntimeException("Whaaat ?!", uhx);
+               }
+       }
+
        public InterfaceConfigurationBuilder/* A */ withSSL(InterfaceConfiguration.SSL theSSL) {
                this.config.setSSL(theSSL);
                return builder();
        }
 
+       public InterfaceConfigurationBuilder withClient(InterfaceConfiguration.Client theClient) {
+               this.config.setClient(theClient);
+               return builder();
+       }
+
+       public InterfaceConfigurationBuilder withServer(InterfaceConfiguration.Server theServer) {
+               this.config.setServer(theServer);
+               return builder();
+       }
+
+       public InterfaceConfigurationBuilder withAddress(String theAddress) throws UnknownHostException {
+               this.config.setAddress(theAddress);
+               return builder();
+       }
+
        /** */
        public static class SSLBuilder {
 
index ba0b18f..1ea386a 100644 (file)
@@ -22,8 +22,10 @@ package org.acumos.federation.gateway.config;
 
 import java.lang.invoke.MethodHandles;
 
-import org.acumos.nexus.client.NexusArtifactClient;
-import org.acumos.nexus.client.RepositoryLocation;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -35,44 +37,51 @@ import org.springframework.stereotype.Component;
 public class NexusConfiguration {
 
        private static final EELFLoggerDelegate log = EELFLoggerDelegate.getLogger(MethodHandles.lookup().lookupClass());
-       private RepositoryLocation repositoryLocation;
-       private String                                           groupId;
-       private String                                           nameSeparator;
+
+       private String          proxy;
+       private String    groupId;
+       private String    id;
+       private String          url;
+       private String          username;
+       private String          password;
+       private String          nameSeparator;
+       @Autowired
+       private LocalInterfaceConfiguration localIfConfig = null;
 
        public NexusConfiguration() {
                reset();
        }
 
        private void reset() {
-               this.repositoryLocation = new RepositoryLocation();
                //defaults
-               this.repositoryLocation.setId("1");
+               this.id = "1";
                this.groupId = null;
                this.nameSeparator = ".";
        }
 
        public void setId(String theId) {
-               this.repositoryLocation.setId(theId);
+               this.id = theId;
        }
 
        public void setUrl(String theUrl) {
-               this.repositoryLocation.setUrl(theUrl);
+               this.url = theUrl;
+  }
+       
+       public String getUrl() {
+               return this.url;
   }
 
        public void setUsername(String theUsername) {
-               this.repositoryLocation.setUsername(theUsername);
+               this.username = theUsername;
        }
 
        public void setPassword(String thePassword) {
-               this.repositoryLocation.setPassword(thePassword);
+               this.password = thePassword;
        }
 
+       @Deprecated
        public void setProxy(String theProxy) {
-               this.repositoryLocation.setProxy(theProxy);
-       }
-
-       public RepositoryLocation getRepositoryLocation() {
-               return this.repositoryLocation;
+               this.proxy = theProxy;
        }
 
        public void setGroupId(String theGroupId) {
@@ -91,8 +100,28 @@ public class NexusConfiguration {
                return this.nameSeparator;
        }
 
-       /** */
-       public NexusArtifactClient getNexusClient() {
-               return new NexusArtifactClient(getRepositoryLocation());
+       public RestTemplate getNexusClient() {
+
+               //cannot use the localIfConfig straightup as it does not carry the Nexus specific client authentication info
+               //but this only need to be built once
+               InterfaceConfiguration nexusIfConfig = InterfaceConfigurationBuilder.buildFrom(this.localIfConfig)
+                                                                                                                                                                                       .withClient(new InterfaceConfiguration.Client(this.username, this.password))
+                                                                                                                                                                                       .buildConfig();
+
+               
+               log.info(EELFLoggerDelegate.debugLogger, "Nexus config: {}", nexusIfConfig);
+
+               RestTemplateBuilder builder =
+                       new RestTemplateBuilder()
+                               .requestFactory(new HttpComponentsClientHttpRequestFactory( 
+                                                                                                       nexusIfConfig.buildClient()));
+               if (this.url != null) {
+                       builder.rootUri(this.url);
+               }
+               if (this.username != null) {
+                       builder.basicAuthorization(this.username, this.password);
+               }
+
+               return builder.build();
        }
 }
index 20c0a45..c35bc84 100644 (file)
@@ -42,7 +42,7 @@ import org.acumos.federation.gateway.service.CatalogService;
 import org.acumos.federation.gateway.service.ContentService;
 import org.acumos.federation.gateway.util.Utils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.Resource;
 import org.springframework.http.MediaType;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Controller;
@@ -388,11 +388,11 @@ public class CatalogController extends AbstractController {
         */
        @CrossOrigin
        @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
-       @ApiOperation(value = "API to download artifact content", response = InputStreamResource.class, code = 200)
+       @ApiOperation(value = "API to download artifact content", response = Resource.class, code = 200)
        @RequestMapping(value = {
                        API.Paths.ARTIFACT_CONTENT }, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
        @ResponseBody
-       public Callable<InputStreamResource> getArtifactContent(HttpServletRequest theHttpRequest,
+       public Callable<Resource> getArtifactContent(HttpServletRequest theHttpRequest,
                        HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId,
                        @PathVariable("revisionId") String theRevisionId, @PathVariable("artifactId") String theArtifactId) {
                        
@@ -402,8 +402,8 @@ public class CatalogController extends AbstractController {
                theHttpResponse.setStatus(HttpServletResponse.SC_OK);
 
                final ControllerContext ctx = new ControllerContext();
-               return new Callable<InputStreamResource>() {
-                       public InputStreamResource call() throws Exception {
+               return new Callable<Resource>() {
+                       public Resource call() throws Exception {
                                try {   
                                        return contentService.getArtifactContent(
                                                theSolutionId, theRevisionId, catalogService.getSolutionRevisionArtifact(theArtifactId, ctx), ctx);
@@ -432,11 +432,11 @@ public class CatalogController extends AbstractController {
         */
        @CrossOrigin
        @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
-       @ApiOperation(value = "API to download a documents' content", response = InputStreamResource.class, code = 200)
+       @ApiOperation(value = "API to download a documents' content", response = Resource.class, code = 200)
        @RequestMapping(value = {
                        API.Paths.DOCUMENT_CONTENT }, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
        @ResponseBody
-       public Callable<InputStreamResource> getDocumentContent(HttpServletRequest theHttpRequest,
+       public Callable<Resource> getDocumentContent(HttpServletRequest theHttpRequest,
                        HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId,
                        @PathVariable("revisionId") String theRevisionId, @PathVariable("documentId") String theDocumentId) {
                        
@@ -446,8 +446,8 @@ public class CatalogController extends AbstractController {
                theHttpResponse.setStatus(HttpServletResponse.SC_OK);
 
                final ControllerContext ctx = new ControllerContext();
-               return new Callable<InputStreamResource>() {
-                       public InputStreamResource call() throws Exception {
+               return new Callable<Resource>() {
+                       public Resource call() throws Exception {
                                try {   
                                        return contentService.getDocumentContent(
                                                                        theSolutionId, theRevisionId, catalogService.getSolutionRevisionDocument(theDocumentId, ctx), ctx);
index 5aad914..474a8b1 100644 (file)
@@ -25,7 +25,6 @@ package org.acumos.federation.gateway.service;
 
 import org.acumos.cds.domain.MLPArtifact;
 import org.acumos.cds.domain.MLPDocument;
-import org.springframework.core.io.InputStreamResource;
 import org.springframework.core.io.Resource;
 
 /**
@@ -44,7 +43,7 @@ public interface ContentService {
         *            the execution context
         * @return resource containing access to the actual artifact content
         */
-       public InputStreamResource getArtifactContent(
+       public Resource getArtifactContent(
                        String theSolutionId, String theRevisionId, MLPArtifact theArtifact, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                throws ServiceException;
 
@@ -57,7 +56,7 @@ public interface ContentService {
         *            The CDS representation of artifact metadata
         * @return resource containing access to the actual artifact content
         */
-       public default InputStreamResource getArtifactContent(
+       public default Resource getArtifactContent(
                        String theSolutionId, String theRevisionId, MLPArtifact theArtifact)                                    throws ServiceException {
                return getArtifactContent(theSolutionId, theRevisionId, theArtifact, selfService());
        }
@@ -106,7 +105,7 @@ public interface ContentService {
         *            the execution context
         * @return resource containing access to the actual document content
         */
-       public InputStreamResource getDocumentContent(
+       public Resource getDocumentContent(
                String theSolutionId, String theRevisionId, MLPDocument theDocument, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                throws ServiceException;
 
@@ -119,7 +118,7 @@ public interface ContentService {
         *            The CDS representation of document metadata
         * @return resource containing access to the actual document content
         */
-       public default InputStreamResource getDocumentContent(
+       public default Resource getDocumentContent(
                String theSolutionId, String theRevisionId, MLPDocument theDocument) throws ServiceException {
 
                return getDocumentContent(theSolutionId, theRevisionId, theDocument, selfService());
index 758507b..82205e9 100644 (file)
@@ -25,11 +25,11 @@ package org.acumos.federation.gateway.service;
  */
 public class ServiceException extends Exception {
 
-       public ServiceException(String theMessage, Exception theCause) {
+       public ServiceException(String theMessage, Throwable theCause) {
                super(theMessage, theCause);
        }
 
-       public ServiceException(Exception theCause) {
+       public ServiceException(Throwable theCause) {
                super("", theCause);
        }
 
index 33162fe..c962abf 100644 (file)
  */
 package org.acumos.federation.gateway.service.impl;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import java.lang.invoke.MethodHandles;
 
 import org.acumos.cds.AccessTypeCode;
@@ -37,11 +40,14 @@ import org.acumos.federation.gateway.config.NexusConfiguration;
 import org.acumos.federation.gateway.service.ContentService;
 import org.acumos.federation.gateway.service.ServiceContext;
 import org.acumos.federation.gateway.service.ServiceException;
-import org.acumos.nexus.client.NexusArtifactClient;
-import org.acumos.nexus.client.data.UploadArtifactInfo;
+
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.HttpStatusCodeException;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.io.InputStreamResource;
 import org.springframework.core.io.Resource;
+import org.springframework.core.io.InputStreamResource;
 import org.springframework.stereotype.Service;
 
 import com.github.dockerjava.api.DockerClient;
@@ -50,6 +56,7 @@ import com.github.dockerjava.api.model.Repository;
 import com.github.dockerjava.core.command.PullImageResultCallback;
 import com.github.dockerjava.core.command.PushImageResultCallback;
 
+import org.apache.commons.io.input.ProxyInputStream;
 
 /**
  * Nexus based implementation of the ContentService.
@@ -67,7 +74,7 @@ public class ContentServiceImpl extends AbstractServiceImpl
   private DockerConfiguration dockerConfig;
 
        @Override
-       public InputStreamResource getArtifactContent(
+       public Resource getArtifactContent(
                String theSolutionId, String theRevisionId, MLPArtifact theArtifact, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                                                        throws ServiceException {
                if (theArtifact.getUri() == null) {
@@ -84,9 +91,13 @@ public class ContentServiceImpl extends AbstractServiceImpl
                                        docker.pullImageCmd(theArtifact.getUri())
                                                                .exec(pullResult);
                                        pullResult.awaitCompletion();
+                                       log.debug(EELFLoggerDelegate.debugLogger, "Completed docker image pull for {}", theArtifact);
                                }
 
-                               return new InputStreamResource(docker.saveImageCmd(theArtifact.getUri()).exec());
+                               InputStream imageSource = docker.saveImageCmd(theArtifact.getUri()).exec();
+                               log.debug(EELFLoggerDelegate.debugLogger, "Completed docker image save for {}", theArtifact);
+
+                               return new InputStreamResource(imageSource);
                        }
                        catch (Exception x) {
                                log.error(EELFLoggerDelegate.errorLogger, "Failed to retrieve artifact content for docker artifact " + theArtifact, x);
@@ -105,11 +116,14 @@ public class ContentServiceImpl extends AbstractServiceImpl
 
                if (ArtifactType.DockerImage == ArtifactType.forCode(theArtifact.getArtifactTypeCode())) {
                        try {
+                               TrackingInputStream imageSource = new TrackingInputStream(theResource.getInputStream());
                                //load followed by push
                                DockerClient docker = this.dockerConfig.getDockerClient();
 
-                               docker.loadImageCmd(theResource.getInputStream())
+                               docker.loadImageCmd(imageSource)
                                                        .exec(); //sync xecution
+                               log.debug(EELFLoggerDelegate.debugLogger, "Completed docker image load for {}. Transfered {} bytes in {} seconds.",
+                                       theArtifact, imageSource.size(), imageSource.duration()/1000);
 
                                // there is an assumption here that the repo info was stripped from the artifact name by the originator
                                Identifier imageId =
@@ -120,9 +134,11 @@ public class ContentServiceImpl extends AbstractServiceImpl
                                        docker.pushImageCmd(imageId)
                                                                .exec(pushResult);
                                        pushResult.awaitCompletion();
+                                       log.debug(EELFLoggerDelegate.debugLogger, "Completed docker image push for {}", theArtifact);
                                }       
                                // update artifact with local repo reference. we also update the name and description in order to stay
                                // alligned with on-boarding's unwritten rules
+                               theArtifact.setSize((int)imageSource.size()); //?? is this correct
                                theArtifact.setUri(imageId.toString());
                                theArtifact.setName(imageId.toString());
                                theArtifact.setDescription(imageId.toString());
@@ -135,15 +151,15 @@ public class ContentServiceImpl extends AbstractServiceImpl
                }
                else {
                        String[] nameParts = splitName(theArtifact.getName());
-                       UploadArtifactInfo info = putNexusContent(
+                       String uri = putNexusContent(
                                nexusPrefix(theSolutionId, theRevisionId), nameParts[0], theArtifact.getVersion(), nameParts[1], theResource);
                        // update artifact with local repo reference
-                       theArtifact.setUri(info.getArtifactMvnPath());
+                       theArtifact.setUri(uri);
                }
        }
 
        @Override
-       public InputStreamResource getDocumentContent(
+       public Resource getDocumentContent(
                String theSolutionId, String theRevisionId, MLPDocument theDocument, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                throws ServiceException {
                if (theDocument.getUri() == null) {
@@ -158,37 +174,55 @@ public class ContentServiceImpl extends AbstractServiceImpl
                String theSolutionId, String theRevisionId, MLPDocument theDocument, Resource theResource, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                throws ServiceException {
                String[] nameParts = splitName(theDocument.getName());
-               UploadArtifactInfo info = putNexusContent(
+               String uri = putNexusContent(
                        nexusPrefix(theSolutionId, theRevisionId), nameParts[0], AccessTypeCode.PB.name(), nameParts[1], theResource);
-               theDocument.setUri(info.getArtifactMvnPath());
+               theDocument.setUri(uri);
        }
 
-       protected InputStreamResource getNexusContent(String theUri) throws ServiceException {
+       protected Resource getNexusContent(String theUri) throws ServiceException {
+               URI contentUri = null;
                try {
-                       NexusArtifactClient artifactClient = this.nexusConfig.getNexusClient();
-                       ByteArrayOutputStream artifactContent = artifactClient.getArtifact(theUri);
-                       log.info(EELFLoggerDelegate.debugLogger, "Retrieved {} bytes of content from {}", artifactContent.size(), theUri);
-                       return new InputStreamResource(
-                                                                               new ByteArrayInputStream(
-                                                                                       artifactContent.toByteArray()
-                                                                       ));
+                       contentUri = new URI(this.nexusConfig.getUrl() + theUri); 
+                       log.info(EELFLoggerDelegate.debugLogger, "Query for {}", contentUri);
+                       ResponseEntity<Resource> response = null;
+                       RequestEntity<Void> request = RequestEntity
+                                                                                                                                               .get(contentUri)
+                                                                                                                                               .accept(MediaType.ALL)
+                                                                                                                                               .build();
+                       response = this.nexusConfig.getNexusClient().exchange(request, Resource.class);
+                       return response.getBody();      
                }
-               catch (Exception x) {
-                       log.error(EELFLoggerDelegate.errorLogger, "Failed to retrieve content from  " + theUri, x);
-                       throw new ServiceException("Failed to retrieve content from " + theUri, x);
+               catch (HttpStatusCodeException x) {
+                       log.error(EELFLoggerDelegate.errorLogger, "Failed to retrieve nexus content from  " + theUri + "(" + contentUri + ")", x);
+                       throw new ServiceException("Failed to retrieve nexus content from " + contentUri, x);
+               }
+               catch (Throwable t) {
+                       log.error(EELFLoggerDelegate.errorLogger, "Unexpected failure for " + contentUri + "(" + contentUri + ")", t);
+                       throw new ServiceException("Unexpected failure for " + contentUri, t);
                }
+
        }
 
-       protected UploadArtifactInfo putNexusContent(
+       protected String putNexusContent(
                String theGroupId, String theContentId, String theVersion, String thePackaging, Resource theResource) throws ServiceException {
 
                try {
-                       UploadArtifactInfo info = this.nexusConfig.getNexusClient()
-                                                                                                                                       .uploadArtifact(theGroupId, theContentId, theVersion, thePackaging,
-                                                                                                                                                                                                       theResource.contentLength(), theResource.getInputStream());
-
-                       log.info(EELFLoggerDelegate.debugLogger, "Wrote artifact content to {}", info.getArtifactMvnPath());
-                       return info;
+                       String path = nexusPath(theGroupId, theContentId, theVersion, thePackaging);
+                       URI uri = new URI(this.nexusConfig.getUrl() + path);
+                       log.info(EELFLoggerDelegate.debugLogger, "Writing artifact content to nexus at {}", path);
+                       RequestEntity<Resource> request = RequestEntity
+                                                                                                                                                                       .put(uri)
+                                                                                                                                                                       .contentType(MediaType.APPLICATION_OCTET_STREAM)
+                                                                                                                                                                       //.contentLength()
+                                                                                                                                                                       .body(theResource);
+                       ResponseEntity<Void> response = this.nexusConfig.getNexusClient().exchange(request, Void.class);
+                       log.debug(EELFLoggerDelegate.debugLogger, "Writing artifact content to {} resulted in {}", path, response.getStatusCode());
+                       if (response.getStatusCode().is2xxSuccessful()) {
+                               log.info(EELFLoggerDelegate.debugLogger, "Wrote artifact content to {}", path);
+                               return path;
+                       }
+                       else
+                               throw new ServiceException("Failed to write artifact content to nexus. Got " + response.getStatusCode());
                }
                catch (Exception x) {
                        log.error(EELFLoggerDelegate.errorLogger,       "Failed to push content to Nexus repo", x);
@@ -196,10 +230,32 @@ public class ContentServiceImpl extends AbstractServiceImpl
                }
        }
 
+       /**
+        * This builds the prefix passed to nexusPath
+        */
        private String nexusPrefix(String theSolutionId, String theRevisionId) {
                return String.join(nexusConfig.getNameSeparator(), nexusConfig.getGroupId(), theSolutionId, theRevisionId);
        }
 
+       /**
+        * This mimics the procedure seen in the nexus client.
+        */
+       private String nexusPath(String thePrefix, String theContentId, String theVersion, String thePackaging) {
+               return new StringBuilder()
+                       .append(thePrefix.replace(".", "/"))
+                       .append("/")
+                       .append(theContentId)
+                       .append("/")
+                       .append(theVersion)
+                       .append("/")
+                       .append(theContentId)
+                       .append("-")
+                       .append(theVersion)
+                       .append(".")
+                       .append(thePackaging)
+                       .toString();
+       }
+
        /**
         * Split a file name into its core name and extension parts.
         * @param theName file name to split
@@ -212,4 +268,35 @@ public class ContentServiceImpl extends AbstractServiceImpl
                        pos == theName.length() - 1 ? new String[] {theName.substring(0,pos), ""} :
                                                                                                                                                new String[] {theName.substring(0,pos), theName.substring(pos+1)};
        }
+
+       private static class TrackingInputStream extends ProxyInputStream {
+               
+               private long size = 0;
+               private long duration = 0;
+
+               public TrackingInputStream(InputStream theSource) {
+                       super(theSource);
+                       duration = System.currentTimeMillis();
+               }
+
+               @Override
+               protected void beforeRead(int n) {
+               }
+
+               @Override
+               protected void afterRead(int n) {
+                       this.size += n;
+                       if (n == -1)
+                               this.duration = System.currentTimeMillis() - this.duration;
+               }
+
+               public long size() {
+                       return this.size;
+               }
+
+               public long duration() {
+                       return this.duration;
+               }
+       }
+
 }
index 1b02978..eaa0aa2 100644 (file)
@@ -50,7 +50,7 @@ public class ContentServiceLocalImpl extends AbstractServiceImpl
         * @throws ServiceException if failing to retrieve artifact information or retrieve content 
         */
        @Override
-       public InputStreamResource getArtifactContent(
+       public Resource getArtifactContent(
                String theSolutionId, String theRevisionId, MLPArtifact theArtifact, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                                                        throws ServiceException {
                if (theArtifact.getUri() == null) {
@@ -87,7 +87,7 @@ public class ContentServiceLocalImpl extends AbstractServiceImpl
        }       
 
        @Override
-       public InputStreamResource getDocumentContent(
+       public Resource getDocumentContent(
                String theSolutionId, String theRevisionId, MLPDocument theDocument, ServiceContext theContext)
                                                                                                                                                                                                                                                                                                                                                throws ServiceException {
                if (theDocument.getUri() == null) {
index 5f45313..f21dde2 100644 (file)
@@ -74,6 +74,9 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.RequestEntity;
+import org.springframework.web.client.RestTemplate;
 import org.springframework.web.client.HttpClientErrorException;
 
 
@@ -104,7 +107,7 @@ public class PeerGatewayTest {
        private ICommonDataServiceRestClient cdsClient;
 
        @Mock
-       private NexusArtifactClient nexusClient;
+       private RestTemplate nexusClient;
 
        @MockBean(name = "federationClient")
        private HttpClient      federationClient;
@@ -300,11 +303,11 @@ public class PeerGatewayTest {
                        .thenReturn(nexusClient);
 
                        when(
-                               this.nexusClient.uploadArtifact(
-                                       any(String.class),any(String.class),any(String.class),any(String.class),any(Long.class),any(InputStream.class)
+                               this.nexusClient.exchange(
+                                       any(RequestEntity.class),any(Class.class)
                                )
                        )
-                       .thenReturn(new UploadArtifactInfo("","","","","",0));
+                       .thenReturn(new ResponseEntity<byte[]>(new byte[] {}, HttpStatus.OK));
 
                        when(
                                this.cdsClient.searchPeers(