Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

SHRINKWRAP-289 - Recursive verbose formatter #32

Open
wants to merge 6 commits into from

3 participants

@gpoul

Tested this changeset with ShinkWrap 1.0.0-beta4.

I'm sure it will require additional changes, but let me know what you think.

@ALRubinger
Owner

Why is this isEmpty() check necessary?

there seems to be an edge case in the JVM-provided ZipOutputStream implementation that causes the underlying JdkZipExporterDelegate to bail on zero-length content archives.

https://github.com/gpoul/shrinkwrap/blob/master/impl-base/src/main/java/org/jboss/shrinkwrap/impl/base/exporter/zip/JdkZipExporterDelegate.java#L71

Ah, yes. I've had fun with that before; glad I noted it as such. My memory is just about the worst. :)

Even still, I think we have a gap here. Nowhere in the API docs does it note that "null" is an acceptable return value:

http://docs.jboss.org/shrinkwrap/1.0.0-beta-4/org/jboss/shrinkwrap/api/asset/Asset.html

How about instead: new ByteArrayInputStream(new byte[]{}); ?

Or even better...whatever content represents a valid this.exporter will no content? We could get around the JDK ZIP limitation by using another tool to generate an empty ZIP archive, putting that in src/main/resources, and return a new InputStream with that as the byte content.

I was thinking that "If this returns null, this denotes that the Asset is to be viewed as a logical path (placeholder/directory) only with no backing content." would cover this case just fine... (?)

Hehe, but this isn't a logical path. It's an empty archive. Directories in ShrinkWrap are denoted by Node with getAsset==null. http://docs.jboss.org/shrinkwrap/1.0.0-beta-4/org/jboss/shrinkwrap/api/Node.html#getAsset()

@ALRubinger

I suspect this filename pattern matching isn't sufficient/fully-accurate as a check to figure out if this is a sub-archive. What about instead getting the Asset contents for the lcNodePath and doing type checking for (pathTarget instanceof ArchiveAsset) instead?

To Aslak's point: Yes. This is a new addition to the SW API.

The ArchiveAsset check won't really work given that some of those subarchives are of type ByteArrayAsset. :-/

I thought some more about this and short of basically detecting in the ByteArrayAsset whether this is a sub-archive I'm not sure how to do this. Trying every file to see whether it is an archive just doesn't feel right.

What do you think about combining a list of extensions and a check for ArchiveAsset? Does that make any sense?

Recommend that we don't bother inspecting ByteArrayAssets for content. They were just added as data, thus not "mounted" and unknown. IMO we should avoid guessing or lengthy import detection checks. So I'd b happy with just archive.getAsType(GenericArchive.class, node.getPath()) or an instanceof ArchiveAsset check.

I'm not sure what you mean with the archive.getAsType(...) as that would basically mean "trying" to get the underlying path as an archive on every path, right?

