a994c847006758191eb51215526705b0a7695d09
[federation.git] / gateway / src / main / java / org / acumos / federation / gateway / controller / CatalogController.java
1 /*-
2  * ===============LICENSE_START=======================================================
3  * Acumos
4  * ===================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property & Tech Mahindra. All rights reserved.
6  * ===================================================================================
7  * This Acumos software file is distributed by AT&T and Tech Mahindra
8  * under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *  
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *  
14  * This file is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ===============LICENSE_END=========================================================
19  */
20
21 package org.acumos.federation.gateway.controller;
22
23 import java.net.URI;
24 import java.net.URISyntaxException;
25
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.Callable;
29
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.acumos.cds.domain.MLPArtifact;
34 import org.acumos.cds.domain.MLPSolution;
35 import org.acumos.cds.domain.MLPSolutionRevision;
36 import org.acumos.federation.gateway.cds.ArtifactType;
37 import org.acumos.federation.gateway.common.API;
38 import org.acumos.federation.gateway.common.JSONTags;
39 import org.acumos.federation.gateway.common.JsonResponse;
40 import org.acumos.federation.gateway.config.EELFLoggerDelegate;
41 import org.acumos.federation.gateway.security.Peer;
42 import org.acumos.federation.gateway.service.CatalogService;
43 import org.acumos.federation.gateway.service.ArtifactService;
44 import org.acumos.federation.gateway.service.ServiceContext;
45 import org.acumos.federation.gateway.util.Utils;
46
47 import org.springframework.beans.factory.annotation.Autowired;
48 import org.springframework.core.io.InputStreamResource;
49 import org.springframework.http.MediaType;
50 import org.springframework.security.access.prepost.PreAuthorize;
51 import org.springframework.security.core.context.SecurityContextHolder;
52 import org.springframework.stereotype.Controller;
53 import org.springframework.util.Base64Utils;
54 import org.springframework.web.bind.annotation.CrossOrigin;
55 import org.springframework.web.bind.annotation.PathVariable;
56 import org.springframework.web.bind.annotation.RequestMapping;
57 import org.springframework.web.bind.annotation.RequestMethod;
58 import org.springframework.web.bind.annotation.RequestParam;
59 import org.springframework.web.bind.annotation.ResponseBody;
60
61 import io.swagger.annotations.ApiOperation;
62
63 import com.github.dockerjava.api.model.Identifier;
64
65 /**
66  * 
67  *
68  */
69 @Controller
70 @RequestMapping(API.Roots.FEDERATION)
71 public class CatalogController extends AbstractController {
72
73         private static final EELFLoggerDelegate log = EELFLoggerDelegate.getLogger(CatalogController.class.getName());
74
75         @Autowired
76         private CatalogService catalogService;
77         @Autowired
78         private ArtifactService artifactService;
79
80         /**
81          * @param theHttpResponse
82          *            HttpServletResponse
83          * @param theSelector
84          *            Solutions selector
85          * @return List of Published ML Solutions in JSON format.
86          */
87         @CrossOrigin
88         // @PreAuthorize("hasAuthority('PEER')"
89         @PreAuthorize("hasAuthority(T(org.acumos.federation.gateway.security.Priviledge).CATALOG_ACCESS)")
90         @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")
91         @RequestMapping(value = { API.Paths.SOLUTIONS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
92         @ResponseBody
93         public JsonResponse<List<MLPSolution>> getSolutions(
94                         HttpServletRequest theHttpRequest,
95                         HttpServletResponse theHttpResponse,
96                         @RequestParam(value = API.QueryParameters.SOLUTIONS_SELECTOR, required = false) String theSelector) {
97                 JsonResponse<List<MLPSolution>> response = null;
98                 List<MLPSolution> solutions = null;
99                 log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTIONS);
100                 try {
101                         log.debug(EELFLoggerDelegate.debugLogger, "getSolutionsListFromPeer: selector " + theSelector);
102                         Map<String, ?> selector = null;
103                         if (theSelector != null)
104                                 selector = Utils.jsonStringToMap(new String(Base64Utils.decodeFromString(theSelector), "UTF-8"));
105
106                         solutions = catalogService.getSolutions(selector, new ControllerContext());
107                         if (solutions != null) {
108                                 for (MLPSolution solution: solutions) {
109                                         encodeSolution(solution, theHttpRequest);
110                                 }
111                         }
112
113                         response = JsonResponse.<List<MLPSolution>> buildResponse()
114                                                                                                                 .withMessage("available public solution for given filter")
115                                                                                                                 .withContent(solutions)
116                                                                                                                 .build();
117                         theHttpResponse.setStatus(HttpServletResponse.SC_OK);
118                         log.debug(EELFLoggerDelegate.debugLogger, "getSolutions: provided {} solutions", solutions == null ? 0 : solutions.size());
119                 }
120                 catch (Exception x) {
121                         response = JsonResponse.<List<MLPSolution>> buildErrorResponse()
122                                                                                                                  .withError(x)
123                                                                                                                  .build();
124                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
125                         log.error(EELFLoggerDelegate.errorLogger, "Exception occurred while fetching solutions", x);
126                 }
127                 return response;
128         }
129
130         @CrossOrigin
131         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
132         @ApiOperation(value = "Invoked by Peer Acumos to get a list detailed solution information from the Catalog of the local Acumos Instance .", response = MLPSolution.class)
133         @RequestMapping(value = { API.Paths.SOLUTION_DETAILS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
134         @ResponseBody
135         public JsonResponse<MLPSolution> getSolutionDetails(
136                         HttpServletRequest theHttpRequest,
137                         HttpServletResponse theHttpResponse,
138                         @PathVariable(value = "solutionId") String theSolutionId) {
139                 JsonResponse<MLPSolution> response = null;
140                 MLPSolution solution = null;
141                 log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_DETAILS + ": " + theSolutionId);
142                 try {
143                         solution = catalogService.getSolution(theSolutionId, new ControllerContext());
144                         encodeSolution(solution, theHttpRequest);
145                         response = JsonResponse.<MLPSolution> buildResponse()
146                                                                                                                 .withMessage("solution details")
147                                                                                                                 .withContent(solution)
148                                                                                                                 .build();
149                         theHttpResponse.setStatus(HttpServletResponse.SC_OK);
150                 }
151                 catch (Exception x) {
152                         response = JsonResponse.<MLPSolution> buildErrorResponse()
153                                                                                                                  .withError(x)
154                                                                                                                  .build();
155                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
156                         log.error(EELFLoggerDelegate.errorLogger, "An error occurred while fetching solution " + theSolutionId, x);
157                 }
158                 return response;
159         }
160
161         /**
162          * @param theSolutionId
163          *            Solution ID
164          * @param theHttpResponse
165          *            HttpServletResponse
166          * @return List of Published ML Solutions in JSON format.
167          */
168         @CrossOrigin
169         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
170         @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")
171         @RequestMapping(value = { API.Paths.SOLUTION_REVISIONS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
172         @ResponseBody
173         public JsonResponse<List<MLPSolutionRevision>> getSolutionRevisions(HttpServletResponse theHttpResponse,
174                         @PathVariable("solutionId") String theSolutionId) {
175                 JsonResponse<List<MLPSolutionRevision>> response = null;
176                 List<MLPSolutionRevision> solutionRevisions = null;
177                 log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_REVISIONS);
178                 try {
179                         solutionRevisions = catalogService.getSolutionRevisions(theSolutionId, new ControllerContext());
180                         response = JsonResponse.<List<MLPSolutionRevision>> buildResponse()
181                                                                                                                 .withMessage("solution revisions")
182                                                                                                                 .withContent(solutionRevisions)
183                                                                                                                 .build();
184                         theHttpResponse.setStatus(HttpServletResponse.SC_OK);
185                         log.debug(EELFLoggerDelegate.debugLogger, "getSolutionsRevisions for solution {} provided {} revisions",
186                                                 theSolutionId, solutionRevisions == null ? 0 : solutionRevisions.size());
187                 }
188                 catch (Exception x) {
189                         response = JsonResponse.<List<MLPSolutionRevision>> buildErrorResponse()
190                                                                                                                  .withError(x)
191                                                                                                                  .build();
192                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
193                         log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revisions", x);
194                 }
195                 return response;
196         }
197
198         /**
199          * 
200          * @param theSolutionId
201          *            Solution ID
202          * @param theRevisionId
203          *            Revision ID
204          * @param theHttpResponse
205          *            HttpServletResponse
206          * @return List of Published ML Solutions in JSON format.
207          */
208         @CrossOrigin
209         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
210         @ApiOperation(value = "Invoked by peer Acumos to get solution revision details from the local Acumos Instance .", response = MLPSolutionRevision.class)
211         @RequestMapping(value = {
212                         API.Paths.SOLUTION_REVISION_DETAILS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
213         @ResponseBody
214         public JsonResponse<MLPSolutionRevision> getSolutionRevisionDetails(HttpServletResponse theHttpResponse,
215                         @PathVariable("solutionId") String theSolutionId, @PathVariable("revisionId") String theRevisionId) {
216                 JsonResponse<MLPSolutionRevision> response = null;
217                 MLPSolutionRevision solutionRevision = null;
218                 log.debug(EELFLoggerDelegate.debugLogger,
219                                 API.Paths.SOLUTION_REVISION_DETAILS + "(" + theSolutionId + "," + theRevisionId + ")");
220                 try {
221                         solutionRevision = catalogService.getSolutionRevision(theSolutionId, theRevisionId,
222                                         new ControllerContext());
223                         response = JsonResponse.<MLPSolutionRevision> buildResponse()
224                                                                                                                 .withMessage("solution revision details")
225                                                                                                                 .withContent(solutionRevision)
226                                                                                                                 .build();
227                         theHttpResponse.setStatus(HttpServletResponse.SC_OK);
228                 }
229                 catch (Exception x) {
230                         response = JsonResponse.<MLPSolutionRevision> buildErrorResponse()
231                                                                                                                  .withError(x)
232                                                                                                                  .build();
233                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
234                         log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revision " + theRevisionId + " details", x);
235                 }
236                 return response;
237         }
238
239         /**
240          * @param theHttpRequest
241          *            HttpServletRequest
242          * @param theHttpResponse
243          *            HttpServletResponse
244          * @param theSolutionId
245          *            Solution ID
246          * @param theRevisionId
247          *            Revision ID
248          * @return List of Published ML Solutions in JSON format.
249          */
250         @CrossOrigin
251         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
252         @ApiOperation(value = "Invoked by Peer Acumos to get a list of solution revision artifacts from the local Acumos Instance .", response = MLPArtifact.class, responseContainer = "List")
253         @RequestMapping(value = {
254                         API.Paths.SOLUTION_REVISION_ARTIFACTS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
255         @ResponseBody
256         public JsonResponse<List<MLPArtifact>> getSolutionRevisionArtifacts(HttpServletRequest theHttpRequest,
257                         HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId,
258                         @PathVariable("revisionId") String theRevisionId) {
259                 JsonResponse<List<MLPArtifact>> response = null;
260                 List<MLPArtifact> solutionRevisionArtifacts = null;
261                 ControllerContext context = new ControllerContext();
262                 log.debug(EELFLoggerDelegate.debugLogger,
263                                 API.Paths.SOLUTION_REVISION_ARTIFACTS + "(" + theSolutionId + "," + theRevisionId + ")");
264                 try {
265                         solutionRevisionArtifacts = catalogService.getSolutionRevisionArtifacts(theSolutionId, theRevisionId, context);
266                         for (MLPArtifact artifact : solutionRevisionArtifacts) {
267                                 if (!context.getPeer().getPeerInfo().isLocal()) {
268                                         encodeArtifact(artifact, theHttpRequest);
269                                 }
270                         }
271                         response = JsonResponse.<List<MLPArtifact>> buildResponse()
272                                                                                                         .withMessage("solution revision artifacts")
273                                                                                                         .withContent(solutionRevisionArtifacts)
274                                                                                                         .build();
275                         theHttpResponse.setStatus(HttpServletResponse.SC_OK);
276                         log.debug(EELFLoggerDelegate.debugLogger, "getSolutionRevisionArtifacts provided {} artifacts",
277                                                 solutionRevisionArtifacts == null ? 0 : solutionRevisionArtifacts.size());
278                 } 
279                 catch (Exception x) {
280                         response = JsonResponse.<List<MLPArtifact>> buildErrorResponse()
281                                                                                                                  .withError(x)
282                                                                                                                  .build();
283                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
284                         log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revision " + theRevisionId + " artifacts", x);
285                 }
286                 return response;
287         }
288
289         /**
290          * @param theHttpRequest
291          *            HttpServletRequest
292          * @param theHttpResponse
293          *            HttpServletResponse
294          * @param theArtifactId
295          *            Artifact ID
296          * @return Archive file of the Artifact for the Solution.
297          */
298         @CrossOrigin
299         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
300         @ApiOperation(value = "API to download the Machine Learning Artifact of the Machine Learning Solution", response = InputStreamResource.class, code = 200)
301         @RequestMapping(value = {
302                         API.Paths.ARTIFACT_DOWNLOAD }, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
303         @ResponseBody
304         public Callable<InputStreamResource> downloadSolutionArtifact(HttpServletRequest theHttpRequest,
305                         HttpServletResponse theHttpResponse, @PathVariable("artifactId") String theArtifactId) {
306                         
307                 theHttpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
308                 theHttpResponse.setHeader("Pragma", "no-cache");
309                 theHttpResponse.setHeader("Expires", "0");
310                 theHttpResponse.setStatus(HttpServletResponse.SC_OK);
311
312                 final ControllerContext ctx = new ControllerContext();
313                 return new Callable<InputStreamResource>() {
314                         public InputStreamResource call() throws Exception {
315                                 try {   
316                                         return artifactService.getArtifactContent(
317                                                                         catalogService.getSolutionRevisionArtifact(theArtifactId, ctx), ctx);
318                                 } 
319                                 catch (Exception x) {
320                                         log.error(EELFLoggerDelegate.errorLogger,
321                                                 "An error occurred while retrieving artifact content " + theArtifactId, x);
322                                         throw x;
323                                 }
324                         }
325                 };
326         }
327
328         /** */
329         private void encodeSolution(MLPSolution theSolution, HttpServletRequest theRequest) throws URISyntaxException {
330                 //encode the 'origin'
331                 if (null == theSolution.getOrigin()) {
332                         URI requestUri = new URI(theRequest.getRequestURL().toString());
333                         URI solutionUri = API.SOLUTION_DETAIL
334                                                                                                 .buildUri(
335                                                                                                         new URI(requestUri.getScheme(), null, requestUri.getHost(),
336                                                                                                                                         requestUri.getPort(), null, null, null).toString(),
337                                                                                                         theSolution.getSolutionId());
338                         theSolution.setOrigin(solutionUri.toString());  
339                 }
340         }
341         
342         /** */
343         private void encodeArtifact(MLPArtifact theArtifact, HttpServletRequest theRequest) throws URISyntaxException {
344
345                 String artifactUri = theArtifact.getUri();
346
347                 //redirect              
348                 {
349                         URI requestUri = new URI(theRequest.getRequestURL().toString());
350                         URI redirectUri = API.ARTIFACT_DOWNLOAD
351                                                                                                 .buildUri(
352                                                                                                         new URI(requestUri.getScheme(), null, requestUri.getHost(),
353                                                                                                                                         requestUri.getPort(), null, null, null).toString(),
354                                                                                                         theArtifact.getArtifactId());
355                         log.debug(EELFLoggerDelegate.debugLogger,       "getSolutionRevisionArtifacts: redirected artifact uri " + redirectUri);
356                         theArtifact.setUri(redirectUri.toString());
357                 }
358                 
359                 if (ArtifactType.DockerImage == ArtifactType.forCode(theArtifact.getArtifactTypeCode())) {
360                         if (artifactUri != null) {
361                                 Identifier imageId = Identifier.fromCompoundString(artifactUri);
362                         
363                                 String imageTag = imageId.tag.get(); //there should always be a tag, right??
364                                 log.debug(EELFLoggerDelegate.debugLogger,       "getSolutionRevisionArtifacts: encoded docker image uri to tag " + imageTag);
365                                 theArtifact.setName(imageTag);
366                                 theArtifact.setDescription(imageTag);
367                         }
368                 }
369         }
370         
371 }