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