What do you think about using a list of extensions combined with a check for instanceof ArchiveAsset? (if that's not what you meant with your last comment)

getAsType handled the Import part under the hood, based on the default configured importer for that type of Archive, GenericArchive is Zip i assume.. but you're right, you'll still need a extension check to avoid trying everything..

and if the goal is only to print things that are already nested Archives, then that won't really help.

if(node.getAsset() instanceOf ArchiveAsset)
{
GenericArchive nested = archive.getAsType(GenericArchive.class, path)
}

So, would this make sense to you?

``` // Is this a sub-archive? (i.e. is this asset of type ArchiveAsset or does it have a known extension?)
if (nodeAsset.getClass().getName().equals("org.jboss.shrinkwrap.impl.base.asset.ArchiveAsset") ||
lcNodePath.endsWith(".jar") ||
lcNodePath.endsWith(".war") ||
lcNodePath.endsWith(".rar") ||
lcNodePath.endsWith(".sar")) {
InputStream nodeInputStream = nodeAsset.openStream();
// If a valid InputStream is returned, list its contents
if (nodeInputStream != null) {
GenericArchive nodeArchive = ShrinkWrap.create(GenericArchive.class).as(ZipImporter.class).importFrom(nodeInputStream).as(GenericArchive.class);
format(sb, nodeArchive.get(ROOT), subArchiveContext + nodePath);
// remove the last newline
sb.deleteCharAt(sb.length() - 1);
// InputStream is not closed on purpose, as that might fail a subsequent export
} else {
// If there is no valid InputStream, only output the path
sb.append(subArchiveContext);
sb.append(nodePath);
}
} else {
// If this is not a sub-archive, print the node path
sb.append(subArchiveContext);
sb.append(nodePath);
}


Notice that ArchiveAsset is in impl-base and this code is in shrinkwrap-api, so the circular dependency checker hated me when I tried to resolve the dependency for ArchiveAsset, so this is the best I could do for now to illustrate what I'd do, although it doesn't look right.
@ALRubinger
Owner

Nice stuff; welcome to contribution to ShrinkWrap. :) Probably a test case is in order too, to ensure our expected output?

@ALRubinger
Owner

Hmm, when flushing out this to disk, "file" command here doesn't recognize as ZIP.

This one does, though:

byte[] emptyZip = new byte[]
{0x50, 0x4b, 0x03, 0x04, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 94 + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0};

Even better: byte[] emptyZip = new byte[]
{0x50, 0x4b, 0x03, 0x04, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0};

And even still, the above commit assumes that this.exporter is using the JDK ZIP exporter. Maybe instead return this array if (this.exporter instanceof ZipExporter), else let other exporter impls stream out the empty contents (so we don't write an empty ZIP when it should perhaps be an empty TAR.GZ).

Unfortunately the "even better" emptyZip above doesn't work for me. Neither the Windows ZIP-routines nor 7-zip accept this as a valid empty zip.

How about the first one? And your thoughts on my proposal for the this.exporter logic?

this.exporter logic is already in the pull request in a slightly modified form. - the first ZIP also doesn't work. It has the same failure behavior as the second one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
56 api/src/main/java/org/jboss/shrinkwrap/api/formatter/VerboseFormatter.java
@@ -1,6 +1,6 @@
/*
* JBoss, Home of Professional Open Source
- * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * Copyright 2009, 2011, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
@@ -16,8 +16,14 @@
*/
package org.jboss.shrinkwrap.api.formatter;
+import java.io.InputStream;
+
import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.Node;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.Asset;
+import org.jboss.shrinkwrap.api.importer.ZipImporter;
/**
* {@link Formatter} implementation to provide an "ls -l"-esque
@@ -25,6 +31,7 @@
* in sorted order
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
+ * @author <a href="mailto:gerhard.poul@gmail.com">Gerhard Poul</a>
* @version $Revision: $
*/
enum VerboseFormatter implements Formatter {
@@ -60,7 +67,7 @@ public String format(final Archive<?> archive) throws IllegalArgumentException
Node rootNode = archive.get(ROOT);
for (Node child : rootNode.getChildren())
{
- format(sb, child);
+ format(sb, child, "");
}
// remove the last NEWLINE
@@ -69,19 +76,52 @@ public String format(final Archive<?> archive) throws IllegalArgumentException
return sb.toString();
}
- private void format(StringBuilder sb, Node node)
+ private void format(StringBuilder sb, Node node, final String subArchiveContext)
{
- sb.append(node.getPath().get());
- if (node.getAsset() == null)
- {
- sb.append(FormattingConstants.SLASH);
+ String nodePath = node.getPath().get();
+ Asset nodeAsset = node.getAsset();
+
+ // Check whether this is a non-null asset
+ if (nodeAsset != null) {
+ String lcNodePath = nodePath.toLowerCase();
+ // Is this a sub-archive? (i.e. is this asset of type ArchiveAsset or does it have a known extension?)
+ if (nodeAsset.getClass().getName().equals("org.jboss.shrinkwrap.impl.base.asset.ArchiveAsset") ||
+ lcNodePath.endsWith(".jar") ||
+ lcNodePath.endsWith(".war") ||
+ lcNodePath.endsWith(".rar") ||
+ lcNodePath.endsWith(".sar")) {
+ InputStream nodeInputStream = nodeAsset.openStream();
+ // If a valid InputStream is returned, list its contents
+ if (nodeInputStream != null) {
+ GenericArchive nodeArchive = ShrinkWrap.create(GenericArchive.class).as(ZipImporter.class).importFrom(nodeInputStream).as(GenericArchive.class);
+ format(sb, nodeArchive.get(ROOT), subArchiveContext + nodePath);
+ // remove the last newline
+ sb.deleteCharAt(sb.length() - 1);
+ // InputStream is not closed on purpose, as that might fail a subsequent export
+ } else {
+ // If there is no valid InputStream, only output the path
+ sb.append(subArchiveContext);
+ sb.append(nodePath);
+ }
+ } else {
+ // If this is not a sub-archive, print the node path
+ sb.append(subArchiveContext);
+ sb.append(nodePath);
+ }
+ } else {
+ // If this is a null-asset, print the node path
+ sb.append(subArchiveContext);
+ sb.append(nodePath);
+ // Only print a trailing slash if this is not a root node
+ if (!nodePath.equals(ROOT))
+ sb.append(FormattingConstants.SLASH);
}
sb.append(FormattingConstants.NEWLINE);
for (Node child : node.getChildren())
{
- format(sb, child);
+ format(sb, child, subArchiveContext);
}
}
View
17 impl-base/src/main/java/org/jboss/shrinkwrap/impl/base/asset/ArchiveAsset.java
@@ -16,11 +16,13 @@
*/
package org.jboss.shrinkwrap.impl.base.asset;
+import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.exporter.StreamExporter;
+import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.impl.base.Validate;
/**
@@ -82,8 +84,19 @@ public ArchiveAsset(final Archive<?> archive, final Class<? extends StreamExport
@Override
public InputStream openStream()
{
- // Export via the specified exporter
- return this.getArchive().as(this.exporter).exportAsInputStream();
+ // Export via the specified exporter unless archive is empty and exporter is a ZipExporter
+ if (!this.getArchive().getContent().isEmpty() ||
+ !(ZipExporter.class.isAssignableFrom(this.exporter)))
+ return this.getArchive().as(this.exporter).exportAsInputStream();
+ else
+ {
+ // Return an empty ZIP file
+ // ZipOutputStream used by ZipExporter can't create empty ZIP files - Oracle Java bug 6440786
+ byte[] emptyZip = new byte[]{ 0x50, 0x4b, 0x05, 0x06, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
+
+ return new ByteArrayInputStream(emptyZip);
+ }
}
/**
View
123 ...est/java/org/jboss/shrinkwrap/impl/base/formatter/VerboseFormatterSubArchiveTestCase.java
@@ -0,0 +1,123 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2011, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * 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.jboss.shrinkwrap.impl.base.formatter;
+
+import java.util.logging.Logger;
+
+import junit.framework.TestCase;
+
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.formatter.Formatter;
+import org.jboss.shrinkwrap.api.formatter.Formatters;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.jboss.shrinkwrap.impl.base.test.ArchiveTestBase;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Ensures that the {@link Formatters.VERBOSE} is functioning
+ * as expected with sub-archives
+ *
+ * @author <a href="mailto:gerhard.poul@gmail.com">Gerhard Poul</a>
+ * @version $Revision: $
+ */
+@Ignore
+public class VerboseFormatterSubArchiveTestCase {
+
+ //-------------------------------------------------------------------------------------||
+ // Class Members ----------------------------------------------------------------------||
+ //-------------------------------------------------------------------------------------||
+
+ /**
+ * Logger
+ */
+ private static final Logger log = Logger.getLogger(VerboseFormatterSubArchiveTestCase.class.getName());
+
+ /**
+ * Name of the test archive
+ */
+ static final String NAME_ARCHIVE = "testArchive.war";
+
+ /**
+ * Expected output of the formatter
+ */
+ private static final String EXPECTED_OUTPUT = NAME_ARCHIVE
+ + ":\n/WEB-INF/\n"
+ + "/WEB-INF/lib/\n"
+ + "/WEB-INF/lib/library.jar/\n"
+ + "/WEB-INF/lib/library.jar/org/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/base/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/base/formatter/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/base/formatter/FormatterTestBase.class\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/base/test/\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/base/test/ArchiveTestBase.class\n"
+ + "/WEB-INF/lib/library.jar/org/jboss/shrinkwrap/impl/base/test/ArchiveTestBase$1.class";
+
+ //-------------------------------------------------------------------------------------||
+ // Instance Members -------------------------------------------------------------------||
+ //-------------------------------------------------------------------------------------||
+
+ /**
+ * Archive used in testing
+ */
+ private Archive<?> archive;
+
+ //-------------------------------------------------------------------------------------||
+ // Lifecycle --------------------------------------------------------------------------||
+ //-------------------------------------------------------------------------------------||
+
+ /**
+ * Creates the archive used in the test
+ */
+ @Before
+ public void createArchive()
+ {
+ JavaArchive libraryJar = ShrinkWrap.create(JavaArchive.class, "library.jar").addClasses(FormatterTestBase.class,
+ ArchiveTestBase.class);
+
+ archive = ShrinkWrap.create(WebArchive.class, NAME_ARCHIVE)
+ .addAsLibrary(libraryJar);
+ }
+
+ //-------------------------------------------------------------------------------------||
+ // Tests ------------------------------------------------------------------------------||
+ //-------------------------------------------------------------------------------------||
+
+ /**
+ * Ensures that the {@link Formatter} is functioning as
+ * contracted given a test archive
+ */
+ @Test
+ public void testFormatter()
+ {
+ // Format
+ final String formatted = archive.toString(Formatters.VERBOSE);
+
+ // Log out, just so we can see
+ log.info(formatted);
+
+ // Ensure expected form
+ TestCase.assertEquals("Formatter output did not match that expected", EXPECTED_OUTPUT, formatted);
+ }
+
+}
Something went wrong with that request. Please try again.