64b5fab9f64bd0309740069378afb1a837420b2c
[ccsdk/features.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP : ccsdk features
4  * ================================================================================
5  * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property.
6  * All rights reserved.
7  * ================================================================================
8  * Licensed 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  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  */
22 package org.onap.ccsdk.features.sdnr.wt.dataprovider.http.about;
23
24 import java.io.IOException;
25 import java.net.URL;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.jar.Attributes;
30 import java.util.jar.Manifest;
31 import javax.servlet.ServletException;
32 import javax.servlet.ServletOutputStream;
33 import javax.servlet.http.HttpServlet;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36 import org.apache.http.HttpHeaders;
37 //import org.apache.karaf.bundle.core.BundleInfo;
38 //import org.apache.karaf.bundle.core.BundleService;
39 import org.onap.ccsdk.features.sdnr.wt.common.Resources;
40 import org.onap.ccsdk.features.sdnr.wt.common.file.PomFile;
41 import org.onap.ccsdk.features.sdnr.wt.common.file.PomPropertiesFile;
42 import org.osgi.framework.Bundle;
43 import org.osgi.framework.BundleContext;
44 import org.osgi.framework.FrameworkUtil;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 public class AboutHttpServlet extends HttpServlet {
49
50     /**
51      *
52      */
53     private static final long serialVersionUID = 1L;
54     private static final Logger LOG = LoggerFactory.getLogger(AboutHttpServlet.class);
55     private static final String UNKNOWN = "unknown";
56     private static final String METAINF_MAVEN = "/META-INF/maven/";
57     private static final String EXCEPTION_FORMAT_UNABLE_TO_READ_INNER_POMFILE = "unable to read inner pom file: {}";
58
59     private static final String URI_PRE = "/about";
60     private static final String RES_BASEPATH = "about/";
61
62     private static final String PLACEHOLDER_ONAP_RELEASENAME = "{release-name}";
63     private static final String PLACEHOLDER_ONAP_RELEASEVERSION = "{release-version}";
64     private static final String PLACEHOLDER_ODL_RELEASENAME = "{odl-version}";
65     private static final String PLACEHOLDER_BUILD_TIMESTAMP = "{build-time}";
66     private static final String PLACEHOLDER_PACKAGE_GITHASH = "{package-githash}";
67     private static final String PLACEHOLDER_PACAKGE_VERSION = "{package-version}";
68     private static final String PLACEHOLDER_CCSDK_VERSION = "{ccsdk-version}";
69     private static final String PLACEHOLDER_CLUSTER_SIZE = "{cluster-size}";
70     private static final String PLACEHOLDER_MDSAL_VERSION = "{mdsal-version}";
71     private static final String PLACEHOLDER_YANGTOOLS_VERSION = "{yangtools-version}";
72     private static final String PLACEHOLDER_KARAF_INFO = "{karaf-info}";
73     private static final String PLACEHOLDER_DEVICEMANAGER_TABLE = "{devicemanagers}";
74     private static final String README_FILE = "README.md";
75     private static final String JSON_FILE = "README.json";
76     private static final String NO_DEVICEMANAGERS_RUNNING_MESSAGE = null;
77     private static final String MIMETYPE_JSON = "application/json";
78     private static final String MIMETYPE_MARKDOWN = "text/markdown";
79
80     private final String groupId = this.getGroupIdOrDefault("org.onap.ccsdk.features.sdnr.wt");
81     private final String artifactId = "sdnr-wt-data-provider-provider";
82
83     private final Map<Integer, String> BUNDLESTATE_LUT;
84     private final Map<String, String> data;
85     private final String readmeContent;
86     //  private BundleService bundleService;
87     private String jsonContent;
88
89
90     public AboutHttpServlet() {
91
92         this.data = new HashMap<>();
93         this.collectStaticData();
94         this.readmeContent = this.render(ContentType.MARKDOWN, this.getResourceFileContent(README_FILE));
95         this.jsonContent = this.render(ContentType.MARKDOWN, this.getResourceFileContent(JSON_FILE));
96         this.BUNDLESTATE_LUT = new HashMap<>();
97         this.BUNDLESTATE_LUT.put(Bundle.UNINSTALLED, "uninstalled");
98         this.BUNDLESTATE_LUT.put(Bundle.INSTALLED, "installed");
99         this.BUNDLESTATE_LUT.put(Bundle.RESOLVED, "resolved");
100         this.BUNDLESTATE_LUT.put(Bundle.STARTING, "starting");
101         this.BUNDLESTATE_LUT.put(Bundle.STOPPING, "stopping");
102         this.BUNDLESTATE_LUT.put(Bundle.ACTIVE, "active");
103
104     }
105
106     protected String getGroupIdOrDefault(String def) {
107         String symbolicName = this.getManifestValue("Bundle-SymbolicName");
108         if (symbolicName != null) {
109             int idx = symbolicName.indexOf(this.artifactId);
110             if (idx > 0) {
111                 return symbolicName.substring(0, idx - 1);
112             }
113         }
114         return def;
115     }
116
117     //  public void setBundleService(BundleService bundleService) {
118     //          this.bundleService = bundleService;
119     //  }
120
121     /**
122      * collect static versioning data
123      */
124     private void collectStaticData() {
125         PomPropertiesFile props = this.getPomProperties();
126         final String ccsdkVersion = this.getPomParentVersion();
127         final String mdsalVersion = SystemInfo.getMdSalVersion(UNKNOWN);
128         this.data.put(PLACEHOLDER_ONAP_RELEASENAME, ODLVersionLUT.getONAPReleaseName(ccsdkVersion, UNKNOWN));
129         this.data.put(PLACEHOLDER_ODL_RELEASENAME, ODLVersionLUT.getOdlVersion(mdsalVersion, UNKNOWN));
130         this.data.put(PLACEHOLDER_BUILD_TIMESTAMP, props != null ? props.getBuildDate().toString() : "");
131         this.data.put(PLACEHOLDER_PACAKGE_VERSION, this.getManifestValue("Bundle-Version"));
132         this.data.put(PLACEHOLDER_CCSDK_VERSION, ccsdkVersion);
133         this.data.put(PLACEHOLDER_ONAP_RELEASEVERSION, SystemInfo.getOnapVersion(UNKNOWN));
134         this.data.put(PLACEHOLDER_MDSAL_VERSION, mdsalVersion);
135         this.data.put(PLACEHOLDER_YANGTOOLS_VERSION, SystemInfo.getYangToolsVersion(UNKNOWN));
136         this.data.put(PLACEHOLDER_PACKAGE_GITHASH, this.getGitHash(UNKNOWN));
137     }
138
139     @Override
140     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
141
142         String uri = req.getRequestURI().substring(URI_PRE.length());
143         LOG.debug("request for {}", uri);
144         if (uri.length() <= 0 || uri.equals("/")) {
145             ContentType ctype = this.detectContentType(req, ContentType.MARKDOWN);
146             // collect data
147             this.collectData(ctype);
148             // render readme
149             String content = this.render(ctype);
150             byte[] output = content != null ? content.getBytes() : new byte[0];
151             // output
152             resp.setStatus(HttpServletResponse.SC_OK);
153             resp.setContentLength(output.length);
154             resp.setContentType(ctype.getMimeType());
155             ServletOutputStream os = null;
156             try {
157                 os = resp.getOutputStream();
158                 os.write(output);
159             } catch (IOException e) {
160                 LOG.warn("problem writing response for {}: {}", uri, e);
161             } finally {
162                 if (os != null) {
163                     try {
164                         os.close();
165                     } catch (IOException e) {
166                         LOG.warn("problem closing response stream: {}", e);
167                     }
168                 }
169             }
170
171         } else {
172             this.doGetFile(uri, resp);
173         }
174     }
175
176     /**
177      * load git.commit.id from jar /META-INF/git.properties
178      *
179      * @param def
180      */
181     private String getGitHash(String def) {
182         String content = Resources.getFileContent(AboutHttpServlet.class, "/META-INF/git.properties");
183         if (content == null) {
184             return def;
185         }
186         String lines[] = content.split("\n");
187         for (String line : lines) {
188             if (line.startsWith("git.commit.id")) {
189                 def = line.substring("git.commit.id=".length());
190                 break;
191             }
192         }
193         return def;
194     }
195
196     private String getResourceFileContent(String filename) {
197         LOG.debug("try ti get content of {}", filename);
198         return Resources.getFileContent(AboutHttpServlet.class, RES_BASEPATH + filename);
199     }
200
201     /**
202      * collect dynamic data for about.md
203      */
204     private void collectData(ContentType ctype) {
205         LOG.info("collecting dynamic data");
206         try {
207             this.data.put(PLACEHOLDER_KARAF_INFO, SystemInfo.get());
208             this.data.put(PLACEHOLDER_DEVICEMANAGER_TABLE, this.getDevicemanagerBundles(ctype));
209         } catch (Exception e) {
210             LOG.warn("problem collecting system data: {}", e);
211         }
212     }
213
214     /**
215      * get value for key out of /META-INF/MANIFEST.MF
216      *
217      * @param key
218      * @return
219      */
220     protected String getManifestValue(String key) {
221         URL url = Resources.getUrlForRessource(AboutHttpServlet.class, "/META-INF/MANIFEST.MF");
222         if (url == null) {
223             return null;
224         }
225         Manifest manifest;
226         try {
227             manifest = new Manifest(url.openStream());
228             Attributes attr = manifest.getMainAttributes();
229             return attr.getValue(key);
230         } catch (IOException e) {
231             LOG.warn("problem reading manifest: {}", e);
232         }
233         return null;
234
235     }
236
237     /**
238      * get object representation of /META-INF/maven/groupId/artifactId/pom.properties
239      *
240      * @return
241      */
242     private PomPropertiesFile getPomProperties() {
243         URL url = Resources.getUrlForRessource(AboutHttpServlet.class,
244                 METAINF_MAVEN + groupId + "/" + artifactId + "/pom.properties");
245         PomPropertiesFile propfile;
246         if (url == null) {
247             return null;
248         }
249         try {
250             propfile = new PomPropertiesFile(url.openStream());
251             return propfile;
252         } catch (Exception e) {
253             LOG.warn(EXCEPTION_FORMAT_UNABLE_TO_READ_INNER_POMFILE, e);
254         }
255         return null;
256     }
257
258     /**
259      * get parent pom version out of /META-INF/maven/groupId/artifactId/pom.xml
260      *
261      * @return
262      */
263     private String getPomParentVersion() {
264         LOG.info("try to get pom parent version");
265         URL url = Resources.getUrlForRessource(AboutHttpServlet.class,
266                 METAINF_MAVEN + groupId + "/" + artifactId + "/pom.xml");
267         if (url == null) {
268             return null;
269         }
270         PomFile pomfile;
271         try {
272             pomfile = new PomFile(url.openStream());
273             return pomfile.getParentVersion();
274         } catch (Exception e) {
275             LOG.warn(EXCEPTION_FORMAT_UNABLE_TO_READ_INNER_POMFILE, e);
276         }
277         return null;
278     }
279
280     private String getDevicemanagerBundles(ContentType ctype) {
281         Bundle thisbundle = FrameworkUtil.getBundle(this.getClass());
282         BundleContext context = thisbundle == null ? null : thisbundle.getBundleContext();
283         if (context == null) {
284             LOG.debug("no bundle context available");
285             return ctype==ContentType.MARKDOWN?"":"[]";
286         }
287         Bundle[] bundles = context.getBundles();
288         if (bundles == null || bundles.length <= 0) {
289             LOG.debug("no bundles found");
290             return ctype==ContentType.MARKDOWN?NO_DEVICEMANAGERS_RUNNING_MESSAGE:"[]";
291         }
292         LOG.debug("found {} bundles", bundles.length);
293         MarkdownTable table = new MarkdownTable();
294         table.setHeader(new String[] {"Bundle-Id", "Version", "Symbolic-Name", "Status"});
295         String name;
296         for (Bundle bundle : bundles) {
297             name = bundle.getSymbolicName();
298             if (!(name.contains("devicemanager") && name.contains("provider"))) {
299                 continue;
300             }
301             if (name.equals("org.onap.ccsdk.features.sdnr.wt.sdnr-wt-devicemanager-provider")) {
302                 continue;
303             }
304             table.addRow(new String[] {String.valueOf(bundle.getBundleId()), bundle.getVersion().toString(), name,
305                     BUNDLESTATE_LUT.getOrDefault(bundle.getState(), "unknown")});
306
307         }
308         return ctype==ContentType.MARKDOWN?table.toMarkDown():table.toJson();
309     }
310
311     /**
312      * get file by uri from resources and write out to response stream
313      *
314      * @param uri
315      * @param resp
316      */
317     private void doGetFile(String uri, HttpServletResponse resp) {
318         String content = this.getResourceFileContent(uri);
319         if (content == null) {
320             try {
321                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
322             } catch (IOException e) {
323                 LOG.debug("unable to send error response : {}", e);
324             }
325         } else {
326             byte[] data = content.getBytes();
327             resp.setStatus(HttpServletResponse.SC_OK);
328             resp.setContentType(this.getContentType(uri));
329             try {
330                 resp.getOutputStream().write(data);
331             } catch (IOException e) {
332                 LOG.debug("unable to send data : {}", e);
333             }
334         }
335
336     }
337
338     /**
339      * create http response contentType by filename
340      *
341      * @param filename
342      * @return
343      */
344     private String getContentType(String filename) {
345         String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
346         switch (ext) {
347             case "jpg":
348             case "jpeg":
349             case "svg":
350             case "png":
351             case "gif":
352             case "bmp":
353                 return "image/" + ext;
354             case "json":
355                 return "application/json";
356             case "html":
357             case "htm":
358                 return "text/html";
359             case "txt":
360             case "md":
361             default:
362                 return "text/plain";
363         }
364     }
365
366     /**
367      * render this.readmeContent with this.data
368      *
369      * @param ctype
370      *
371      * @return
372      */
373     private String render(ContentType ctype) {
374         return this.render(ctype, null);
375     }
376
377     /**
378      * render content with this.data
379      *
380      * @param content
381      * @return
382      */
383     private String render(ContentType ctype, String content) {
384         if (content == null) {
385             content = ctype==ContentType.MARKDOWN? this.readmeContent:this.jsonContent;
386         }
387         if (content == null) {
388             return null;
389         }
390         for (Entry<String, String> entry : this.data.entrySet()) {
391             if (entry.getValue() != null && content.contains(entry.getKey())) {
392                 content = content.replace(entry.getKey(), entry.getValue());
393             }
394         }
395
396         return content;
397     }
398
399     public void setClusterSize(String value) {
400         this.data.put(PLACEHOLDER_CLUSTER_SIZE, value);
401     }
402
403     private ContentType detectContentType(HttpServletRequest req, ContentType def) {
404         String accept = req.getHeader(HttpHeaders.ACCEPT);
405         if (accept != null) {
406             if (accept.equals(MIMETYPE_JSON)) {
407                 return ContentType.JSON;
408             } else if (accept.equals(MIMETYPE_MARKDOWN)) {
409                 return ContentType.MARKDOWN;
410             }
411         }
412         return def;
413     }
414
415     private enum ContentType {
416         MARKDOWN(MIMETYPE_MARKDOWN), JSON(MIMETYPE_JSON);
417
418         private String mimeType;
419
420         ContentType(String mimeType) {
421             this.mimeType = mimeType;
422         }
423
424         String getMimeType() {
425             return this.mimeType;
426         }
427     }
428 }