Federation 3.2.0 - Model Data api
[federation.git] / gateway / src / main / java / org / acumos / federation / gateway / FederationController.java
1 /*-
2  * ===============LICENSE_START=======================================================
3  * Acumos
4  * ===================================================================================
5  * Copyright (C) 2017-2019 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 package org.acumos.federation.gateway;
21
22 import java.lang.invoke.MethodHandles;
23 import java.util.concurrent.Callable;
24 import java.util.List;
25
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
28
29 import io.swagger.annotations.ApiOperation;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import org.springframework.beans.factory.annotation.Autowired;
35 import org.springframework.core.io.InputStreamResource;
36 import org.springframework.core.io.Resource;
37 import org.springframework.http.MediaType;
38 import org.springframework.stereotype.Controller;
39 import org.springframework.web.bind.annotation.CrossOrigin;
40 import org.springframework.web.bind.annotation.ExceptionHandler;
41 import org.springframework.web.bind.annotation.GetMapping;
42 import org.springframework.web.bind.annotation.PathVariable;
43 import org.springframework.web.bind.annotation.PostMapping;
44 import org.springframework.web.bind.annotation.RequestBody;
45 import org.springframework.web.bind.annotation.RequestMapping;
46 import org.springframework.web.bind.annotation.RequestParam;
47 import org.springframework.web.bind.annotation.ResponseBody;
48 import org.springframework.web.util.UriTemplateHandler;
49 import org.springframework.security.access.annotation.Secured;
50 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
51
52 import org.acumos.cds.domain.MLPPeer;
53 import org.acumos.cds.domain.MLPCatalog;
54 import org.acumos.cds.domain.MLPSolution;
55 import org.acumos.cds.domain.MLPSolutionRevision;
56 import org.acumos.cds.domain.MLPArtifact;
57 import org.acumos.cds.domain.MLPDocument;
58
59 import org.acumos.federation.client.ClientBase;
60 import org.acumos.federation.client.config.ClientConfig;
61 import org.acumos.federation.client.FederationClient;
62 import org.acumos.federation.client.data.Artifact;
63 import org.acumos.federation.client.data.Document;
64 import org.acumos.federation.client.data.JsonResponse;
65 import org.acumos.federation.client.data.ModelData;
66 import org.acumos.federation.client.data.SolutionRevision;
67
68 /**
69  * Controller bean for the external (public) API.
70  */
71 @Controller
72 @CrossOrigin
73 @RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
74 public class FederationController {
75         private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
76
77         @Autowired
78         private FederationConfig federation;
79
80         @Autowired
81         private WebSecurityConfigurerAdapter security;
82
83         @Autowired
84         private PeerService peerService;
85
86         @Autowired
87         private CatalogService catalogService;
88
89         @Autowired
90         private LogstashService logstashService;
91
92         @Autowired
93         private ContentService contentService;
94
95         private UriTemplateHandler originBuilder;
96
97         private String makeOrigin(String uri, Object... params) {
98                 if (originBuilder == null) {
99                         originBuilder = ClientBase.buildRestTemplate("https://" + ((Security)security).getSelf().getSubjectName() + ":" + federation.getServer().getPort(), new ClientConfig(), null, null).getUriTemplateHandler();
100                 }
101                 return originBuilder.expand(uri, params).toString();
102         }
103
104         private void markOrigin(MLPSolution sol) {
105                 if (sol.getOrigin() == null) {
106                         sol.setOrigin(makeOrigin(FederationClient.SOLUTION_URI, sol.getSolutionId()));
107                 }
108         }
109
110         private static String makeFilename(String uri, boolean isnexus) {
111                 if (uri == null) {
112                         return null;
113                 }
114                 String[] urix = uri.split("/");
115                 int len = urix.length;
116                 String tag = "";
117                 int off = urix[len - 1].lastIndexOf('.');
118                 if (isnexus && off != -1) {
119                         tag = urix[len - 1].substring(off);
120                         urix[len - 1] = urix[len - 1].substring(0, off);
121                 }
122                 if (isnexus && len >= 3 && urix[len - 1].equals(urix[len - 3] + '-' + urix[len - 2])) {
123                         return urix[len - 3] + tag;
124                 }
125                 uri = urix[len - 1];
126                 off = uri.indexOf(isnexus? '-': ':');
127                 if (off != -1) {
128                         uri = uri.substring(0, off);
129                 }
130                 return uri + tag;
131         }
132
133         private void markOrigin(MLPArtifact art) {
134                 ((Artifact)art).setFilename(makeFilename(art.getUri(), !FederationClient.ATC_DOCKER.equals(art.getArtifactTypeCode())));
135                 if (!Security.isCurrentPeerLocal()) {
136                         if (art.getUri() != null && FederationClient.ATC_DOCKER.equals(art.getArtifactTypeCode())) {
137                                 art.setDescription(art.getUri());
138                         }
139                         art.setUri(makeOrigin(FederationClient.ARTIFACT_URI, art.getArtifactId()));
140                 }
141         }
142
143         private void markOrigin(MLPDocument doc) {
144                 ((Document)doc).setFilename(makeFilename(doc.getUri(), true));
145                 if (!Security.isCurrentPeerLocal()) {
146                         doc.setUri(makeOrigin(FederationClient.DOCUMENT_URI, doc.getDocumentId()));
147                 }
148         }
149
150         @Secured(Security.ROLE_PEER)
151         @ApiOperation(value = "Invoked by Peer Acumos to get status and self information.", response = MLPPeer.class)
152         @GetMapping(FederationClient.PING_URI)
153         @ResponseBody
154         public JsonResponse<MLPPeer> ping() {
155                 log.debug("/ping");
156                 return respond(((Security)security).getSelf());
157         }
158
159         @Secured(Security.ROLE_PARTNER)
160         @ApiOperation(value = "Invoked by Peer Acumos to get a list of peers from local Acumos Instance .", response = MLPPeer.class, responseContainer = "List")
161         @GetMapping(FederationClient.PEERS_URI)
162         @ResponseBody
163         public JsonResponse<List<MLPPeer>> getPeers() {
164                 log.debug("/peers");
165                 return respond(peerService.getPeers());
166         }
167
168         @Secured(Security.ROLE_REGISTER)
169         @ApiOperation(value = "Invoked by another Acumos Instance to request federation.", response = MLPPeer.class)
170         @PostMapping(FederationClient.REGISTER_URI)
171         @ResponseBody
172         public JsonResponse<MLPPeer> register() {
173                 log.debug("/peer/register");
174                 if (!federation.isRegistrationEnabled()) {
175                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "Not Found");
176                 }
177                 peerService.register();
178                 return respond(((Security)security).getSelf());
179         }
180
181         @Secured(Security.ROLE_UNREGISTER)
182         @ApiOperation(value = "Invoked by another Acumos Instance to request federation termination.", response = MLPPeer.class)
183         @PostMapping(FederationClient.UNREGISTER_URI)
184         @ResponseBody
185         public JsonResponse<MLPPeer> unregister() {
186                 log.debug("/peer/unregister");
187                 if (!federation.isRegistrationEnabled()) {
188                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "Not Found");
189                 }
190                 peerService.unregister();
191                 return respond(((Security)security).getSelf());
192         }
193
194         @Secured(Security.ROLE_PEER)
195         @ApiOperation(value = "Invoked by Peer Acumos to get a list of visible Catalogs from the local Acumos Instance .", response = MLPCatalog.class, responseContainer = "List")
196         @GetMapping(FederationClient.CATALOGS_URI)
197         @ResponseBody
198         public JsonResponse<List<MLPCatalog>> getCatalogs() {
199                 log.debug("/catalogs");
200                 return respond(catalogService.getCatalogs());
201         }
202
203         @Secured(Security.ROLE_PEER)
204         @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")
205         @GetMapping(FederationClient.SOLUTIONS_URI)
206         @ResponseBody
207         public JsonResponse<List<MLPSolution>> getSolutions(@RequestParam(value="catalogId", required = true) String catalogId) {
208                 log.debug("/solutions?catalogId={}", catalogId);
209                 if (!catalogService.isCatalogAllowed(catalogId)) {
210                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No catalog with id " + catalogId);
211                 }
212                 List<MLPSolution> ret = catalogService.getSolutions(catalogId);
213                 for (MLPSolution sol: ret) {
214                         markOrigin(sol);
215                 }
216                 return respond(ret);
217         }
218
219         @Secured(Security.ROLE_PEER)
220         @ApiOperation(value = "Invoked by Peer Acumos to get a list detailed solution information from the Catalog of the local Acumos Instance .", response = MLPSolution.class)
221         @GetMapping(FederationClient.SOLUTION_URI)
222         @ResponseBody
223         public JsonResponse<MLPSolution> getSolution(@PathVariable("solutionId") String solutionId) {
224                 log.debug("/solutions/{}", solutionId);
225                 MLPSolution ret = null;
226                 if (!catalogService.isSolutionAllowed(solutionId) || (ret = catalogService.getSolution(solutionId)) == null) {
227                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No solution with id " + solutionId);
228                 }
229                 markOrigin(ret);
230                 return respond(ret);
231         }
232
233         @Secured(Security.ROLE_PEER)
234         @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")
235         @GetMapping(FederationClient.REVISIONS_URI)
236         @ResponseBody
237         public JsonResponse<List<MLPSolutionRevision>> getRevisions(@PathVariable("solutionId") String solutionId) {
238                 log.debug("/solutions/{}/revisions", solutionId);
239                 List<MLPSolutionRevision> ret = null;
240                 if (!catalogService.isSolutionAllowed(solutionId) || (ret = catalogService.getRevisions(solutionId)).isEmpty()) {
241                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No solution with id " + solutionId);
242                 }
243                 return respond(ret);
244         }
245
246         @Secured(Security.ROLE_PEER)
247         @ApiOperation(value = "Invoked by peer Acumos to get solution revision details from the local Acumos Instance .", response = MLPSolutionRevision.class)
248         @GetMapping(FederationClient.REVISION_URI)
249         @ResponseBody
250         public JsonResponse<MLPSolutionRevision> getRevision(
251             @PathVariable("solutionId") String solutionId,
252             @PathVariable("revisionId") String revisionId,
253             @RequestParam(value = "catalogId", required = false) String catalogId) {
254                 log.debug("/solutions/{}/revisions/{}?catalogId={}", solutionId, revisionId, catalogId);
255                 if (catalogId != null && !catalogService.isCatalogAllowed(catalogId)) {
256                         catalogId = null;
257                 }
258                 SolutionRevision ret = (SolutionRevision)catalogService.getRevision(revisionId, catalogId);
259                 if (ret == null || !catalogService.isSolutionAllowed(ret.getSolutionId())) {
260                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No revision with id " + revisionId);
261                 }
262                 for (MLPArtifact art: ret.getArtifacts()) {
263                         markOrigin(art);
264                 }
265                 for (MLPDocument doc: ret.getDocuments()) {
266                         markOrigin(doc);
267                 }
268                 return respond(ret);
269         }
270
271         @Secured(Security.ROLE_PEER)
272         @ApiOperation(value = "Invoked by Peer Acumos to get a list of solution revision artifacts from the local Acumos Instance .", response = MLPArtifact.class, responseContainer = "List")
273         @GetMapping(FederationClient.ARTIFACTS_URI)
274         @ResponseBody
275         public JsonResponse<List<MLPArtifact>> getArtifacts(
276             @PathVariable("solutionId") String solutionId,
277             @PathVariable("revisionId") String revisionId) {
278                 log.debug("/solutions/{}/revisions/{}/artifacts", solutionId, revisionId);
279                 if (!catalogService.isRevisionAllowed(revisionId)) {
280                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No revision with id " + revisionId);
281                 }
282                 List<MLPArtifact> ret = catalogService.getArtifacts(revisionId);
283                 for (MLPArtifact art: ret) {
284                         markOrigin(art);
285                 }
286                 return respond(ret);
287         }
288
289         @Secured(Security.ROLE_PEER)
290         @ApiOperation(value = "Invoked by Peer Acumos to get a list of solution revision public documents from the local Acumos Instance .", response = MLPArtifact.class, responseContainer = "List")
291         @GetMapping(FederationClient.DOCUMENTS_URI)
292         @ResponseBody
293         public JsonResponse<List<MLPDocument>> getDocuments(
294             @PathVariable("revisionId") String revisionId,
295             @RequestParam(value = "catalogId", required = true) String catalogId) {
296                 log.debug("/revisions/{}/documents?catalogId={}", revisionId, catalogId);
297                 if (!catalogService.isCatalogAllowed(catalogId)) {
298                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No catalog with id " + catalogId);
299                 }
300                 List<MLPDocument> ret = catalogService.getDocuments(revisionId, catalogId);
301                 for (MLPDocument doc: ret) {
302                         markOrigin(doc);
303                 }
304                 return respond(ret);
305         }
306
307         @Secured(Security.ROLE_PEER)
308         @ApiOperation(value = "API to download artifact content", response = Resource.class, code = 200)
309         @GetMapping(value = FederationClient.ARTIFACT_URI, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
310         @ResponseBody
311         public Callable<Resource> getArtifactContent(@PathVariable("artifactId") String artifactId) {
312                 log.debug("/artifacts/{}/content", artifactId);
313                 if (!catalogService.isArtifactAllowed(artifactId)) {
314                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No artifact with id " + artifactId);
315                 }
316                 return () -> new InputStreamResource(contentService.getArtifactContent(catalogService.getArtifact(artifactId)));
317         }
318
319         @Secured(Security.ROLE_PEER)
320         @ApiOperation(value = "API to download document content", response = Resource.class, code = 200)
321         @GetMapping(value = FederationClient.DOCUMENT_URI, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
322         @ResponseBody
323         public Resource getDocumentContent(@PathVariable("documentId") String documentId) {
324                 log.debug("/documents/{}/content", documentId);
325                 if (!catalogService.isDocumentAllowed(documentId)) {
326                         throw new BadRequestException(HttpServletResponse.SC_NOT_FOUND, "No document with id " + documentId);
327                 }
328                 return new InputStreamResource(contentService.getDocumentContent(catalogService.getDocument(documentId)));
329         }
330
331         private <T> JsonResponse<T> respond(T content) {
332                 JsonResponse<T> ret = new JsonResponse<>();
333                 ret.setContent(content);
334                 return ret;
335         }
336
337         @ExceptionHandler(BadRequestException.class)
338         @ResponseBody
339         public JsonResponse<Void> badRequestError(HttpServletRequest request, HttpServletResponse response, BadRequestException badRequest) {
340                 log.warn("Rejecting invalid request {}: {} {} {}", request.getRequestURI(), badRequest.getMessage(), badRequest.getCode(), badRequest);
341                 JsonResponse<Void> ret = new JsonResponse<>();
342                 ret.setError(badRequest.getMessage());
343                 response.setStatus(badRequest.getCode());
344                 return ret;
345         }
346
347         /**
348          * Receives model data payload from
349          * {@link GatewayController#peerModelData(HttpServletResponse, ModelData, String)}
350          *
351          * @param payload         model data payload The payload must have a model.solutionId
352          * 
353          * @param theHttpResponse HttpServletResponse
354          * 
355          */
356         @CrossOrigin
357         @Secured(Security.ROLE_PEER)
358         @ApiOperation(value = "Invoked by Peer Acumos to post model data to elastic search service .",
359                         response = Void.class)
360         @PostMapping(FederationClient.MODEL_DATA)
361         @ResponseBody
362         public void receiveModelData(@RequestBody ModelData payload,
363                         HttpServletResponse theHttpResponse) {
364
365                 log.debug(FederationClient.MODEL_DATA);
366                 log.debug("Model parameters: {}", payload);
367                 logstashService.sendModelData(payload);
368         }
369 }