/* * Copyright 2016 the original author or authors. * * 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.glowroot.agent.dist; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import javax.annotation.Nullable; import com.google.common.base.Charsets; import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.io.CharStreams; import com.google.common.io.Files; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.glowroot.agent.config.ImmutablePluginDescriptor; import org.glowroot.agent.config.ImmutablePropertyDescriptor; import org.glowroot.agent.config.PluginDescriptor; import org.glowroot.agent.config.PropertyDescriptor; import org.glowroot.agent.config.PropertyValue; import org.glowroot.agent.dist.PluginConfig.PropertyConfig; class PluginJsonTransformer { private final MavenProject project; private final PluginConfig[] pluginConfigs; PluginJsonTransformer(MavenProject project, PluginConfig[] pluginConfigs) { this.project = project; this.pluginConfigs = pluginConfigs; } void execute() throws Exception { Set<Artifact> artifacts = project.getDependencyArtifacts(); createArtifactJar(artifacts); } private void createArtifactJar(Set<Artifact> artifacts) throws Exception { List<PluginDescriptor> pluginDescriptors = getPluginDescriptors(artifacts); validateConfigForDuplicates(); for (PluginConfig pluginConfig : pluginConfigs) { validateConfigItem(pluginDescriptors, pluginConfig); } String pluginsJson = transform(pluginDescriptors); File metaInfDir = new File(project.getBuild().getOutputDirectory(), "META-INF"); File file = new File(metaInfDir, "glowroot.plugins.json"); if (!metaInfDir.exists() && !metaInfDir.mkdirs()) { throw new IOException("Could not create directory: " + metaInfDir.getAbsolutePath()); } Files.write(pluginsJson, file, Charsets.UTF_8); } private static List<PluginDescriptor> getPluginDescriptors(Set<Artifact> artifacts) throws IOException { List<PluginDescriptor> pluginDescriptors = Lists.newArrayList(); for (Artifact artifact : artifacts) { String content = getGlowrootPluginJson(artifact); if (content == null) { continue; } // de-serialization of shaded immutables objects needs to be done using shaded jackson pluginDescriptors.add(PluginDescriptor.readValue(content)); } return pluginDescriptors; } private static @Nullable String getGlowrootPluginJson(Artifact artifact) throws IOException { File artifactFile = artifact.getFile(); if (artifactFile.isDirectory()) { File jsonFile = new File(artifactFile, "META-INF/glowroot.plugin.json"); if (!jsonFile.exists()) { return null; } FileReader reader = new FileReader(jsonFile); try { return CharStreams.toString(reader); } finally { reader.close(); } } JarInputStream jarIn = new JarInputStream(new FileInputStream(artifact.getFile())); try { JarEntry jarEntry; while ((jarEntry = jarIn.getNextJarEntry()) != null) { String name = jarEntry.getName(); if (jarEntry.isDirectory()) { continue; } if (!name.equals("META-INF/glowroot.plugin.json")) { continue; } InputStreamReader in = new InputStreamReader(jarIn, Charsets.UTF_8); String content = CharStreams.toString(in); in.close(); return content; } return null; } finally { jarIn.close(); } } private void validateConfigForDuplicates() throws MojoExecutionException { for (PluginConfig pluginConfig : pluginConfigs) { for (PluginConfig pluginConfig2 : pluginConfigs) { if (pluginConfig != pluginConfig2 && Objects.equal(pluginConfig.getId(), pluginConfig2.getId())) { throw new MojoExecutionException("Found duplicate <plugin> tags" + " (same groupId and artifactId) under <configuration>"); } } } } private void validateConfigItem(List<PluginDescriptor> pluginDescriptors, PluginConfig pluginConfig) throws MojoExecutionException { PluginDescriptor pluginDescriptor = getPlugin(pluginConfig, pluginDescriptors); if (pluginDescriptor == null) { throw new MojoExecutionException("Found <plugin> tag under <configuration> that" + " doesn't have a corresponding dependency in the pom file"); } // check for property names with missing corresponding property name in property descriptor validateProperties(pluginConfig, pluginDescriptor); } private @Nullable PluginDescriptor getPlugin(PluginConfig pluginConfig, List<PluginDescriptor> pluginDescriptors) { for (PluginDescriptor pluginDescriptor : pluginDescriptors) { if (pluginDescriptor.id().equals(pluginConfig.getId())) { return pluginDescriptor; } } return null; } private void validateProperties(PluginConfig pluginConfig, PluginDescriptor pluginDescriptor) throws MojoExecutionException { for (PropertyConfig propertyConfig : pluginConfig.getProperties()) { String propertyName = propertyConfig.getName(); if (propertyName == null || propertyName.length() == 0) { throw new MojoExecutionException("Missing or empty <name> under" + " <configuration>/<plugins>/<plugin>/<properties>/<property>"); } boolean found = false; for (PropertyDescriptor propertyDescriptor : pluginDescriptor.properties()) { if (propertyDescriptor.name().equals(propertyName)) { found = true; break; } } if (!found) { throw new MojoExecutionException("Found <property> tag with name '" + propertyName + "' under <configuration>/<plugins>/<plugin>/<properties> that doesn't" + " have a corresponding property defined in the plugin '" + pluginDescriptor.id() + "'"); } } } private String transform(List<PluginDescriptor> pluginDescriptors) throws Exception { List<PluginDescriptor> updatedPlugins = Lists.newArrayList(); for (PluginDescriptor pluginDescriptor : pluginDescriptors) { ImmutablePluginDescriptor updatedPlugin = ImmutablePluginDescriptor.copyOf(pluginDescriptor) .withProperties(getPropertiesWithOverrides(pluginDescriptor)); updatedPlugins.add(updatedPlugin); } // serialization of shaded immutables objects needs to be done using shaded jackson return PluginDescriptor.writeValue(updatedPlugins); } private @Nullable PluginConfig getPluginConfig(String id) { for (PluginConfig pluginConfig : pluginConfigs) { if (id.equals(pluginConfig.getId())) { return pluginConfig; } } return null; } private List<PropertyDescriptor> getPropertiesWithOverrides(PluginDescriptor pluginDescriptor) throws MojoExecutionException { List<PropertyDescriptor> properties = Lists.newArrayList(); PluginConfig pluginConfig = getPluginConfig(pluginDescriptor.id()); for (PropertyDescriptor property : pluginDescriptor.properties()) { PropertyConfig override = getPropertyConfig(pluginConfig, property.name()); if (override == null) { properties.add(property); continue; } PropertyDescriptorOverlay overlay = new PropertyDescriptorOverlay(property); String overrideDefault = override.getDefault(); String overrideDescription = override.getDescription(); if (overrideDefault != null) { overlay.setDefault(getDefaultFromText(overrideDefault, property.type())); } if (overrideDescription != null) { overlay.setDescription(overrideDescription); } properties.add(overlay.build()); } return properties; } private PropertyValue getDefaultFromText(String text, PropertyValue.PropertyType type) throws MojoExecutionException { switch (type) { case BOOLEAN: return new PropertyValue(Boolean.parseBoolean(text)); case DOUBLE: return new PropertyValue(Double.parseDouble(text)); case STRING: return new PropertyValue(text); default: throw new MojoExecutionException("Unexpected property type: " + type); } } private @Nullable PropertyConfig getPropertyConfig(@Nullable PluginConfig pluginConfig, String name) { if (pluginConfig == null) { return null; } for (PropertyConfig propertyConfig : pluginConfig.getProperties()) { if (name.equals(propertyConfig.getName())) { return propertyConfig; } } return null; } private static class PropertyDescriptorOverlay { private final String name; private final String label; private final PropertyValue.PropertyType type; private @Nullable PropertyValue defaultValue; private String description; private PropertyDescriptorOverlay(PropertyDescriptor base) { name = base.name(); type = base.type(); label = base.label(); defaultValue = base.defaultValue(); description = base.description(); } private void setDefault(PropertyValue defaultValue) { this.defaultValue = defaultValue; } private void setDescription(String description) { this.description = description; } private PropertyDescriptor build() { return ImmutablePropertyDescriptor.builder() .name(name) .label(label) .type(type) .defaultValue(defaultValue) .description(description).build(); } } }