use jaxb to parse plugin descriptor

This commit is contained in:
Sebastian Sdorra
2014-07-13 13:47:35 +02:00
parent 4afc54bb20
commit c5399823a7
11 changed files with 230 additions and 375 deletions

View File

@@ -36,6 +36,7 @@ package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
//~--- JDK imports ------------------------------------------------------------
@@ -165,6 +166,11 @@ public final class Plugin extends ScmModule
*/
public Set<String> getDependencies()
{
if (dependencies == null)
{
dependencies = ImmutableSet.of();
}
return dependencies;
}

View File

@@ -39,17 +39,8 @@ package sonia.scm.plugin;
public interface PluginConstants
{
/** descriptor xml element artifactId */
public static final String EL_ARTIFACTID = "artifactId";
/** descriptor xml element dependency */
public static final String EL_DEPENDENCY = "dependency";
/** descriptor xml element groupId */
public static final String EL_GROUPID = "groupId";
/** descriptor xml element version */
public static final String EL_VERSION = "version";
/** Field description */
public static final String ID_DELIMITER = ":";
/** checksum file */
public static final String FILE_CHECKSUM = "checksum";

View File

@@ -33,38 +33,53 @@ package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
/**
* Id of a plugin. The id of a plugin consists of the groupId, artifactId and
* the version of the plugin.
* Util methods to handle plugins.
*
* @author Sebastian Sdorra
* @since 2.0.0
*/
public final class PluginId
public final class Plugins
{
/** Field description */
private static final String DELIMITER = ":";
private static JAXBContext context;
//~--- static initializers --------------------------------------------------
static
{
try
{
context = JAXBContext.newInstance(Plugin.class, ScmModule.class);
}
catch (JAXBException ex)
{
throw new PluginException("could not create jaxb context", ex);
}
}
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param groupId
* @param artifactId
* @param version
*/
public PluginId(String groupId, String artifactId, String version)
{
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
private Plugins() {}
//~--- methods --------------------------------------------------------------
@@ -72,119 +87,42 @@ public final class PluginId
* Method description
*
*
* @param obj
* @param path
*
* @return
*/
@Override
public boolean equals(Object obj)
public static Plugin parsePluginDescriptor(Path path)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final PluginId other = (PluginId) obj;
return Objects.equal(this.groupId, other.groupId)
&& Objects.equal(this.artifactId, other.artifactId)
&& Objects.equal(this.version, other.version);
return parsePluginDescriptor(Files.asByteSource(path.toFile()));
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(groupId, artifactId, version);
}
/**
* Method description
*
* @param data
*
* @return
*/
@Override
public String toString()
public static Plugin parsePluginDescriptor(ByteSource data)
{
return Joiner.on(DELIMITER).join(groupId, artifactId, version);
Preconditions.checkNotNull(data, "data parameter is required");
Plugin plugin;
try (InputStream stream = data.openStream())
{
plugin = (Plugin) context.createUnmarshaller().unmarshal(stream);
}
catch (JAXBException ex)
{
throw new PluginLoadException("could not parse plugin descriptor", ex);
}
catch (IOException ex)
{
throw new PluginLoadException("could not read plugin descriptor", ex);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getArtifactId()
{
return artifactId;
return plugin;
}
/**
* Method description
*
*
* @return
*/
public String getGroupId()
{
return groupId;
}
/**
* Method description
*
*
* @return
*/
public String getId()
{
return toString();
}
/**
* Method description
*
*
* @return
*/
public String getIdWithoutVersion()
{
return Joiner.on(DELIMITER).join(groupId, artifactId);
}
/**
* Method description
*
*
* @return
*/
public String getVersion()
{
return version;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final String artifactId;
/** Field description */
private final String groupId;
/** Field description */
private final String version;
}

View File

@@ -35,18 +35,12 @@ package sonia.scm.plugin;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import sonia.scm.util.IOUtil;
import sonia.scm.util.XmlUtil;
//~--- JDK imports ------------------------------------------------------------
@@ -60,12 +54,9 @@ import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.ParserConfigurationException;
/**
* Smp plugin archive.
*
@@ -74,7 +65,6 @@ import javax.xml.parsers.ParserConfigurationException;
*/
public final class SmpArchive
{
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
@@ -163,30 +153,6 @@ public final class SmpArchive
return path;
}
/**
* Method description
*
*
* @param map
* @param key
* @param <K>
* @param <V>
*
* @return
*/
private static <K, V> V getSingleValue(Multimap<K, V> map, K key)
{
V value = null;
Collection<V> values = map.get(key);
if (!values.isEmpty())
{
value = values.iterator().next();
}
return value;
}
//~--- methods --------------------------------------------------------------
/**
@@ -240,39 +206,39 @@ public final class SmpArchive
*
* @throws IOException
*/
public Document getDescriptorDocument() throws IOException
public Plugin getPlugin() throws IOException
{
if (descriptorDocument == null)
if (plugin == null)
{
try
plugin = createPlugin();
PluginInformation info = plugin.getInformation();
if (info == null)
{
descriptorDocument = createDescriptorDocument();
throw new PluginException("could not find information section");
}
catch (ParserConfigurationException | SAXException ex)
if (Strings.isNullOrEmpty(info.getGroupId()))
{
throw new PluginException("could not parse descriptor", ex);
throw new PluginException(
"could not find groupId in plugin descriptor");
}
if (Strings.isNullOrEmpty(info.getArtifactId()))
{
throw new PluginException(
"could not find artifactId in plugin descriptor");
}
if (Strings.isNullOrEmpty(info.getVersion()))
{
throw new PluginException(
"could not find version in plugin descriptor");
}
}
return descriptorDocument;
}
/**
* Method description
*
*
* @return
*
* @throws IOException
*/
public PluginId getPluginId() throws IOException
{
if (pluginId == null)
{
pluginId = createPluginId();
}
return pluginId;
return plugin;
}
//~--- methods --------------------------------------------------------------
@@ -281,17 +247,13 @@ public final class SmpArchive
* Method description
*
*
*
* @return
*
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
*/
private Document createDescriptorDocument()
throws IOException, ParserConfigurationException, SAXException
private Plugin createPlugin() throws IOException
{
Document doc = null;
Plugin p = null;
NonClosingZipInputStream zis = null;
try
@@ -304,7 +266,7 @@ public final class SmpArchive
{
if (PluginConstants.PATH_DESCRIPTOR.equals(getPath(entry)))
{
doc = XmlUtil.createDocument(zis);
p = Plugins.parsePluginDescriptor(new InputStreamByteSource(zis));
}
entry = zis.getNextEntry();
@@ -320,56 +282,12 @@ public final class SmpArchive
}
}
if (doc == null)
if (p == null)
{
throw new PluginException("could not find descritor");
throw new PluginLoadException("could not find plugin descriptor");
}
return doc;
}
/**
* Method description
*
*
* @return
*
* @throws IOException
*/
private PluginId createPluginId() throws IOException
{
//J-
Multimap<String, String> entries = XmlUtil.values(
getDescriptorDocument(),
PluginConstants.EL_GROUPID,
PluginConstants.EL_ARTIFACTID,
PluginConstants.EL_VERSION
);
//J+
String groupId = getSingleValue(entries, PluginConstants.EL_GROUPID);
if (Strings.isNullOrEmpty(groupId))
{
throw new PluginException("could not find groupId in plugin descriptor");
}
String artifactId = getSingleValue(entries, PluginConstants.EL_ARTIFACTID);
if (Strings.isNullOrEmpty(artifactId))
{
throw new PluginException(
"could not find artifactId in plugin descriptor ");
}
String version = getSingleValue(entries, PluginConstants.EL_VERSION);
if (Strings.isNullOrEmpty(version))
{
throw new PluginException("could not find version in plugin descriptor ");
}
return new PluginId(groupId, artifactId, version);
return p;
}
/**
@@ -400,6 +318,50 @@ public final class SmpArchive
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 14/07/13
* @author Enter your name here...
*/
private static class InputStreamByteSource extends ByteSource
{
/**
* Constructs ...
*
*
* @param input
*/
public InputStreamByteSource(InputStream input)
{
this.input = input;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @return
*
* @throws IOException
*/
@Override
public InputStream openStream() throws IOException
{
return input;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final InputStream input;
}
/**
* Class description
*
@@ -456,8 +418,5 @@ public final class SmpArchive
private final ByteSource archive;
/** Field description */
private Document descriptorDocument;
/** Field description */
private PluginId pluginId;
private Plugin plugin;
}

View File

@@ -110,31 +110,20 @@ public class SmpArchiveTest
* @throws IOException
*/
@Test
public void testGetDescriptorDocument() throws IOException
public void testGetPlugin() throws IOException
{
File archive = createArchive("sonia.sample", "sample", "1.0");
Document doc = SmpArchive.create(archive).getDescriptorDocument();
Plugin plugin = SmpArchive.create(archive).getPlugin();
assertNotNull(doc);
assertEquals("plugin", doc.getDocumentElement().getNodeName());
}
assertNotNull(plugin);
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testGetPluginId() throws IOException
{
File archive = createArchive("sonia.sample", "sample", "1.0");
PluginId pluginId = SmpArchive.create(archive).getPluginId();
PluginInformation info = plugin.getInformation();
assertNotNull(pluginId);
assertEquals("sonia.sample", pluginId.getGroupId());
assertEquals("sample", pluginId.getArtifactId());
assertEquals("1.0", pluginId.getVersion());
assertNotNull(info);
assertEquals("sonia.sample", info.getGroupId());
assertEquals("sample", info.getArtifactId());
assertEquals("1.0", info.getVersion());
}
/**
@@ -147,7 +136,7 @@ public class SmpArchiveTest
{
File archive = createArchive("sonia.sample", null, "1.0");
SmpArchive.create(archive).getPluginId();
SmpArchive.create(archive).getPlugin();
}
/**
@@ -160,7 +149,7 @@ public class SmpArchiveTest
{
File archive = createArchive(null, "sample", "1.0");
SmpArchive.create(archive).getPluginId();
SmpArchive.create(archive).getPlugin();
}
/**
@@ -173,7 +162,7 @@ public class SmpArchiveTest
{
File archive = createArchive("sonia.sample", "sample", null);
SmpArchive.create(archive).getPluginId();
SmpArchive.create(archive).getPlugin();
}
/**

View File

@@ -42,11 +42,11 @@ import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.ScmContextListener;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginException;
import sonia.scm.plugin.PluginId;
import sonia.scm.plugin.PluginLoadException;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.plugin.Plugins;
import sonia.scm.plugin.PluginsInternal;
import sonia.scm.plugin.SmpArchive;
import sonia.scm.util.ClassLoaders;
import sonia.scm.util.IOUtil;
@@ -152,7 +152,7 @@ public class BootstrapContextListener implements ServletContextListener
ClassLoader cl =
ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
Set<PluginWrapper> plugins = Plugins.collectPlugins(cl,
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl,
pluginDirectory.toPath());
contextListener = new ScmContextListener(cl, plugins);
@@ -182,23 +182,24 @@ public class BootstrapContextListener implements ServletContextListener
{
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
SmpArchive archive = SmpArchive.create(url);
PluginId id = archive.getPluginId();
Plugin plugin = archive.getPlugin();
File directory = Plugins.createPluginDirectory(pluginDirectory, id);
File checksumFile = Plugins.getChecksumFile(directory);
File directory = PluginsInternal.createPluginDirectory(pluginDirectory,
plugin);
File checksumFile = PluginsInternal.getChecksumFile(directory);
if (!directory.exists())
{
logger.warn("install plugin {}", id);
Plugins.extract(archive, entry.getChecksum(), directory, checksumFile,
true);
logger.warn("install plugin {}", plugin.getInformation().getId());
PluginsInternal.extract(archive, entry.getChecksum(), directory,
checksumFile, true);
}
else if (!checksumFile.exists())
{
logger.warn("plugin directory {} exists without checksum file.",
directory);
Plugins.extract(archive, entry.getChecksum(), directory, checksumFile,
true);
PluginsInternal.extract(archive, entry.getChecksum(), directory,
checksumFile, true);
}
else
{
@@ -206,13 +207,15 @@ public class BootstrapContextListener implements ServletContextListener
if (checksum.equals(entry.getChecksum()))
{
logger.debug("plugin {} is up to date", id);
logger.debug("plugin {} is up to date",
plugin.getInformation().getId());
}
else
{
logger.warn("checksum mismatch of pluing {}, start update", id);
Plugins.extract(archive, entry.getChecksum(), directory, checksumFile,
true);
logger.warn("checksum mismatch of pluing {}, start update",
plugin.getInformation().getId());
PluginsInternal.extract(archive, entry.getChecksum(), directory,
checksumFile, true);
}
}
}

View File

@@ -101,7 +101,7 @@ public class DefaultPluginLoader implements PluginLoader
Builder<Plugin> builder = ImmutableSet.builder();
builder.addAll(ips);
builder.addAll(Plugins.unwrap(wrappedPlugins));
builder.addAll(PluginsInternal.unwrap(wrappedPlugins));
plugins = builder.build();
appendExtensions(multiple, single, extensions, modules);

View File

@@ -34,20 +34,14 @@ package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
import sonia.scm.util.Util;
import sonia.scm.util.XmlUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Set;
/**
*
@@ -56,8 +50,6 @@ import java.util.Collection;
public final class ExplodedSmp implements Comparable<ExplodedSmp>
{
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
@@ -65,12 +57,12 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
* @param path
* @param pluginId
* @param dependencies
* @param plugin
*/
ExplodedSmp(Path path, PluginId pluginId, Collection<String> dependencies)
ExplodedSmp(Path path, Plugin plugin)
{
this.path = path;
this.pluginId = pluginId;
this.dependencies = dependencies;
this.plugin = plugin;
}
//~--- methods --------------------------------------------------------------
@@ -87,35 +79,9 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
*/
public static ExplodedSmp create(Path directory) throws IOException
{
ExplodedSmp smp;
Path desc = directory.resolve(PluginConstants.FILE_DESCRIPTOR);
Path descriptor = directory.resolve(PluginConstants.FILE_DESCRIPTOR);
try (InputStream in = Files.newInputStream(descriptor))
{
//J-
Multimap<String,String> values = XmlUtil.values(in,
PluginConstants.EL_GROUPID,
PluginConstants.EL_ARTIFACTID,
PluginConstants.EL_VERSION,
PluginConstants.EL_DEPENDENCY
);
PluginId pluginId = new PluginId(
Util.getFirst(values, PluginConstants.EL_GROUPID),
Util.getFirst(values, PluginConstants.EL_ARTIFACTID),
Util.getFirst(values, PluginConstants.EL_VERSION)
);
smp = new ExplodedSmp(
directory,
pluginId,
values.get(PluginConstants.EL_DEPENDENCY)
);
//J+
}
return smp;
return new ExplodedSmp(directory, Plugins.parsePluginDescriptor(desc));
}
/**
@@ -131,24 +97,27 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
{
int result;
if (dependencies.isEmpty() && o.dependencies.isEmpty())
Set<String> depends = plugin.getDependencies();
Set<String> odepends = o.plugin.getDependencies();
if (depends.isEmpty() && odepends.isEmpty())
{
result = 0;
}
else if (dependencies.isEmpty() &&!o.dependencies.isEmpty())
else if (depends.isEmpty() &&!odepends.isEmpty())
{
result = -1;
}
else if (!dependencies.isEmpty() && o.dependencies.isEmpty())
else if (!depends.isEmpty() && odepends.isEmpty())
{
result = 1;
}
else
{
String id = pluginId.getIdWithoutVersion();
String oid = o.pluginId.getIdWithoutVersion();
String id = plugin.getInformation().getId(false);
String oid = o.plugin.getInformation().getId(false);
if (dependencies.contains(oid) && o.dependencies.contains(id))
if (depends.contains(oid) && odepends.contains(id))
{
StringBuilder b = new StringBuilder("circular dependency detected: ");
@@ -157,11 +126,11 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
throw new PluginCircularDependencyException(b.toString());
}
else if (dependencies.contains(oid))
else if (depends.contains(oid))
{
result = 999;
}
else if (o.dependencies.contains(id))
else if (odepends.contains(id))
{
result = -999;
}
@@ -176,17 +145,6 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Collection<String> getDependencies()
{
return dependencies;
}
/**
* Method description
*
@@ -204,9 +162,9 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
*
* @return
*/
public PluginId getPluginId()
public Plugin getPlugin()
{
return pluginId;
return plugin;
}
//~--- inner classes --------------------------------------------------------
@@ -246,12 +204,9 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
//~--- fields ---------------------------------------------------------------
/** Field description */
private final Collection<String> dependencies;
/** Field description */
private final Path path;
/** Field description */
private final PluginId pluginId;
private final Plugin plugin;
}

View File

@@ -451,16 +451,17 @@ public final class PluginProcessor
SmpArchive smp = SmpArchive.create(archive);
logger.debug("extract plugin {}", smp.getPluginId());
logger.debug("extract plugin {}", smp.getPlugin());
File directory = Plugins.createPluginDirectory(pluginDirectory.toFile(),
smp.getPluginId());
File directory =
PluginsInternal.createPluginDirectory(pluginDirectory.toFile(),
smp.getPlugin());
String checksum = com.google.common.io.Files.hash(archiveFile,
Hashing.sha256()).toString();
File checksumFile = Plugins.getChecksumFile(directory);
File checksumFile = PluginsInternal.getChecksumFile(directory);
Plugins.extract(smp, checksum, directory, checksumFile, false);
PluginsInternal.extract(smp, checksum, directory, checksumFile, false);
moveArchive(archive);
}
}

View File

@@ -56,13 +56,14 @@ import java.util.Set;
*
* @author Sebastian Sdorra
*/
public final class Plugins
public final class PluginsInternal
{
/**
* the logger for Plugins
* the logger for PluginsInternal
*/
private static final Logger logger = LoggerFactory.getLogger(Plugins.class);
private static final Logger logger =
LoggerFactory.getLogger(PluginsInternal.class);
//~--- constructors ---------------------------------------------------------
@@ -70,7 +71,7 @@ public final class Plugins
* Constructs ...
*
*/
private Plugins() {}
private PluginsInternal() {}
//~--- methods --------------------------------------------------------------
@@ -99,13 +100,15 @@ public final class Plugins
*
*
* @param parent
* @param id
* @param plugin
*
* @return
*/
public static File createPluginDirectory(File parent, PluginId id)
public static File createPluginDirectory(File parent, Plugin plugin)
{
return new File(new File(parent, id.getGroupId()), id.getArtifactId());
PluginInformation info = plugin.getInformation();
return new File(new File(parent, info.getGroupId()), info.getArtifactId());
}
/**
@@ -127,13 +130,14 @@ public final class Plugins
if (directory.exists())
{
logger.debug("delete directory {} for plugin extraction",
archive.getPluginId());
archive.getPlugin().getInformation().getId(false));
IOUtil.delete(directory);
}
IOUtil.mkdirs(directory);
logger.debug("extract plugin {}", archive.getPluginId());
logger.debug("extract plugin {}",
archive.getPlugin().getInformation().getId(false));
archive.extract(directory);
Files.write(checksum, checksumFile, Charsets.UTF_8);

View File

@@ -37,6 +37,8 @@ import com.google.common.collect.Lists;
import org.junit.Test;
import org.mockito.internal.util.collections.Sets;
import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
@@ -127,8 +129,15 @@ public class ExplodedSmpTest
private ExplodedSmp create(String groupId, String artifactId, String version,
String... dependencies)
{
return new ExplodedSmp(null, new PluginId(groupId, artifactId, version),
Lists.newArrayList(dependencies));
PluginInformation info = new PluginInformation();
info.setGroupId(groupId);
info.setArtifactId(artifactId);
info.setVersion(version);
Plugin plugin = new Plugin(info, null, null, Sets.newSet(dependencies));
return new ExplodedSmp(null, plugin);
}
/**
@@ -160,6 +169,6 @@ public class ExplodedSmpTest
*/
private void is(List<ExplodedSmp> es, int p, String a)
{
assertEquals(a, es.get(p).getPluginId().getArtifactId());
assertEquals(a, es.get(p).getPlugin().getInformation().getArtifactId());
}
}