5e013b50ebb5af9f9f6da4bc4f0e940d7df54cff
[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, "getSolutions: 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                         if (null == solution) {
145                                 response = JsonResponse.<MLPSolution> buildResponse()
146                                                                                                                         .withMessage("No solution with id " + theSolutionId + " is available.")
147                                                                                                                         .build();
148                                 theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
149                         }
150                         else {
151                                 encodeSolution(solution, theHttpRequest);
152                                 response = JsonResponse.<MLPSolution> buildResponse()
153                                                                                                                         .withMessage("solution details")
154                                                                                                                         .withContent(solution)
155                                                                                                                         .build();
156                                 theHttpResponse.setStatus(HttpServletResponse.SC_OK);
157                         }
158                 }
159                 catch (Exception x) {
160                         response = JsonResponse.<MLPSolution> buildErrorResponse()
161                                                                                                                  .withError(x)
162                                                                                                                  .build();
163                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
164                         log.error(EELFLoggerDelegate.errorLogger, "An error occurred while fetching solution " + theSolutionId, x);
165                 }
166                 return response;
167         }
168
169         /**
170          * @param theSolutionId
171          *            Solution ID
172          * @param theHttpResponse
173          *            HttpServletResponse
174          * @return List of Published ML Solutions in JSON format.
175          */
176         @CrossOrigin
177         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
178         @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")
179         @RequestMapping(value = { API.Paths.SOLUTION_REVISIONS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
180         @ResponseBody
181         public JsonResponse<List<MLPSolutionRevision>> getSolutionRevisions(HttpServletResponse theHttpResponse,
182                         @PathVariable("solutionId") String theSolutionId) {
183                 JsonResponse<List<MLPSolutionRevision>> response = null;
184                 List<MLPSolutionRevision> solutionRevisions = null;
185                 log.debug(EELFLoggerDelegate.debugLogger, API.Paths.SOLUTION_REVISIONS);
186                 try {
187                         solutionRevisions = catalogService.getSolutionRevisions(theSolutionId, new ControllerContext());
188                         if (null == solutionRevisions) {
189                                 response = JsonResponse.<List<MLPSolutionRevision>> buildResponse()
190                                                                                                                         .withMessage("No solution with id " + theSolutionId + " is available.")
191                                                                                                                         .build();
192                                 theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
193                         }
194                         else {
195                                 response = JsonResponse.<List<MLPSolutionRevision>> buildResponse()
196                                                                                                                         .withMessage("solution revisions")
197                                                                                                                         .withContent(solutionRevisions)
198                                                                                                                         .build();
199                                 theHttpResponse.setStatus(HttpServletResponse.SC_OK);
200                                 log.debug(EELFLoggerDelegate.debugLogger, "getSolutionsRevisions for solution {} provided {} revisions",
201                                                 theSolutionId, solutionRevisions == null ? 0 : solutionRevisions.size());
202                         }
203                 }
204                 catch (Exception x) {
205                         response = JsonResponse.<List<MLPSolutionRevision>> buildErrorResponse()
206                                                                                                                  .withError(x)
207                                                                                                                  .build();
208                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
209                         log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revisions", x);
210                 }
211                 return response;
212         }
213
214         /**
215          * 
216          * @param theSolutionId
217          *            Solution ID
218          * @param theRevisionId
219          *            Revision ID
220          * @param theHttpResponse
221          *            HttpServletResponse
222          * @return List of Published ML Solutions in JSON format.
223          */
224         @CrossOrigin
225         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
226         @ApiOperation(value = "Invoked by peer Acumos to get solution revision details from the local Acumos Instance .", response = MLPSolutionRevision.class)
227         @RequestMapping(value = {
228                         API.Paths.SOLUTION_REVISION_DETAILS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
229         @ResponseBody
230         public JsonResponse<MLPSolutionRevision> getSolutionRevisionDetails(HttpServletResponse theHttpResponse,
231                         @PathVariable("solutionId") String theSolutionId, @PathVariable("revisionId") String theRevisionId) {
232                 JsonResponse<MLPSolutionRevision> response = null;
233                 MLPSolutionRevision solutionRevision = null;
234                 log.debug(EELFLoggerDelegate.debugLogger,
235                                 API.Paths.SOLUTION_REVISION_DETAILS + "(" + theSolutionId + "," + theRevisionId + ")");
236                 try {
237                         solutionRevision = catalogService.getSolutionRevision(theSolutionId, theRevisionId,
238                                         new ControllerContext());
239                         if (null == solutionRevision) {
240                                 response = JsonResponse.<MLPSolutionRevision> buildResponse()
241                                                                                                                                 .withMessage("No solution revision " + theSolutionId + "/" + theRevisionId + " is available.")
242                                                                                                                                 .build();
243                                 theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
244                         }
245                         else {
246                                 response = JsonResponse.<MLPSolutionRevision> buildResponse()
247                                                                                                                                 .withMessage("solution revision details")
248                                                                                                                                 .withContent(solutionRevision)
249                                                                                                                                 .build();
250                                 theHttpResponse.setStatus(HttpServletResponse.SC_OK);
251                         }
252                 }
253                 catch (Exception x) {
254                         response = JsonResponse.<MLPSolutionRevision> buildErrorResponse()
255                                                                                                                  .withError(x)
256                                                                                                                  .build();
257                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
258                         log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revision " + theRevisionId + " details", x);
259                 }
260                 return response;
261         }
262
263         /**
264          * @param theHttpRequest
265          *            HttpServletRequest
266          * @param theHttpResponse
267          *            HttpServletResponse
268          * @param theSolutionId
269          *            Solution ID
270          * @param theRevisionId
271          *            Revision ID
272          * @return List of Published ML Solutions in JSON format.
273          */
274         @CrossOrigin
275         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
276         @ApiOperation(value = "Invoked by Peer Acumos to get a list of solution revision artifacts from the local Acumos Instance .", response = MLPArtifact.class, responseContainer = "List")
277         @RequestMapping(value = {
278                         API.Paths.SOLUTION_REVISION_ARTIFACTS }, method = RequestMethod.GET, produces = APPLICATION_JSON)
279         @ResponseBody
280         public JsonResponse<List<MLPArtifact>> getSolutionRevisionArtifacts(HttpServletRequest theHttpRequest,
281                         HttpServletResponse theHttpResponse, @PathVariable("solutionId") String theSolutionId,
282                         @PathVariable("revisionId") String theRevisionId) {
283                 JsonResponse<List<MLPArtifact>> response = null;
284                 List<MLPArtifact> solutionRevisionArtifacts = null;
285                 ControllerContext context = new ControllerContext();
286                 log.debug(EELFLoggerDelegate.debugLogger,
287                                 API.Paths.SOLUTION_REVISION_ARTIFACTS + "(" + theSolutionId + "," + theRevisionId + ")");
288                 try {
289                         solutionRevisionArtifacts = catalogService.getSolutionRevisionArtifacts(theSolutionId, theRevisionId, context);
290                         if (null == solutionRevisionArtifacts) {
291                                 response = JsonResponse.<List<MLPArtifact>> buildResponse()
292                                                                                                                                 .withMessage("No solution revision " + theSolutionId + "/" + theRevisionId + " is available.")
293                                                                                                                                 .build();
294                                 theHttpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
295                         }
296                         else {
297                                 for (MLPArtifact artifact : solutionRevisionArtifacts) {
298                                         if (!context.getPeer().getPeerInfo().isLocal()) {
299                                                 encodeArtifact(artifact, theHttpRequest);
300                                         }
301                                 }
302                                 response = JsonResponse.<List<MLPArtifact>> buildResponse()
303                                                                                                                 .withMessage("solution revision artifacts")
304                                                                                                                 .withContent(solutionRevisionArtifacts)
305                                                                                                                 .build();
306                                 theHttpResponse.setStatus(HttpServletResponse.SC_OK);
307                                 log.debug(EELFLoggerDelegate.debugLogger, "getSolutionRevisionArtifacts provided {} artifacts",
308                                                         solutionRevisionArtifacts == null ? 0 : solutionRevisionArtifacts.size());
309                         }
310                 } 
311                 catch (Exception x) {
312                         response = JsonResponse.<List<MLPArtifact>> buildErrorResponse()
313                                                                                                                  .withError(x)
314                                                                                                                  .build();
315                         theHttpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
316                         log.error(EELFLoggerDelegate.errorLogger, "An error occured while fetching solution " + theSolutionId + " revision " + theRevisionId + " artifacts", x);
317                 }
318                 return response;
319         }
320
321         /**
322          * @param theHttpRequest
323          *            HttpServletRequest
324          * @param theHttpResponse
325          *            HttpServletResponse
326          * @param theArtifactId
327          *            Artifact ID
328          * @return Archive file of the Artifact for the Solution.
329          */
330         @CrossOrigin
331         @PreAuthorize("hasAuthority('CATALOG_ACCESS')")
332         @ApiOperation(value = "API to download the Machine Learning Artifact of the Machine Learning Solution", response = InputStreamResource.class, code = 200)
333         @RequestMapping(value = {
334                         API.Paths.ARTIFACT_DOWNLOAD }, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
335         @ResponseBody
336         public Callable<InputStreamResource> downloadSolutionArtifact(HttpServletRequest theHttpRequest,
337                         HttpServletResponse theHttpResponse, @PathVariable("artifactId") String theArtifactId) {
338                         
339                 theHttpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
340                 theHttpResponse.setHeader("Pragma", "no-cache");
341                 theHttpResponse.setHeader("Expires", "0");
342                 theHttpResponse.setStatus(HttpServletResponse.SC_OK);
343
344                 final ControllerContext ctx = new ControllerContext();
345                 return new Callable<InputStreamResource>() {
346                         public InputStreamResource call() throws Exception {
347                                 try {   
348                                         return artifactService.getArtifactContent(
349                                                                         catalogService.getSolutionRevisionArtifact(theArtifactId, ctx), ctx);
350                                 } 
351                                 catch (Exception x) {
352                                         log.error(EELFLoggerDelegate.errorLogger,
353                                                 "An error occurred while retrieving artifact content " + theArtifactId, x);
354                                         throw x;
355                                 }
356                         }
357                 };
358         }
359
360         /** */
361         private void encodeSolution(MLPSolution theSolution, HttpServletRequest theRequest) throws URISyntaxException {
362                 //encode the 'origin'
363                 if (null == theSolution.getOrigin()) {
364                         URI requestUri = new URI(theRequest.getRequestURL().toString());
365                         URI solutionUri = API.SOLUTION_DETAIL
366                                                                                                 .buildUri(
367                                                                                                         new URI(requestUri.getScheme(), null, requestUri.getHost(),
368                                                                                                                                         requestUri.getPort(), null, null, null).toString(),
369                                                                                                         theSolution.getSolutionId());
370                         theSolution.setOrigin(solutionUri.toString());  
371                 }
372         }
373         
374         /** */
375         private void encodeArtifact(MLPArtifact theArtifact, HttpServletRequest theRequest) throws URISyntaxException {
376
377                 String artifactUri = theArtifact.getUri();
378
379                 //redirect              
380                 {
381                         URI requestUri = new URI(theRequest.getRequestURL().toString());
382                         URI redirectUri = API.ARTIFACT_DOWNLOAD
383                                                                                                 .buildUri(
384                                                                                                         new URI(requestUri.getScheme(), null, requestUri.getHost(),
385                                                                                                                                         requestUri.getPort(), null, null, null).toString(),
386                                                                                                         theArtifact.getArtifactId());
387                         log.debug(EELFLoggerDelegate.debugLogger,       "getSolutionRevisionArtifacts: redirected artifact uri " + redirectUri);
388                         theArtifact.setUri(redirectUri.toString());
389                 }
390                 
391                 if (ArtifactType.DockerImage == ArtifactType.forCode(theArtifact.getArtifactTypeCode())) {
392                         if (artifactUri != null) {
393                                 Identifier imageId = Identifier.fromCompoundString(artifactUri);
394                         
395                                 String imageTag = imageId.tag.get(); //there should always be a tag, right??
396                                 log.debug(EELFLoggerDelegate.debugLogger,       "getSolutionRevisionArtifacts: encoded docker image uri to tag " + imageTag);
397                                 theArtifact.setName(imageTag);
398                                 theArtifact.setDescription(imageTag);
399                         }
400                 }
401         }
402         
403 }