This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch maintenance-1.0.x in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-console.git
commit 8bdd88ff7e203b1ab9cd0f058d4e2a930c09638c Author: Konrad Windszus <[email protected]> AuthorDate: Sat Jan 4 18:28:51 2020 +0100 SLING-8897 dedicated console for serializing configurations (#1) * SLING-8897 dedicated console for serializing configurations Update to parent 35 --- pom.xml | 59 ++--- .../ConfigurationSerializerWebConsolePlugin.java | 292 +++++++++++++++++++++ src/main/resources/res/ui/clipboard.js | 31 +++ 3 files changed, 352 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index 011c683..ed1611b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,35 +1,35 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file + or more contributor license agreements. See the NOTICE file distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file + regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the + KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.apache.sling</groupId> - <artifactId>sling</artifactId> - <version>30</version> - <relativePath /> + <artifactId>sling-bundle-parent</artifactId> + <version>35</version> + <relativePath/> </parent> <artifactId>org.apache.sling.installer.console</artifactId> <version>1.0.3-SNAPSHOT</version> - <packaging>bundle</packaging> <name>Apache Sling Installer WebConsole Plugin</name> <description> @@ -42,37 +42,36 @@ <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-installer-console.git</url> </scm> - <build> - <plugins> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>maven-bundle-plugin</artifactId> - <extensions>true</extensions> - <configuration> - <instructions> - <Private-Package> - org.apache.sling.installer.core.impl.console - </Private-Package> - </instructions> - </configuration> - </plugin> - </plugins> - </build> - <dependencies> <dependency> <groupId>org.osgi</groupId> - <artifactId>osgi.core</artifactId> + <artifactId>org.osgi.framework</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.cm</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.installer.core</artifactId> - <version>3.8.0</version> + <version>3.9.1-SNAPSHOT</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> + <scope>provided</scope> </dependency> </dependencies> </project> diff --git a/src/main/java/org/apache/sling/installer/core/impl/console/ConfigurationSerializerWebConsolePlugin.java b/src/main/java/org/apache/sling/installer/core/impl/console/ConfigurationSerializerWebConsolePlugin.java new file mode 100644 index 0000000..d6d065f --- /dev/null +++ b/src/main/java/org/apache/sling/installer/core/impl/console/ConfigurationSerializerWebConsolePlugin.java @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.installer.core.impl.console; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; + +import javax.servlet.GenericServlet; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory; +import org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory.Format; +import org.osgi.framework.Constants; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service=javax.servlet.Servlet.class, + property = { + Constants.SERVICE_VENDOR + "=The Apache Software Foundation", + Constants.SERVICE_DESCRIPTION + "=Apache Sling OSGi Installer Configuration Serializer Web Console Plugin", + "felix.webconsole.label=" + ConfigurationSerializerWebConsolePlugin.LABEL, + "felix.webconsole.title=OSGi Installer Configuration Printer", + "felix.webconsole.category=OSGi" + }) +@SuppressWarnings("serial") +public class ConfigurationSerializerWebConsolePlugin extends GenericServlet { + + public static final String LABEL = "osgi-installer-config-printer"; + private static final String RES_LOC = LABEL + "/res/ui"; + private static final String PARAMETER_PID = "pid"; + private static final String PARAMETER_FORMAT = "format"; + + // copied from org.apache.sling.installer.factories.configuration.impl.ConfigUtil + /** + * This property has been used in older versions to keep track where the + * configuration has been installed from. + */ + private static final String CONFIG_PATH_KEY = "org.apache.sling.installer.osgi.path"; + + /** + * This property has been used in older versions to keep track of factory + * configurations. + */ + private static final String ALIAS_KEY = "org.apache.sling.installer.osgi.factoryaliaspid"; + + /** Configuration properties to ignore when printing */ + private static final Set<String> IGNORED_PROPERTIES = new HashSet<>(); + static { + IGNORED_PROPERTIES.add(Constants.SERVICE_PID); + IGNORED_PROPERTIES.add(CONFIG_PATH_KEY); + IGNORED_PROPERTIES.add(ALIAS_KEY); + IGNORED_PROPERTIES.add(ConfigurationAdmin.SERVICE_FACTORYPID); + } + + /** The logger */ + private final Logger LOGGER = LoggerFactory.getLogger(ConfigurationSerializerWebConsolePlugin.class); + + @Reference + ConfigurationAdmin configurationAdmin; + + /** + * Method to retrieve static resources from this bundle. + */ + @SuppressWarnings("unused") + private URL getResource(final String path) { + if (path.startsWith("/" + RES_LOC)) { + return this.getClass().getResource(path.substring(LABEL.length()+1)); + } + return null; + } + + @Override + public void service(final ServletRequest request, final ServletResponse response) + throws IOException { + + final String pid = request.getParameter(PARAMETER_PID); + final String format = request.getParameter(PARAMETER_FORMAT); + ConfigurationSerializerFactory.Format serializationFormat = Format.JSON; + if (format != null && !format.trim().isEmpty()) { + try { + serializationFormat = ConfigurationSerializerFactory.Format.valueOf(format); + } catch (IllegalArgumentException e) { + LOGGER.warn("Illegal parameter 'format' given, falling back to default '{}'", serializationFormat, e); + } + } + final PrintWriter pw = response.getWriter(); + + pw.println("<script type=\"text/javascript\" src=\"" + RES_LOC + "/clipboard.js\"></script>"); + pw.print("<form method='get'>"); + pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>"); + + titleHtml( + pw, + "OSGi Installer Configuration Printer", + "To emit the configuration properties just enter the configuration PID, select a <a href='https://sling.apache.org/documentation/bundles/configuration-installer-factory.html'>serialization format</a> and click 'Print'"); + + tr(pw); + tdLabel(pw, "PID"); + tdContent(pw); + + pw.print("<input type='text' name='"); + pw.print(PARAMETER_PID); + pw.print("' value='"); + if ( pid != null ) { + pw.print(escapeXml(pid)); + } + + pw.println("' class='input' size='120'>"); + closeTd(pw); + closeTr(pw); + closeTr(pw); + + tr(pw); + tdLabel(pw, "Serialization Format"); + tdContent(pw); + pw.print("<select name='"); + pw.print(PARAMETER_FORMAT); + pw.println("'>"); + option(pw, "JSON", "OSGi Configurator JSON", format); + option(pw, "CONFIG", "Apache Felix Config", format); + option(pw, "PROPERTIES", "Java Properties", format); + option(pw, "PROPERTIES_XML", "Java Properties (XML)", format); + pw.println("</select>"); + + pw.println(" <input type='submit' value='Print' class='submit'>"); + + closeTd(pw); + closeTr(pw); + + if (pid != null && !pid.trim().isEmpty()) { + tr(pw); + tdLabel(pw, "Serialized Configuration Properties"); + tdContent(pw); + + Configuration configuration = configurationAdmin.getConfiguration(pid); + Dictionary<String, Object> properties = configuration.getProperties(); + if (properties == null) { + pw.print("<p class='ui-state-error-text'>"); + pw.print("No configuration properties for pid '" + escapeXml(pid) + "' found!"); + pw.println("</p>"); + } else { + properties = cleanConfiguration(properties); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ConfigurationSerializerFactory.create(serializationFormat).serialize(properties, baos); + pw.println("<textarea rows=\"20\" cols=\"120\" id=\"output\" readonly>"); + pw.print(new String(baos.toByteArray(), StandardCharsets.UTF_8)); + pw.println("</textarea>"); + pw.println("<button type='button' id='copy'>Copy to Clipboard</a>"); + } catch (Throwable e) { + pw.print("<p class='ui-state-error-text'>"); + pw.print("Error serializing pid '" + escapeXml(pid) + "': " + e.getMessage()); + pw.println("</p>"); + LOGGER.warn("Error serializing pid '{}'", pid, e); + } + } + closeTd(pw); + closeTr(pw); + } + + pw.println("</table>"); + pw.print("</form>"); + } + + /** + * Copied from org.apache.sling.api.request.ResponseUtil + * Escape XML text + * @param input The input text + * @return The escaped text + */ + private String escapeXml(final String input) { + if (input == null) { + return null; + } + + final StringBuilder b = new StringBuilder(input.length()); + for(int i = 0;i < input.length(); i++) { + final char c = input.charAt(i); + if(c == '&') { + b.append("&"); + } else if(c == '<') { + b.append("<"); + } else if(c == '>') { + b.append(">"); + } else if(c == '"') { + b.append("""); + } else if(c == '\'') { + b.append("'"); + } else { + b.append(c); + } + } + return b.toString(); + } + + // copied from org.apache.sling.installer.factories.configuration.impl.ConfigUtil + /** + * Remove all ignored properties + */ + public static Dictionary<String, Object> cleanConfiguration(final Dictionary<String, Object> config) { + final Dictionary<String, Object> cleanedConfig = new Hashtable<>(); + final Enumeration<String> e = config.keys(); + while(e.hasMoreElements()) { + final String key = e.nextElement(); + if ( !IGNORED_PROPERTIES.contains(key) ) { + cleanedConfig.put(key, config.get(key)); + } + } + + return cleanedConfig; + } + + private void tdContent(final PrintWriter pw) { + pw.print("<td class='content' colspan='2'>"); + } + + private void closeTd(final PrintWriter pw) { + pw.print("</td>"); + } + + private void closeTr(final PrintWriter pw) { + pw.println("</tr>"); + } + + private void tdLabel(final PrintWriter pw, final String label) { + pw.print("<td class='content'>"); + pw.print(label); + pw.println("</td>"); + } + + private void tr(final PrintWriter pw) { + pw.println("<tr class='content'>"); + } + + private void option(final PrintWriter pw, String value, String label, String selectedValue) { + pw.print("<option value='"); + pw.print(value); + pw.print("'"); + if (value.equals(selectedValue)) { + pw.print(" selected"); + } + pw.print(">"); + pw.print(label); + pw.println("</option>"); + } + + private void titleHtml(final PrintWriter pw, final String title, final String description) { + tr(pw); + pw.print("<th colspan='3' class='content container'>"); + pw.print(escapeXml(title)); + pw.println("</th>"); + closeTr(pw); + + if (description != null) { + tr(pw); + pw.print("<td colspan='3' class='content'>"); + pw.print(description); + pw.println("</th>"); + closeTr(pw); + } + } + +} diff --git a/src/main/resources/res/ui/clipboard.js b/src/main/resources/res/ui/clipboard.js new file mode 100644 index 0000000..197f933 --- /dev/null +++ b/src/main/resources/res/ui/clipboard.js @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +function copyToClipboard(inputElement) { + inputElement.select(); + document.execCommand("copy"); +} + +window.onload = function() { + button = document.querySelector("#copy"); + if (button != null) { + button.addEventListener("click", function() { + copyToClipboard(document.querySelector("#output")); + }); + } +}
