Author: cziegeler Date: Tue Jun 20 18:40:10 2017 New Revision: 1799361 URL: http://svn.apache.org/viewvc?rev=1799361&view=rev Log: Start work on application plugin
Added: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java (with props) Removed: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ModelUtils.java Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Environment.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Preprocessor.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java Added: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java?rev=1799361&view=auto ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java (added) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java Tue Jun 20 18:40:10 2017 @@ -0,0 +1,95 @@ +/* + * 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.feature.maven; + +import org.apache.maven.artifact.Artifact; + +public class ApplicationProjectConfig { + + public static final String CFG_FEATURES = "features"; + + public static final String CFG_TEST_FEATURES = "testFeatures"; + + public static final String DEFAULT_FEATURE_DIR = "src/main/osgi"; + + public static final String DEFAULT_TEST_FEATURE_DIR = "src/test/osgi"; + + private final String featuresDirName; + + private final boolean skipAddDep; + + private final String name; + + private final String scope; + + private final boolean isTest; + + public static ApplicationProjectConfig getMainConfig(final ApplicationProjectInfo info) { + return new ApplicationProjectConfig(info, false); + } + + public static ApplicationProjectConfig getTestConfig(final ApplicationProjectInfo info) { + return new ApplicationProjectConfig(info, true); + } + + private ApplicationProjectConfig(final ApplicationProjectInfo info, final boolean test) { + this.isTest = test; + final String featuresDirCfgName; + final String defaultDir; + final String skipAddDepCfgName; + final String defaultSkipValue; + if ( test ) { + featuresDirCfgName = CFG_TEST_FEATURES; + defaultDir = DEFAULT_TEST_FEATURE_DIR; + skipAddDepCfgName = FeatureProjectConfig.CFG_SKIP_ADD_TEST_FEATURE_DEPENDENCIES; + defaultSkipValue = "true"; + this.scope = Artifact.SCOPE_TEST; + this.name = "test features"; + } else { + featuresDirCfgName = CFG_FEATURES; + defaultDir = DEFAULT_FEATURE_DIR; + skipAddDepCfgName = FeatureProjectConfig.CFG_SKIP_ADD_FEATURE_DEPENDENCIES; + defaultSkipValue = "false"; + this.scope = Artifact.SCOPE_PROVIDED; + this.name = "features"; + } + this.featuresDirName = ProjectHelper.getConfigValue(info.plugin, featuresDirCfgName, defaultDir); + final String skipCfg = ProjectHelper.getConfigValue(info.plugin, skipAddDepCfgName, defaultSkipValue); + this.skipAddDep = "true".equals(skipCfg.toLowerCase()); + } + + public String getName() { + return this.name; + } + + public String getFeatureDir() { + return this.featuresDirName; + } + + public boolean isSkipAddDependencies() { + return this.skipAddDep; + } + + public String getScope() { + return this.scope; + } + + public boolean isTestConfig() { + return this.isTest; + } +} + Propchange: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java ------------------------------------------------------------------------------ svn:keywords = author date id revision rev url Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java Tue Jun 20 18:40:10 2017 @@ -18,12 +18,11 @@ package org.apache.sling.feature.maven; import java.util.List; -import org.apache.sling.feature.Application; import org.apache.sling.feature.Feature; public class ApplicationProjectInfo extends ProjectInfo { - public List<Feature> features; - public Application application; + public List<Feature> features; + public List<Feature> testFeatures; } Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Environment.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Environment.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Environment.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Environment.java Tue Jun 20 18:40:10 2017 @@ -31,6 +31,5 @@ public class Environment { public MavenSession session; public Logger logger; public final Map<String, ProjectInfo> modelProjects = new HashMap<>(); - } Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java Tue Jun 20 18:40:10 2017 @@ -16,13 +16,7 @@ */ package org.apache.sling.feature.maven; -import java.util.ArrayList; -import java.util.List; - import org.apache.maven.artifact.Artifact; -import org.apache.maven.model.Plugin; -import org.apache.maven.model.PluginExecution; -import org.codehaus.plexus.util.xml.Xpp3Dom; public class FeatureProjectConfig { @@ -89,7 +83,7 @@ public class FeatureProjectConfig { skipAddDepCfgName = CFG_SKIP_ADD_TEST_FEATURE_DEPENDENCIES; defaultSkipValue = "true"; this.name = "test feature"; - this.skipAddJar = "true".equals(this.getConfigValue(info.plugin, CFG_SKIP_ADD_JAR_TO_TEST_FEATURE, "true")); + this.skipAddJar = "true".equals(ProjectHelper.getConfigValue(info.plugin, CFG_SKIP_ADD_JAR_TO_TEST_FEATURE, "true")); } else { inlineCfgName = CFG_FEATURE_INLINED; fileCfgName = CFG_TEST_FEATURE_FILE; @@ -98,13 +92,13 @@ public class FeatureProjectConfig { skipAddDepCfgName = CFG_SKIP_ADD_FEATURE_DEPENDENCIES; defaultSkipValue = "false"; this.name = "feature"; - this.skipAddJar = "true".equals(this.getConfigValue(info.plugin, CFG_SKIP_ADD_JAR_TO_FEATURE, "true")); + this.skipAddJar = "true".equals(ProjectHelper.getConfigValue(info.plugin, CFG_SKIP_ADD_JAR_TO_FEATURE, "true")); } - this.inlinedFeature = getConfigValue(info.plugin, inlineCfgName, null); - this.featureFileName = getConfigValue(info.plugin, fileCfgName, defaultFile); - final String skipCfg = this.getConfigValue(info.plugin, skipAddDepCfgName, defaultSkipValue); + this.inlinedFeature = ProjectHelper.getConfigValue(info.plugin, inlineCfgName, null); + this.featureFileName = ProjectHelper.getConfigValue(info.plugin, fileCfgName, defaultFile); + final String skipCfg = ProjectHelper.getConfigValue(info.plugin, skipAddDepCfgName, defaultSkipValue); this.skipAddDep = "true".equals(skipCfg.toLowerCase()); - this.jarStartLevel = getConfigValue(info.plugin, CFG_JAR_START_LEVEL, DEFAULT_START_LEVEL); + this.jarStartLevel = ProjectHelper.getConfigValue(info.plugin, CFG_JAR_START_LEVEL, DEFAULT_START_LEVEL); } public String getName() { @@ -139,36 +133,6 @@ public class FeatureProjectConfig { return this.skipAddJar; } - /** - * Gets a configuration value for a plugin if it is set in the configuration for - * the plugin or any configuration for an execution of the plugin. - * @param plugin Plugin - * @param name Configuration parameter. - * @param defaultValue The default value if no configuration is found. - * @return The default value if nothing is configured, the value otherwise. - * @throws RuntimeException If more than one value is configured - */ - private String getConfigValue(final Plugin plugin, - final String name, - final String defaultValue) { - final List<String> values = new ArrayList<>(); - final Xpp3Dom config = plugin == null ? null : (Xpp3Dom)plugin.getConfiguration(); - final Xpp3Dom globalNode = (config == null ? null : config.getChild(name)); - if ( globalNode != null ) { - values.add(globalNode.getValue()); - } - for(final PluginExecution exec : plugin.getExecutions()) { - final Xpp3Dom cfg = (Xpp3Dom)exec.getConfiguration(); - final Xpp3Dom pluginNode = (cfg == null ? null : cfg.getChild(name)); - if ( pluginNode != null ) { - values.add(pluginNode.getValue()); - } - } - if ( values.size() > 1 ) { - throw new RuntimeException("More than one value configured in plugin (executions) of " - + plugin.getKey() + " for " + name + " : " + values); - } - return values.isEmpty() ? defaultValue : values.get(0); - } + } Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java Tue Jun 20 18:40:10 2017 @@ -20,11 +20,12 @@ import org.apache.sling.feature.Feature; public class FeatureProjectInfo extends ProjectInfo { - public Feature feature; - public Feature assembledFeature; + public boolean done = false; - public Feature testFeature; - public Feature assembledTestFeature; + public Feature feature; + public Feature assembledFeature; + public Feature testFeature; + public Feature assembledTestFeature; } Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Preprocessor.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Preprocessor.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Preprocessor.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Preprocessor.java Tue Jun 20 18:40:10 2017 @@ -21,6 +21,8 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.apache.maven.model.Dependency; @@ -55,6 +57,10 @@ public class Preprocessor { } ProjectHelper.storeProjectInfo(finfo); + } else { + final ApplicationProjectInfo ainfo = (ApplicationProjectInfo)info; + process(env, ainfo, ApplicationProjectConfig.getMainConfig(ainfo)); + process(env, ainfo, ApplicationProjectConfig.getTestConfig(ainfo)); } } } @@ -64,7 +70,7 @@ public class Preprocessor { * * @param env The environment with all maven settings and projects * @param info The project to process. - * @param checkTestFeature {@code true} if the test feature should be processed + * @param config The configuration for the project. */ private void process(final Environment env, final FeatureProjectInfo info, @@ -109,7 +115,7 @@ public class Preprocessor { @Override public Feature provide(final ArtifactId id) { - final Dependency dep = ModelUtils.toDependency(id, config.getScope()); + final Dependency dep = ProjectHelper.toDependency(id, config.getScope()); if ( !config.isSkipAddDependencies() ) { env.logger.debug("- adding feature dependency " + id.toMvnId()); @@ -142,7 +148,7 @@ public class Preprocessor { env.logger.debug("Found external " + id.getType() + " dependency: " + id); // "external" dependency, we can already resolve it - final File featureFile = ModelUtils.getOrResolveArtifact(info.project, env.session, env.artifactHandlerManager, env.resolver, id).getFile(); + final File featureFile = ProjectHelper.getOrResolveArtifact(info.project, env.session, env.artifactHandlerManager, env.resolver, id).getFile(); try (final FileReader r = new FileReader(featureFile)) { return FeatureJSONReader.read(r, featureFile.getAbsolutePath()); } catch ( final IOException ioe) { @@ -167,6 +173,38 @@ public class Preprocessor { } /** + * Process a single application project. + * + * @param env The environment with all maven settings and projects + * @param info The project to process. + * @param config The configuration for the project. + */ + private void process(final Environment env, + final ApplicationProjectInfo info, + final ApplicationProjectConfig config) { + final List<Feature> featureList = new ArrayList<>(); + env.logger.debug("Processing " + config.getName() + " in project " + info.project.getId()); + + // read project features, either inlined or from file + // TODO + + + if ( config.isTestConfig() ) { + info.testFeatures.addAll(featureList); + } else { + info.features.addAll(featureList); + } + + if ( config.isSkipAddDependencies() ) { + env.logger.debug("Not adding artifacts from features as dependencies"); + } else { + for(final Feature feature : featureList) { + addDependenciesFromFeature(env, info, feature, config.getScope()); + } + } + } + + /** * Add all dependencies from the feature * @param env The environment * @param info The project info @@ -175,7 +213,7 @@ public class Preprocessor { */ private void addDependenciesFromFeature( final Environment env, - final FeatureProjectInfo info, + final ProjectInfo info, final Feature assembledFeature, final String scope) { for(final Map.Entry<Integer, org.apache.sling.feature.Artifact> entry : assembledFeature.getBundles()) { @@ -189,7 +227,7 @@ public class Preprocessor { } env.logger.debug("- adding dependency " + a.toMvnId()); - final Dependency dep = ModelUtils.toDependency(a, scope); + final Dependency dep = ProjectHelper.toDependency(a, scope); info.project.getDependencies().add(dep); } for(final Extension ext : assembledFeature.getExtensions()) { @@ -206,7 +244,7 @@ public class Preprocessor { continue; } env.logger.debug("- adding dependency " + a.toMvnId()); - final Dependency dep = ModelUtils.toDependency(a, scope); + final Dependency dep = ProjectHelper.toDependency(a, scope); info.project.getDependencies().add(dep); } } @@ -283,7 +321,7 @@ public class Preprocessor { throw new RuntimeException("Wrong version for feature. It should be " + id.getVersion() + " but is " + feature.getId().getVersion()); } - // post process and retutrn + // post process and return return postProcessReadFeature(feature); } Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java Tue Jun 20 18:40:10 2017 @@ -19,11 +19,26 @@ package org.apache.sling.feature.maven; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.util.HashSet; +import java.util.Set; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; import org.apache.maven.project.MavenProject; +import org.apache.sling.feature.ArtifactId; import org.apache.sling.feature.Feature; import org.apache.sling.feature.json.FeatureJSONReader; import org.apache.sling.feature.json.FeatureJSONWriter; +import org.codehaus.plexus.util.xml.Xpp3Dom; public abstract class ProjectHelper { @@ -122,4 +137,89 @@ public abstract class ProjectHelper { public static Feature getTestFeature(final MavenProject project) { return get(project, RAW_TEST_FEATURE_JSON); } + + /** + * Gets a configuration value for a plugin if it is set in the configuration for + * the plugin or any configuration for an execution of the plugin. + * @param plugin Plugin + * @param name Configuration parameter. + * @param defaultValue The default value if no configuration is found. + * @return The default value if nothing is configured, the value otherwise. + * @throws RuntimeException If more than one value is configured + */ + public static String getConfigValue(final Plugin plugin, + final String name, + final String defaultValue) { + final Set<String> values = new HashSet<>(); + final Xpp3Dom config = plugin == null ? null : (Xpp3Dom)plugin.getConfiguration(); + final Xpp3Dom globalNode = (config == null ? null : config.getChild(name)); + if ( globalNode != null ) { + values.add(globalNode.getValue()); + } + for(final PluginExecution exec : plugin.getExecutions()) { + final Xpp3Dom cfg = (Xpp3Dom)exec.getConfiguration(); + final Xpp3Dom pluginNode = (cfg == null ? null : cfg.getChild(name)); + if ( pluginNode != null ) { + values.add(pluginNode.getValue()); + } + } + if ( values.size() > 1 ) { + throw new RuntimeException("More than one value configured in plugin (executions) of " + + plugin.getKey() + " for " + name + " : " + values); + } + return values.isEmpty() ? defaultValue : values.iterator().next(); + } + + /** + * Get a resolved Artifact from the coordinates provided + * + * @return the artifact, which has been resolved. + */ + public static Artifact getOrResolveArtifact(final MavenProject project, + final MavenSession session, + final ArtifactHandlerManager artifactHandlerManager, + final ArtifactResolver resolver, + final ArtifactId id) { + final Set<Artifact> artifacts = project.getDependencyArtifacts(); + for(final Artifact artifact : artifacts) { + if ( artifact.getGroupId().equals(id.getGroupId()) + && artifact.getArtifactId().equals(id.getArtifactId()) + && artifact.getVersion().equals(id.getVersion()) + && artifact.getType().equals(id.getVersion()) + && ((id.getClassifier() == null && artifact.getClassifier() == null) || (id.getClassifier() != null && id.getClassifier().equals(artifact.getClassifier()))) ) { + return artifact; + } + } + final Artifact prjArtifact = new DefaultArtifact(id.getGroupId(), + id.getArtifactId(), + VersionRange.createFromVersion(id.getVersion()), + Artifact.SCOPE_PROVIDED, + id.getType(), + id.getClassifier(), + artifactHandlerManager.getArtifactHandler(id.getType())); + try { + resolver.resolve(prjArtifact, project.getRemoteArtifactRepositories(), session.getLocalRepository()); + } catch (final ArtifactResolutionException | ArtifactNotFoundException e) { + throw new RuntimeException("Unable to get artifact for " + id.toMvnId(), e); + } + return prjArtifact; + } + + public static String toString(final Dependency d) { + return "Dependency {groupId=" + d.getGroupId() + ", artifactId=" + d.getArtifactId() + ", version=" + d.getVersion() + + (d.getClassifier() != null ? ", classifier=" + d.getClassifier() : "") + + ", type=" + d.getType() + "}"; + } + + public static Dependency toDependency(final ArtifactId id, final String scope) { + final Dependency dep = new Dependency(); + dep.setGroupId(id.getGroupId()); + dep.setArtifactId(id.getArtifactId()); + dep.setVersion(id.getVersion()); + dep.setType(id.getType()); + dep.setClassifier(id.getClassifier()); + dep.setScope(scope); + + return dep; + } } Modified: sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java?rev=1799361&r1=1799360&r2=1799361&view=diff ============================================================================== --- sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java (original) +++ sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java Tue Jun 20 18:40:10 2017 @@ -21,9 +21,7 @@ import org.apache.maven.project.MavenPro public class ProjectInfo { - public MavenProject project; - public Plugin plugin; - public boolean done = false; - + public MavenProject project; + public Plugin plugin; }