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