Java tutorial
/* * Copyright (C) 2014 Servoy BV * * Licensed 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.sablo.specification; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import javax.servlet.ServletContext; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An abstraction of package that contains Servoy web-components. * @author acostescu */ public class WebComponentPackage { private static final Logger log = LoggerFactory.getLogger(WebComponentPackage.class.getCanonicalName()); private static final String GLOBAL_TYPES_MANIFEST_ATTR = "Global-Types"; private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; // for package name private static final String BUNDLE_NAME = "Bundle-Name"; // for package display name public interface IPackageReader { String getName(); String getPackageName(); String getPackageDisplayname(); Manifest getManifest() throws IOException; String readTextFile(String path, Charset charset) throws IOException; URL getUrlForPath(String path) throws MalformedURLException; URL getPackageURL(); /** * @param specpath * @param e */ void reportError(String specpath, Exception e); } public interface ISpecificationFilter { boolean filter(WebComponentSpecification spec); } private IPackageReader reader; public WebComponentPackage(IPackageReader reader) { if (reader == null) throw new NullPointerException(); this.reader = reader; } public String getName() { return reader.getName(); } public String getPackageName() { return reader.getPackageName(); } IPackageReader getReader() { return reader; } public void appendGlobalTypesJSON(JSONObject allGlobalTypesFromAllPackages) throws IOException { Manifest mf = reader.getManifest(); if (mf != null) { Attributes mainAttrs = mf.getMainAttributes(); if (mainAttrs != null) { String globalTypesSpecPath = mainAttrs.getValue(GLOBAL_TYPES_MANIFEST_ATTR); if (globalTypesSpecPath != null) { try { String specfileContent = reader.readTextFile(globalTypesSpecPath, Charset.forName("UTF8")); // TODO: check encoding if (specfileContent != null) { JSONObject json = new JSONObject(specfileContent); Object types = json.get(WebComponentSpecification.TYPES_KEY); if (types instanceof JSONObject) { Iterator<String> typesIt = ((JSONObject) types).keys(); while (typesIt.hasNext()) { String key = typesIt.next(); allGlobalTypesFromAllPackages.put(key, ((JSONObject) types).get(key)); } } } } catch (Exception e) { reader.reportError(globalTypesSpecPath, e); } } } } } public WebComponentPackageSpecification<WebComponentSpecification> getWebComponentDescriptions( String attributeName) throws IOException { String packageName = null; String packageDisplayname = null; Map<String, WebComponentSpecification> descriptions = new HashMap<>(); Manifest mf = reader.getManifest(); if (mf != null) { packageName = reader.getPackageName(); packageDisplayname = reader.getPackageDisplayname(); for (String specpath : getWebEntrySpecNames(mf, attributeName)) { String specfileContent = reader.readTextFile(specpath, Charset.forName("UTF8")); // TODO: check encoding if (specfileContent != null) { try { WebComponentSpecification parsed = WebComponentSpecification.parseSpec(specfileContent, reader.getPackageName(), reader); if (reader instanceof ISpecificationFilter && ((ISpecificationFilter) reader).filter(parsed)) continue; parsed.setSpecURL(reader.getUrlForPath(specpath)); if (parsed.getDefinition() != null) { String definition; if (packageName != null && parsed.getDefinition().startsWith(packageName + '/')) { definition = parsed.getDefinition().substring(packageName.length() + 1); } else if (packageName != null && parsed.getDefinition().startsWith("/")) { definition = parsed.getDefinition(); } else { log.warn("Definition file for spec file " + specpath + " does not start with package name '" + packageName + "'"); definition = parsed.getDefinition() .substring(parsed.getDefinition().indexOf("/") + 1); } parsed.setDefinitionFileURL(reader.getUrlForPath(definition)); } descriptions.put(parsed.getName(), parsed); } catch (Exception e) { reader.reportError(specpath, e); } } } } return new WebComponentPackageSpecification<>(packageName, packageDisplayname, descriptions, mf); } public WebComponentPackageSpecification<WebLayoutSpecification> getLayoutDescriptions() throws IOException { String packageName = null; String packageDisplayname = null; Map<String, WebLayoutSpecification> descriptions = new HashMap<>(); Manifest mf = reader.getManifest(); if (mf != null) { packageName = reader.getPackageName(); packageDisplayname = reader.getPackageDisplayname(); for (String specpath : getWebEntrySpecNames(mf, "Web-Layout")) { String specfileContent = reader.readTextFile(specpath, Charset.forName("UTF8")); // TODO: check encoding if (specfileContent != null) { try { WebLayoutSpecification parsed = WebLayoutSpecification.parseLayoutSpec(specfileContent, packageName, reader); parsed.setSpecURL(reader.getUrlForPath(specpath)); if (parsed.getDefinition() != null) parsed.setDefinitionFileURL(reader.getUrlForPath( parsed.getDefinition().substring(parsed.getDefinition().indexOf("/") + 1))); descriptions.put(parsed.getName(), parsed); } catch (Exception e) { reader.reportError(specpath, e); } } } for (String specpath : getWebEntrySpecNames(mf, "Web-Composite")) { String specfileContent = reader.readTextFile(specpath, Charset.forName("UTF8")); // TODO: check encoding if (specfileContent != null) { try { WebLayoutSpecification parsed = WebLayoutSpecification.parseLayoutSpec(specfileContent, packageName, reader); parsed.setSpecURL(reader.getUrlForPath(specpath)); if (parsed.getDefinition() != null) parsed.setDefinitionFileURL(reader.getUrlForPath( parsed.getDefinition().substring(parsed.getDefinition().indexOf("/") + 1))); descriptions.put(parsed.getName(), parsed); } catch (Exception e) { reader.reportError(specpath, e); } } } } return new WebComponentPackageSpecification<>(packageName, packageDisplayname, descriptions, mf); } private static List<String> getWebEntrySpecNames(Manifest mf, String attributeName) { List<String> names = new ArrayList<String>(); for (Entry<String, Attributes> entry : mf.getEntries().entrySet()) { if ("true".equalsIgnoreCase((String) entry.getValue().get(new Attributes.Name(attributeName)))) { names.add(entry.getKey()); } } return names; } public void dispose() { reader = null; } public static class JarServletContextReader implements IPackageReader { private final ServletContext servletContext; private final String resourcePath; private Manifest manifest = null; public JarServletContextReader(ServletContext servletContext, String resourcePath) { this.servletContext = servletContext; this.resourcePath = resourcePath; } @Override public String getName() { return resourcePath; } @Override public String getPackageName() { try { String packageName = WebComponentPackage.getPackageName(getManifest()); if (packageName != null) return packageName; } catch (Exception e) { log.error("Error getting package name." + getName(), e); } return FilenameUtils.getBaseName(resourcePath); } @Override public String getPackageDisplayname() { try { String packageDisplayname = WebComponentPackage.getPackageDisplayname(getManifest()); if (packageDisplayname != null) return packageDisplayname; } catch (IOException e) { log.error("Error getting package display name." + getName(), e); } // fall back to symbolic name return getPackageName(); } @Override public Manifest getManifest() throws IOException { if (manifest == null) { try (JarInputStream jarInputStream = new JarInputStream( servletContext.getResourceAsStream(resourcePath))) { manifest = jarInputStream.getManifest(); } } return manifest; } @Override public URL getUrlForPath(String path) throws MalformedURLException { return servletContext.getResource(path.charAt(0) == '/' ? path : '/' + getPackageName() + '/' + path); } @Override public String readTextFile(String path, Charset charset) throws IOException { String pathWithSlashPrefix = path.charAt(0) == '/' ? path : '/' + path; try (InputStream inputStream = servletContext.getResourceAsStream(pathWithSlashPrefix)) { if (inputStream != null) { return IOUtils.toString(inputStream, charset); } } return null; } @Override public void reportError(String specpath, Exception e) { log.error("Cannot parse spec file '" + specpath + "' from package '" + toString() + "'. ", e); } @Override public String toString() { return "JarPackage: " + getName(); } @Override public URL getPackageURL() { try { return servletContext.getResource(resourcePath); } catch (MalformedURLException e) { log.error("MalformedURL", e); } return null; } } public static class JarPackageReader implements IPackageReader { private final File jarFile; public JarPackageReader(File jarFile) { this.jarFile = jarFile; } @Override public String getName() { return jarFile.getAbsolutePath(); } @Override public String getPackageName() { try { String packageName = WebComponentPackage.getPackageName(getManifest()); if (packageName != null) return packageName; } catch (Exception e) { log.error("Error getting package name " + jarFile.getAbsolutePath(), e); } return FilenameUtils.getBaseName(jarFile.getAbsolutePath()); } @Override public String getPackageDisplayname() { try { String packageDisplayname = WebComponentPackage.getPackageDisplayname(getManifest()); if (packageDisplayname != null) return packageDisplayname; } catch (IOException e) { log.error("Error getting package display name " + jarFile.getAbsolutePath(), e); } // fall back to symbolic name return getPackageName(); } @Override public Manifest getManifest() throws IOException { try (JarFile jar = new JarFile(jarFile)) { return jar.getManifest(); } } @Override public URL getUrlForPath(String path) { String pathWithSlashPrefix = path.startsWith("/") ? path : "/" + path; try (JarFile jar = new JarFile(jarFile)) { JarEntry entry = jar.getJarEntry(pathWithSlashPrefix.substring(1)); // strip / if (entry != null) { return new URL("jar:" + jarFile.toURI().toURL() + "!" + pathWithSlashPrefix); } } catch (IOException e) { log.error("Exception in getUrlForPath", e); } return null; } @Override public String readTextFile(String path, Charset charset) throws IOException { try (JarFile jar = new JarFile(jarFile)) { JarEntry entry = jar.getJarEntry(path); if (entry != null) { return IOUtils.toString(jar.getInputStream(entry), charset); } } return null; } @Override public void reportError(String specpath, Exception e) { log.error("Cannot parse spec file '" + specpath + "' from package '" + toString() + "'. ", e); } @Override public String toString() { return "JarPackage: " + jarFile.getAbsolutePath(); } @Override public URL getPackageURL() { try { return jarFile.toURI().toURL(); } catch (MalformedURLException e) { log.error("MalformedURL", e); } return null; } } public static class DirPackageReader implements IPackageReader { private final File dir; public DirPackageReader(File dir) { if (!dir.isDirectory()) throw new IllegalArgumentException( "Non-directory package cannot be read by directory reader: " + dir.getAbsolutePath()); this.dir = dir; } @Override public String getName() { return dir.getAbsolutePath(); } @Override public String getPackageName() { try { String packageName = WebComponentPackage.getPackageName(getManifest()); if (packageName != null) return packageName; } catch (IOException e) { log.error("Error getting package name", e); } return dir.getName(); } @Override public String getPackageDisplayname() { try { String packageDisplayname = WebComponentPackage.getPackageDisplayname(getManifest()); if (packageDisplayname != null) return packageDisplayname; } catch (IOException e) { log.error("Error getting package display name", e); } // fall back to symbolic name return getPackageName(); } @Override public Manifest getManifest() throws IOException { try (InputStream is = new BufferedInputStream( new FileInputStream(new File(dir, "META-INF/MANIFEST.MF")))) { return new Manifest(is); } } @Override public URL getUrlForPath(String path) { File file = new File(dir, path); if (file.exists()) { try { return file.toURI().toURL(); } catch (MalformedURLException e) { log.error("MalformedURLException", e); } } return null; } @Override public String readTextFile(String path, Charset charset) throws IOException { try (InputStream is = new BufferedInputStream(new FileInputStream(new File(dir, path)))) { return IOUtils.toString(is, charset); } } @Override public void reportError(String specpath, Exception e) { log.error("Cannot parse spec file '" + specpath + "' from package '" + toString() + "'. ", e); } @Override public String toString() { return "DirPackage: " + dir.getAbsolutePath(); } @Override public URL getPackageURL() { try { return dir.toURI().toURL(); } catch (MalformedURLException e) { log.error("MalformedURLException", e); } return null; } } public static class WarURLPackageReader implements WebComponentPackage.IPackageReader, WebComponentPackage.ISpecificationFilter { private final URL urlOfManifest; private final String packageName; private final ServletContext servletContext; private HashSet<String> exportedComponents; public WarURLPackageReader(ServletContext servletContext, String packageName) throws MalformedURLException { this.packageName = packageName.endsWith("/") ? packageName : packageName + "/"; this.urlOfManifest = servletContext.getResource(this.packageName + "META-INF/MANIFEST.MF"); this.servletContext = servletContext; if (urlOfManifest == null) { throw new IllegalArgumentException( "Package " + this.packageName + "META-INF/MANIFEST.MF not found in this context"); } try { if (servletContext.getResource("/WEB-INF/exported_components.properties") != null) { InputStream is = servletContext.getResourceAsStream("/WEB-INF/exported_components.properties"); Properties properties = new Properties(); properties.load(is); exportedComponents = new HashSet<String>( Arrays.asList(properties.getProperty("components").split(","))); } } catch (Exception e) { throw new IllegalArgumentException("Exception during init exported_components.properties reading", e); } } @Override public String getName() { return urlOfManifest.toExternalForm(); } @Override public String getPackageName() { return packageName.replaceAll("/", ""); } @Override public String getPackageDisplayname() { try { String packageDisplayname = WebComponentPackage.getPackageDisplayname(getManifest()); if (packageDisplayname != null) return packageDisplayname; } catch (IOException e) { log.error("getting package display name." + getName(), e); } // fall back to symbolic name return getPackageName(); } @Override public Manifest getManifest() throws IOException { try (InputStream is = urlOfManifest.openStream()) { return new Manifest(is); } } @Override public URL getUrlForPath(String path) { try { return servletContext.getResource(packageName + path);// path includes / } catch (MalformedURLException e) { log.error("MalformedURLException", e); return null; } } @Override public String readTextFile(String path, Charset charset) throws IOException { URL url = getUrlForPath(path); if (url == null) return null; try (InputStream is = url.openStream()) { return IOUtils.toString(is, charset); } } @Override public void reportError(String specpath, Exception e) { log.error("Cannot parse spec file '" + specpath + "' from package 'WarReeader[ " + urlOfManifest + " ]'. ", e); } @Override public URL getPackageURL() { return null; } /** * @param spec * @return true if the component is not in the list of the exported components */ @Override public boolean filter(WebComponentSpecification spec) { return exportedComponents != null && !exportedComponents.contains(spec.getName()); } } @Override public String toString() { return "WebComponent-package: " + getPackageName(); } /** * @param manifest * @return */ public static String getPackageName(Manifest manifest) { String bundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLIC_NAME); if (bundleName != null && bundleName.indexOf(';') > 0) { return bundleName.substring(0, bundleName.indexOf(';')).trim(); } return bundleName; } public static String getPackageDisplayname(Manifest manifest) { if (manifest == null) return null; return manifest.getMainAttributes().getValue(BUNDLE_NAME); } public static class DuplicatePackageException extends Exception { public DuplicatePackageException(String message) { super(message); } } }