<netty.version>4.1.5.Final</netty.version>
<slf4j.version>1.7.21</slf4j.version>
<logback.version>1.1.7</logback.version>
+ <aether.version>1.1.0</aether.version>
+ <maven.version>3.1.0</maven.version>
</properties>
<dependencies>
<version>${logback.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.aether</groupId>
+ <artifactId>aether-api</artifactId>
+ <version>${aether.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.aether</groupId>
+ <artifactId>aether-impl</artifactId>
+ <version>${aether.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-aether-provider</artifactId>
+ <version>${maven.version}</version>
+ </dependency>
+
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
}
else if (args[0].equalsIgnoreCase("LOAD") && args.length == 2)
{
- Plugin p = PluginManager.findPlugin(args[1]);
+ String[] s = args[1].split(":");
+ if (s.length != 3)
+ {
+ Acidictive.reply(u, to, c, "Format is groupId:artifactId:version");
+ return;
+ }
+
+ Plugin p = PluginManager.findPlugin(s[1]);
if (p != null)
{
Acidictive.reply(u, to, c, p.getName() + " is already loaded.");
try
{
- p = PluginManager.loadPlugin(args[1]);
+ p = PluginManager.loadPlugin(s[0], s[1], s[2]);
Acidictive.reply(u, to, c, "Plugin " + p.getName() + " successfully loaded");
log.info("PLUGINS LOAD for " + p.getName() + " from " + u.getNick());
}
try
{
- p = PluginManager.loadPlugin(args[1]);
+ p = PluginManager.loadPlugin(p.getArtifact().getGroupId(), p.getArtifact().getArtifactId(), p.getArtifact().getVersion());
Acidictive.reply(u, to, c, "Plugin " + p.getName() + " successfully reloaded");
log.info("PLUGINS RELOAD for " + p.getName() + " from " + u.getNick());
}
import net.rizon.acid.core.User;
import net.rizon.acid.events.EventRehash;
import net.rizon.acid.plugins.PluginManager;
-import net.rizon.acid.util.ClassLoader;
+import net.rizon.acid.plugins.ClassLoader;
/**
* Reloads the configuration files.
Acidictive.conf = c;
Acidictive.loadClients(null, c.clients);
- Acidictive.loader = new ClassLoader(Acidictive.loaderBase);
+ Acidictive.loader = new ClassLoader("net.rizon.acid.commands.");
EventRehash event = new EventRehash();
Acidictive.eventBus.post(event);
public List<Database> database;
public List<Client> clients;
public List<Channel> channel;
- public List<String> plugins;
+ public List<PluginDesc> plugins;
public List<AccessPreset> access_preset;
@Override
--- /dev/null
+package net.rizon.acid.conf;
+
+public class PluginDesc
+{
+ private String groupId;
+ private String artifactId;
+ private String version;
+
+ @Override
+ public String toString()
+ {
+ return "PluginDesc{" + "groupId=" + groupId + ", artifactId=" + artifactId + ", version=" + version + '}';
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId)
+ {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ public void setArtifactId(String artifactId)
+ {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion(String version)
+ {
+ this.version = version;
+ }
+}
channel = future.channel();
future.await();
+
+ if (future.isSuccess() == false)
+ {
+ log.warn("unable to connect", future.cause());
+ }
}
protected static void send(IRCMessage message)
import net.rizon.acid.plugins.Plugin;
import com.google.common.eventbus.EventBus;
import io.netty.util.concurrent.ScheduledFuture;
+import java.net.MalformedURLException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import net.rizon.acid.conf.Client;
import net.rizon.acid.conf.Config;
+import net.rizon.acid.conf.PluginDesc;
import net.rizon.acid.events.EventCTCP;
import net.rizon.acid.events.EventCTCPReply;
import net.rizon.acid.events.EventChanMode;
import net.rizon.acid.plugins.PluginManager;
import net.rizon.acid.sql.SQL;
import net.rizon.acid.util.Blowfish;
-import net.rizon.acid.util.ClassLoader;
+import net.rizon.acid.plugins.ClassLoader;
import net.rizon.acid.util.CloakGenerator;
import net.rizon.acid.util.Util;
import org.slf4j.Logger;
System.exit(-1);
}
- loader = new ClassLoader(loaderBase);
+ try
+ {
+ loader = new ClassLoader("net.rizon.acid.commands.");
+ }
+ catch (MalformedURLException ex)
+ {
+ log.error(null, ex);
+ System.exit(-1);
+ }
+
if (conf.clients != null)
for (Client c : conf.clients)
new AcidUser(null, c);
if (conf.plugins != null)
- for (String s : conf.plugins)
+ for (PluginDesc desc : conf.plugins)
{
try
{
- Plugin p = PluginManager.loadPlugin(s);
+ Plugin p = PluginManager.loadPlugin(desc.getGroupId(), desc.getArtifactId(), desc.getVersion());
+ if (p == null)
+ {
+ log.error("Unable to load plugin {}", desc);
+ System.exit(-1);
+ }
+
log.info("Loaded plugin " + p.getName());
}
catch (Exception ex)
{
- log.error("Unable to load plugin " + s, ex);
+ log.error("Unable to load plugin " + desc, ex);
System.exit(-1);
}
}
package net.rizon.acid.core;
import java.util.HashMap;
-import net.rizon.acid.util.ClassLoader;
+import net.rizon.acid.plugins.ClassLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
try
{
- ClassLoader loader = new ClassLoader("messages");
+ ClassLoader loader = new ClassLoader("net.rizon.acid.messages.");
for (String s : messageClasses)
loader.loadClass(messageBase + "." + s).newInstance();
if (AcidCore.me != null && !AcidCore.me.isBursting() && User.findUser(Acidictive.conf.general.control) != null)
{
+ String routingSpam = Acidictive.conf.getChannelNamed("routing-spam");
+
if (message != null)
{
- Acidictive.privmsg(Acidictive.conf.getChannelNamed("routing-spam"), message);
+ Acidictive.privmsg(routingSpam, message);
+ }
+
+ if (throwable != null && throwable.getMessage() != null)
+ {
+ Acidictive.privmsg(routingSpam, throwable.getMessage());
}
if (stes != null)
{
- Acidictive.privmsg(Acidictive.conf.getChannelNamed("routing-spam"), throwable.getMessage());
for (StackTraceElement ste : stes)
{
- Acidictive.privmsg(Acidictive.conf.getChannelNamed("routing-spam"), ste.toString());
+ Acidictive.privmsg(routingSpam, ste.toString());
}
}
}
--- /dev/null
+/*
+ * Copyright (c) 2016, Adam <Adam@sigterm.info>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Adam <Adam@sigterm.info>
+ * 4. Neither the name of the Adam <Adam@sigterm.info> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Adam <Adam@sigterm.info> ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL Adam <Adam@sigterm.info> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins;
+
+import java.io.File;
+import java.util.List;
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResolutionException;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.eclipse.aether.util.filter.DependencyFilterUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ArtifactResolver
+{
+ private static final Logger logger = LoggerFactory.getLogger(ArtifactResolver.class);
+
+ private final File repository;
+
+ public ArtifactResolver()
+ {
+ repository = new File(System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository");
+ }
+
+ public List<ArtifactResult> resolveArtifacts(Artifact artifact) throws DependencyResolutionException
+ {
+ RepositorySystem system = newRepositorySystem();
+
+ RepositorySystemSession session = newRepositorySystemSession(system);
+
+ DependencyFilter classpathFlter = DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE, JavaScopes.RUNTIME);
+
+ CollectRequest collectRequest = new CollectRequest();
+ collectRequest.setRoot(new Dependency(artifact, JavaScopes.COMPILE));
+
+ DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, classpathFlter);
+
+ List<ArtifactResult> results = system.resolveDependencies(session, dependencyRequest).getArtifactResults();
+ return results;
+ }
+
+ private DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system)
+ {
+ DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
+
+ LocalRepository localRepo = new LocalRepository(repository.getAbsolutePath());
+ session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
+
+ return session;
+ }
+
+ private RepositorySystem newRepositorySystem()
+ {
+ DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
+
+ return locator.getService(RepositorySystem.class);
+ }
+}
--- /dev/null
+package net.rizon.acid.plugins;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+public final class ClassLoader extends URLClassLoader
+{
+ private static final Logger logger = LoggerFactory.getLogger(ClassLoader.class);
+
+ private static final File JAR = new File(ClassLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());
+
+ private final String base;
+
+ public ClassLoader(String base) throws MalformedURLException
+ {
+ this(JAR, base);
+ }
+
+ public ClassLoader(File jar, String base) throws MalformedURLException
+ {
+ // Set parent classloader to null so we can tell whether or not
+ // the class comes from this urlclassloader, or the systems
+ super(new URL[0], null);
+
+ addFile(jar);
+
+ this.base = base;
+ }
+
+ public void addFile(File f) throws MalformedURLException
+ {
+ this.addURL(f.toURI().toURL());
+ }
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException
+ {
+ return loadClassRecurse(name, true);
+ }
+
+ private Class<?> loadClassRecurse(String name, boolean recurse) throws ClassNotFoundException
+ {
+ // Always reload from the urlloader anything under base
+ if (name.startsWith(base) == true)
+ {
+ Class<?> c = super.loadClass(name);
+ logger.debug("Loaded class {} from classloader of base {}", c, base);
+ return c;
+ }
+
+ // Second, see if system classloader can find it, and prefer that
+ // These can never be reloaded
+ try
+ {
+ Class<?> c = getSystemClassLoader().loadClass(name);
+ logger.debug("Loaded class {} from system class loader", c);
+ return c;
+ }
+ catch (ClassNotFoundException ex)
+ {
+ }
+
+ // If system loader can't find it it is either another plugin's class,
+ // a dependency of another plugin, or a dependency of this plugin
+ if (recurse)
+ {
+ // Search the other plugins. If it is a dependency of both the
+ // other plugin and this plugin, we will prefer the other plugin's.
+
+ for (Plugin p : PluginManager.getPlugins())
+ {
+ try
+ {
+ Class<?> c = p.loader.loadClassRecurse(name, false);
+ logger.debug("Loaded class {} from plugin {}", c, p);
+ return c;
+ }
+ catch (ClassNotFoundException ex)
+ {
+ }
+ }
+ }
+
+ // This is a dependency of this plugin
+ Class<?> c = super.loadClass(name);
+ logger.debug("Loaded class {} from url loader", c);
+ return c;
+ }
+}
package net.rizon.acid.plugins;
+import java.util.jar.Manifest;
import net.rizon.acid.core.AcidUser;
import net.rizon.acid.core.User;
-import net.rizon.acid.util.ClassLoader;
+import org.eclipse.aether.artifact.Artifact;
public abstract class Plugin
{
private String name;
private boolean permanent;
public ClassLoader loader; // Loader for this plugin
+ protected Artifact artifact;
+ protected Manifest manifest;
protected void remove()
{
this.name = name;
}
+ public Artifact getArtifact()
+ {
+ return artifact;
+ }
+
public abstract void start() throws Exception;
public abstract void stop();
public void reload() throws Exception { }
package net.rizon.acid.plugins;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.net.URLConnection;
import java.util.LinkedList;
import java.util.List;
+import java.util.jar.Manifest;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class PluginManager
{
- private static final String pluginBase = "net.rizon.acid.plugins.";
+ private static final Logger logger = LoggerFactory.getLogger(PluginManager.class);
private static List<Plugin> plugins = new LinkedList<>();
-
- public static Plugin loadPlugin(String name) throws Exception
+
+ private static Manifest getManifest(File jar) throws IOException
{
- Plugin p = findPlugin(name);
- if (p != null)
+ String manifestPath = "jar:file:" + jar.getAbsolutePath() + "!/META-INF/MANIFEST.MF";
+
+ URL url = new URL(manifestPath);
+
+ URLConnection con = url.openConnection();
+ con.setUseCaches(false);
+
+ try (InputStream manifestInputStream = con.getInputStream())
{
- return p;
+ return new Manifest(manifestInputStream);
}
+ }
+
+ public static Plugin loadPlugin(String groupId, String artifactId, String version) throws Exception
+ {
+ Artifact a = new DefaultArtifact(groupId, artifactId, "", "jar", version);
+
+ Plugin p = findPlugin(a);
+ if (p != null)
+ return p;
- String finalName = name.substring(name.lastIndexOf('.') + 1);
+ ArtifactResolver resolver = new ArtifactResolver();
+ List<ArtifactResult> artifacts = resolver.resolveArtifacts(a);
- net.rizon.acid.util.ClassLoader cl = new net.rizon.acid.util.ClassLoader(name, pluginBase + name);
- Class<?> c = cl.loadClass(pluginBase + name + "." + finalName);
- Constructor<?> con = c.getConstructor();
- p = (Plugin) con.newInstance();
- p.setName(name);
- p.loader = cl;
+ ArtifactResult artifact = artifacts.remove(0);
+ // artifacts contains dependencies now
+
+ logger.debug("Located artifact {}: {}", artifact, artifact.getArtifact().getFile());
+
+ Manifest mf = getManifest(artifact.getArtifact().getFile());
+
+ String mainClass = mf.getMainAttributes().getValue("Main-Class");
+ String exportBase = mf.getMainAttributes().getValue("Export-Base");
+ if (mainClass == null || exportBase == null)
+ {
+ logger.warn("No main class or export base for plugin {}", a);
+ return null;
+ }
+
+ ClassLoader cl = new ClassLoader(artifact.getArtifact().getFile(), exportBase);
try
{
+
+ for (ArtifactResult a2 : artifacts)
+ // XXX I think if this is another plugin it won't work right?
+ cl.addFile(a2.getArtifact().getFile());
+
+ Class<?> c;
+ try
+ {
+ c = cl.loadClass(mainClass);
+ }
+ catch (ClassNotFoundException ex)
+ {
+ logger.warn("unable to load main class", ex);
+ return null;
+ }
+
+ Constructor<?> con = c.getConstructor();
+
+ p = (Plugin) con.newInstance();
+
+ p.artifact = artifact.getArtifact();
+ p.loader = cl;
+ cl = null;
+ p.manifest = mf;
+ p.setName(artifactId);
+
p.start();
+
+ plugins.add(p);
+
+ return p;
}
- catch (Exception ex)
+ finally
{
- throw ex;
+ if (cl != null)
+ cl.close();
}
-
- plugins.add(p);
-
- return p;
}
public static void unload(Plugin plugin)
return null;
}
- public static Plugin findPluginByClassName(String name)
+ public static Plugin findPlugin(Artifact artifact)
{
- if (!name.startsWith(pluginBase))
- {
- return null;
- }
-
- name = name.substring(pluginBase.length());
- int idx = name.indexOf('.');
- if (idx != -1)
- {
- name = name.substring(0, idx);
- }
-
- for (final Plugin p : plugins)
- {
- if (p.getName().equals(name))
- {
+ for (Plugin p : plugins)
+ if (p.artifact.equals(artifact))
return p;
- }
- }
-
return null;
}
}
+++ /dev/null
-package net.rizon.acid.util;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import net.rizon.acid.plugins.Plugin;
-import net.rizon.acid.plugins.PluginManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class ClassLoader extends URLClassLoader
-{
- private static final Logger log = LoggerFactory.getLogger(ClassLoader.class);
-
- public ClassLoader(String plugin, String classname)
- {
- super(new URL[0]);
-
- File jar = null;
-
- if (plugin != null)
- {
- String pluginPath = plugin.replaceAll("\\.", "/");
- String pluginName = plugin.replaceAll("\\.", "-");
-
- File targetFolder = new File(pluginPath + "/target/");
-
- if (!targetFolder.exists())
- targetFolder = new File("../" + pluginPath + "/target/");
-
- /* can't find the directory where we expect it */
- if (!targetFolder.exists())
- {
- log.warn("Unable to find " + pluginPath + "/target/ directory to source the JAR from.");
- return;
- }
-
- File[] targetFiles = targetFolder.listFiles();
-
- for (File loadCandidate : targetFiles)
- {
- String name = loadCandidate.getName();
-
- if (name.endsWith(".jar") && name.startsWith("acid-" + pluginName))
- {
- jar = loadCandidate;
-
- /*
- * `jar-with-dependencies` is the best load candidate, so
- * use it straight away, otherwise look for any other matching jar.
- */
- if (name.endsWith("-jar-with-dependencies.jar"))
- break;
- }
- }
-
- if (jar == null)
- {
- File f = new File(targetFolder, "classes/");
- if (!f.isDirectory())
- {
- log.warn("Unable to locate plugin JAR/class files for " + plugin);
- return;
- }
-
- log.warn("Using classes/ for plugin " + plugin);
- try
- {
- this.addURL(f.toURI().toURL());
- }
- catch (MalformedURLException ex)
- {
- log.warn("Unable to add class URL for " + plugin + " [" + f.toURI() + "]", ex);
- }
- return;
- }
- else
- {
- log.debug("Found load candidate " + jar.getName() + " for plugin `" + plugin + "`");
- }
- }
- else
- {
- /* no plugin so use the main jar */
- jar = new File(ClassLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());
- }
-
- if (jar.exists())
- {
- try
- {
- this.addURL(jar.toURI().toURL());
- }
- catch (MalformedURLException ex)
- {
- log.warn("Unable to add plugin JAR URL for " + plugin + " [" + jar.toURI() + "]", ex);
- }
- }
- else
- {
- log.warn("Unable to locate JAR for plugin " + plugin);
- }
- }
-
- public ClassLoader(String classname)
- {
- this(null, classname);
- }
-
- @Override
- public Class<?> loadClass(String name) throws ClassNotFoundException
- {
- return loadClassRecurse(name, true);
- }
-
- private Class<?> loadClassRecurse(String name, boolean recurse) throws ClassNotFoundException
- {
- try
- {
- return super.loadClass(name);
- }
- catch (ClassNotFoundException ex)
- {
- if (recurse)
- for (Plugin p : PluginManager.getPlugins())
- try
- {
- return p.loader.loadClassRecurse(name, false);
- }
- catch (ClassNotFoundException ex2)
- {
- }
-
- throw ex;
- }
- }
-}
<build>
<plugins>
<plugin>
- <artifactId>maven-assembly-plugin</artifactId>
+ <artifactId>maven-jar-plugin</artifactId>
<configuration>
- <descriptorRefs>
- <descriptorRef>jar-with-dependencies</descriptorRef>
- </descriptorRefs>
+ <archive>
+ <manifest>
+ <mainClass>net.rizon.acid.plugins.pyva.core.core</mainClass>
+ </manifest>
+ <manifestEntries>
+ <Export-Base>net.rizon.acid.plugins.pyva.core.</Export-Base>
+ </manifestEntries>
+ </archive>
</configuration>
- <executions>
- <execution>
- <phase>package</phase>
- <goals>
- <goal>single</goal>
- </goals>
- </execution>
- </executions>
</plugin>
</plugins>
</build>
key_steam: xxx
key_google: xxx
outputlimit: 10
- max_line_length: 500
+ maxlinelength: 500
yt_parse_delay: 180
limitserv:
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>net.rizon.acid.plugins.pyva.pyva.pyva</mainClass>
+ </manifest>
+ <manifestEntries>
+ <Export-Base>net.rizon.acid.plugins.pyva.pyva.</Export-Base>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</project>
<version>${project.version}</version>
</dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>net.rizon.acid.plugins.trapbot.trapbot</mainClass>
+ </manifest>
+ <manifestEntries>
+ <Export-Base>net.rizon.acid.plugins.trapbot.</Export-Base>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</project>