/*- * ===============LICENSE_START======================================================= * Acumos * =================================================================================== * Copyright (C) 2017 AT&T Intellectual Property & Tech Mahindra. All rights reserved. * =================================================================================== * This Acumos software file is distributed by AT&T and Tech Mahindra * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * This file 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. * ===============LICENSE_END========================================================= */ package org.acumos.federation.gateway.controller; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.acumos.cds.domain.MLPArtifact; import org.acumos.cds.domain.MLPSolution; import org.acumos.cds.domain.MLPSolutionRevision; import org.acumos.federation.gateway.cds.ArtifactType; import org.acumos.federation.gateway.common.API; import org.acumos.federation.gateway.common.JSONTags; import org.acumos.federation.gateway.common.JsonResponse; import org.acumos.federation.gateway.config.EELFLoggerDelegate; import org.acumos.federation.gateway.security.Peer; import org.acumos.federation.gateway.service.CatalogService; import org.acumos.federation.gateway.service.ArtifactService; import org.acumos.federation.gateway.service.ServiceContext; import org.acumos.federation.gateway.util.Utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.util.Base64Utils; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import io.swagger.annotations.ApiOperation; import com.github.dockerjava.api.model.Identifier; /** * * */ @Controller @RequestMapping(API.Roots.FEDERATION) public class CatalogController extends AbstractController { private static final EELFLoggerDelegate log = EELFLoggerDelegate.getLogger(CatalogController.class.getName()); @Autowired private CatalogService catalogService; @Autowired private ArtifactService artifactService; /** * @param theHttpResponse * HttpServletResponse * @param theSelector * Solutions selector * @return List of Published ML Solutions in JSON format. */ @CrossOrigin // @PreAuthorize("hasAuthority('PEER')" @PreAuthorize("hasAuthority(T(org.acumos.federation.gateway.security.Priviledge).CATALOG_ACCESS)") @ApiOperation(value = "Invoked by Peer Acumos to get a list of Published Solutions from the Catalog of the local Acumos Instance .", response = MLPSolution.class, responseContainer = "List") @RequestMapping(value = { API.Paths.SOLUTIONS }, method = RequestMethod.GET, produces = APPLICATION_JSON) @ResponseBody public JsonResponse> getSolutions( HttpServletRequest theHttpRequest, HttpServletResponse theHttpResponse, @RequestParam(value = API.QueryParameters.SOLUTIONS_SELECTOR, required = false) String theSelector) { JsonResponse> response = null; List solutions = null; log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTIONS); try { log.debug(EELFLoggerDelegate.debugLogger, "getSolutions: selector " + theSelector); Map selector = null; if (theSelector != null) selector = Utils.jsonStringToMap(new String(Base64Utils.decodeFromString(theSelector), "UTF-8")); solutions = catalogService.getSolutions(selector, new ControllerContext()); if (solutions != null) { for (MLPSolution solution: solutions) { encodeSolution(solution, theHttpRequest); } } response = JsonResponse.> buildResponse() .withMessage("available public solution for given filter") .withContent(solutions) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_OK); log.debug(EELFLoggerDelegate.debugLogger, "getSolutions: provided {} solutions", solutions == null ? 0 : solutions.size()); } catch (Exception x) { response = JsonResponse.> buildErrorResponse() .withError(x) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error(EELFLoggerDelegate.errorLogger, "Exception occurred while fetching solutions", x); } return response; } @CrossOrigin @PreAuthorize("hasAuthority('CATALOG_ACCESS')") @ApiOperation(value = "Invoked by Peer Acumos to get a list detailed solution information from the Catalog of the local Acumos Instance .", response = MLPSolution.class) @RequestMapping(value = { API.Paths.SOLUTION_DETAILS }, method = RequestMethod.GET, produces = APPLICATION_JSON) @ResponseBody public JsonResponse getSolutionDetails( HttpServletRequest theHttpRequest, HttpServletResponse theHttpResponse, @PathVariable(value = "solutionId") String theSolutionId) { JsonResponse response = null; MLPSolution solution = null; log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_DETAILS + ": " + theSolutionId); try { solution = catalogService.getSolution(theSolutionId, new ControllerContext()); if (null == solution) { response = JsonResponse. buildResponse() .withMessage("No solution with id " + theSolutionId + " is available.") .build(); theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } else { encodeSolution(solution, theHttpRequest); response = JsonResponse. buildResponse() .withMessage("solution details") .withContent(solution) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_OK); } } catch (Exception x) { response = JsonResponse. buildErrorResponse() .withError(x) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error(EELFLoggerDelegate.errorLogger, "An error occurred while fetching solution " + theSolutionId, x); } return response; } /** * @param theSolutionId * Solution ID * @param theHttpResponse * HttpServletResponse * @return List of Published ML Solutions in JSON format. */ @CrossOrigin @PreAuthorize("hasAuthority('CATALOG_ACCESS')") @ApiOperation(value = "Invoked by Peer Acumos to get a list of Solution Revision from the Catalog of the local Acumos Instance .", response = MLPSolutionRevision.class, responseContainer = "List") @RequestMapping(value = { API.Paths.SOLUTION_REVISIONS }, method = RequestMethod.GET, produces = APPLICATION_JSON) @ResponseBody public JsonResponse> getSolutionRevisions(HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId) { JsonResponse> response = null; List solutionRevisions = null; log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_REVISIONS); try { solutionRevisions = catalogService.getSolutionRevisions(theSolutionId, new ControllerContext()); if (null == solutionRevisions) { response = JsonResponse.> buildResponse() .withMessage("No solution with id " + theSolutionId + " is available.") .build(); theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } else { response = JsonResponse.> buildResponse() .withMessage("solution revisions") .withContent(solutionRevisions) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_OK); log.debug(EELFLoggerDelegate.debugLogger, "getSolutionsRevisions for solution {} provided {} revisions", theSolutionId, solutionRevisions == null ? 0 : solutionRevisions.size()); } } catch (Exception x) { response = JsonResponse.> buildErrorResponse() .withError(x) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revisions", x); } return response; } /** * * @param theSolutionId * Solution ID * @param theRevisionId * Revision ID * @param theHttpResponse * HttpServletResponse * @return List of Published ML Solutions in JSON format. */ @CrossOrigin @PreAuthorize("hasAuthority('CATALOG_ACCESS')") @ApiOperation(value = "Invoked by peer Acumos to get solution revision details from the local Acumos Instance .", response = MLPSolutionRevision.class) @RequestMapping(value = { API.Paths.SOLUTION_REVISION_DETAILS }, method = RequestMethod.GET, produces = APPLICATION_JSON) @ResponseBody public JsonResponse getSolutionRevisionDetails(HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId, @PathVariable("revisionId") String theRevisionId) { JsonResponse response = null; MLPSolutionRevision solutionRevision = null; log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_REVISION_DETAILS + "(" + theSolutionId + "," + theRevisionId + ")"); try { solutionRevision = catalogService.getSolutionRevision(theSolutionId, theRevisionId, new ControllerContext()); if (null == solutionRevision) { response = JsonResponse. buildResponse() .withMessage("No solution revision " + theSolutionId + "/" + theRevisionId + " is available.") .build(); theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } else { response = JsonResponse. buildResponse() .withMessage("solution revision details") .withContent(solutionRevision) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_OK); } } catch (Exception x) { response = JsonResponse. buildErrorResponse() .withError(x) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revision " + theRevisionId + " details", x); } return response; } /** * @param theHttpRequest * HttpServletRequest * @param theHttpResponse * HttpServletResponse * @param theSolutionId * Solution ID * @param theRevisionId * Revision ID * @return List of Published ML Solutions in JSON format. */ @CrossOrigin @PreAuthorize("hasAuthority('CATALOG_ACCESS')") @ApiOperation(value = "Invoked by Peer Acumos to get a list of solution revision artifacts from the local Acumos Instance .", response = MLPArtifact.class, responseContainer = "List") @RequestMapping(value = { API.Paths.SOLUTION_REVISION_ARTIFACTS }, method = RequestMethod.GET, produces = APPLICATION_JSON) @ResponseBody public JsonResponse> getSolutionRevisionArtifacts(HttpServletRequest theHttpRequest, HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId, @PathVariable("revisionId") String theRevisionId) { JsonResponse> response = null; List solutionRevisionArtifacts = null; ControllerContext context = new ControllerContext(); log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_REVISION_ARTIFACTS + "(" + theSolutionId + "," + theRevisionId + ")"); try { solutionRevisionArtifacts = catalogService.getSolutionRevisionArtifacts(theSolutionId, theRevisionId, context); if (null == solutionRevisionArtifacts) { response = JsonResponse.> buildResponse() .withMessage("No solution revision " + theSolutionId + "/" + theRevisionId + " is available.") .build(); theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } else { for (MLPArtifact artifact : solutionRevisionArtifacts) { if (!context.getPeer().getPeerInfo().isLocal()) { encodeArtifact(artifact, theHttpRequest); } } response = JsonResponse.> buildResponse() .withMessage("solution revision artifacts") .withContent(solutionRevisionArtifacts) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_OK); log.debug(EELFLoggerDelegate.debugLogger, "getSolutionRevisionArtifacts provided {} artifacts", solutionRevisionArtifacts == null ? 0 : solutionRevisionArtifacts.size()); } } catch (Exception x) { response = JsonResponse.> buildErrorResponse() .withError(x) .build(); theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revision " + theRevisionId + " artifacts", x); } return response; } /** * @param theHttpRequest * HttpServletRequest * @param theHttpResponse * HttpServletResponse * @param theArtifactId * Artifact ID * @return Archive file of the Artifact for the Solution. */ @CrossOrigin @PreAuthorize("hasAuthority('CATALOG_ACCESS')") @ApiOperation(value = "API to download the Machine Learning Artifact of the Machine Learning Solution", response = InputStreamResource.class, code = 200) @RequestMapping(value = { API.Paths.ARTIFACT_DOWNLOAD }, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) @ResponseBody public Callable downloadSolutionArtifact(HttpServletRequest theHttpRequest, HttpServletResponse theHttpResponse, @PathVariable("artifactId") String theArtifactId) { theHttpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); theHttpResponse.setHeader("Pragma", "no-cache"); theHttpResponse.setHeader("Expires", "0"); theHttpResponse.setStatus(HttpServletResponse.SC_OK); final ControllerContext ctx = new ControllerContext(); return new Callable() { public InputStreamResource call() throws Exception { try { return artifactService.getArtifactContent( catalogService.getSolutionRevisionArtifact(theArtifactId, ctx), ctx); } catch (Exception x) { log.error(EELFLoggerDelegate.errorLogger, "An error occurred while retrieving artifact content " + theArtifactId, x); throw x; } } }; } /** */ private void encodeSolution(MLPSolution theSolution, HttpServletRequest theRequest) throws URISyntaxException { //encode the 'origin' if (null == theSolution.getOrigin()) { URI requestUri = new URI(theRequest.getRequestURL().toString()); URI solutionUri = API.SOLUTION_DETAIL .buildUri( new URI(requestUri.getScheme(), null, requestUri.getHost(), requestUri.getPort(), null, null, null).toString(), theSolution.getSolutionId()); theSolution.setOrigin(solutionUri.toString()); } } /** */ private void encodeArtifact(MLPArtifact theArtifact, HttpServletRequest theRequest) throws URISyntaxException { String artifactUri = theArtifact.getUri(); //redirect { URI requestUri = new URI(theRequest.getRequestURL().toString()); URI redirectUri = API.ARTIFACT_DOWNLOAD .buildUri( new URI(requestUri.getScheme(), null, requestUri.getHost(), requestUri.getPort(), null, null, null).toString(), theArtifact.getArtifactId()); log.debug(EELFLoggerDelegate.debugLogger, "getSolutionRevisionArtifacts: redirected artifact uri " + redirectUri); theArtifact.setUri(redirectUri.toString()); } if (ArtifactType.DockerImage == ArtifactType.forCode(theArtifact.getArtifactTypeCode())) { if (artifactUri != null) { Identifier imageId = Identifier.fromCompoundString(artifactUri); String imageTag = imageId.tag.get(); //there should always be a tag, right?? log.debug(EELFLoggerDelegate.debugLogger, "getSolutionRevisionArtifacts: encoded docker image uri to tag " + imageTag); theArtifact.setName(imageTag); theArtifact.setDescription(imageTag); } } } }