diff options
| author | Amadornes <amadornes@gmail.com> | 2018-07-02 07:12:27 +0200 |
|---|---|---|
| committer | Amadornes <amadornes@gmail.com> | 2018-07-02 07:12:27 +0200 |
| commit | 92b2a6669392bd410e9a60749656a49f3e309cc0 (patch) | |
| tree | cae858e8f371018771f84afeef6ae059d8910e0d | |
| parent | 4ffefc5676def5855d91dffa0d089fe5402364fa (diff) | |
| download | Artifactural-92b2a6669392bd410e9a60749656a49f3e309cc0.tar.gz Artifactural-92b2a6669392bd410e9a60749656a49f3e309cc0.tar.bz2 Artifactural-92b2a6669392bd410e9a60749656a49f3e309cc0.zip | |
Initial (untested) version of Artifactural
26 files changed, 1290 insertions, 0 deletions
diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java b/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java new file mode 100644 index 0000000..c82bbd9 --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java @@ -0,0 +1,35 @@ +package com.amadornes.artifactural.api.artifact; + +import com.amadornes.artifactural.api.cache.ArtifactCache; +import com.amadornes.artifactural.api.transform.ArtifactTransformer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.function.UnaryOperator; + +public interface Artifact { + + static Artifact none() { + return Internal.NO_ARTIFACT; + } + + ArtifactIdentifier getIdentifier(); + + ArtifactMetadata getMetadata(); + + ArtifactType getType(); + + Artifact withMetadata(ArtifactMetadata metadata); + + Artifact apply(ArtifactTransformer transformer); + + Artifact cache(ArtifactCache cache, String specifier); + + boolean isPresent(); + + InputStream openStream() throws IOException, MissingArtifactException; + +} diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactIdentifier.java b/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactIdentifier.java new file mode 100644 index 0000000..57c263a --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactIdentifier.java @@ -0,0 +1,61 @@ +package com.amadornes.artifactural.api.artifact; + +import java.util.function.Predicate; + +public interface ArtifactIdentifier { + + static ArtifactIdentifier none() { + return Internal.NO_IDENTIFIER; + } + + String getGroup(); + + String getName(); + + String getVersion(); + + String getClassifier(); + + String getExtension(); + + static Predicate<ArtifactIdentifier> groupMatches(String group) { + return identifier -> identifier.getGroup().matches(group); + } + + static Predicate<ArtifactIdentifier> nameMatches(String name) { + return identifier -> identifier.getName().matches(name); + } + + static Predicate<ArtifactIdentifier> versionMatches(String version) { + return identifier -> identifier.getVersion().matches(version); + } + + static Predicate<ArtifactIdentifier> classifierMatches(String classifier) { + return identifier -> identifier.getClassifier().matches(classifier); + } + + static Predicate<ArtifactIdentifier> extensionMatches(String extension) { + return identifier -> identifier.getExtension().matches(extension); + } + + static Predicate<ArtifactIdentifier> groupEquals(String group) { + return identifier -> identifier.getGroup().equals(group); + } + + static Predicate<ArtifactIdentifier> nameEquals(String name) { + return identifier -> identifier.getName().equals(name); + } + + static Predicate<ArtifactIdentifier> versionEquals(String version) { + return identifier -> identifier.getVersion().equals(version); + } + + static Predicate<ArtifactIdentifier> classifierEquals(String classifier) { + return identifier -> identifier.getClassifier().equals(classifier); + } + + static Predicate<ArtifactIdentifier> extensionEquals(String extension) { + return identifier -> identifier.getExtension().equals(extension); + } + +} diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactMetadata.java b/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactMetadata.java new file mode 100644 index 0000000..1dcd838 --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactMetadata.java @@ -0,0 +1,13 @@ +package com.amadornes.artifactural.api.artifact; + +public interface ArtifactMetadata { + + static ArtifactMetadata empty() { + return (ArtifactMetadata) null; + } + + ArtifactMetadata with(String key, String value); + + String getHash(); + +} diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactType.java b/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactType.java new file mode 100644 index 0000000..1bd0f97 --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/ArtifactType.java @@ -0,0 +1,5 @@ +package com.amadornes.artifactural.api.artifact; + +public enum ArtifactType { + BINARY, SOURCE, OTHER +} diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java b/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java new file mode 100644 index 0000000..41201df --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java @@ -0,0 +1,84 @@ +package com.amadornes.artifactural.api.artifact; + +import com.amadornes.artifactural.api.cache.ArtifactCache; +import com.amadornes.artifactural.api.transform.ArtifactTransformer; + +import java.io.IOException; +import java.io.InputStream; + +class Internal { + + static final ArtifactIdentifier NO_IDENTIFIER = new ArtifactIdentifier() { + + @Override + public String getGroup() { + return "missing"; + } + + @Override + public String getName() { + return "missing"; + } + + @Override + public String getVersion() { + return "0.0.0"; + } + + @Override + public String getClassifier() { + return ""; + } + + @Override + public String getExtension() { + return "missing"; + } + + }; + + static final Artifact NO_ARTIFACT = new Artifact() { + + @Override + public ArtifactIdentifier getIdentifier() { + return ArtifactIdentifier.none(); + } + + @Override + public ArtifactMetadata getMetadata() { + return ArtifactMetadata.empty(); + } + + @Override + public ArtifactType getType() { + return ArtifactType.OTHER; + } + + @Override + public Artifact withMetadata(ArtifactMetadata metadata) { + return this; + } + + @Override + public Artifact apply(ArtifactTransformer transformer) { + return this; + } + + @Override + public Artifact cache(ArtifactCache cache, String specifier) { + return this; + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public InputStream openStream() throws MissingArtifactException { + throw new MissingArtifactException(getIdentifier()); + } + + }; + +} diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java b/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java new file mode 100644 index 0000000..479909c --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java @@ -0,0 +1,9 @@ +package com.amadornes.artifactural.api.artifact; + +public class MissingArtifactException extends RuntimeException { + + public MissingArtifactException(ArtifactIdentifier identifier) { + super("Could not find artifact: " + identifier); + } + +} diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/Streamable.java b/src/api/java/com/amadornes/artifactural/api/artifact/Streamable.java new file mode 100644 index 0000000..1b7de8f --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/artifact/Streamable.java @@ -0,0 +1,11 @@ +package com.amadornes.artifactural.api.artifact; + +import java.io.IOException; +import java.io.InputStream; + +@FunctionalInterface +public interface Streamable { + + InputStream openStream() throws IOException; + +} diff --git a/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java b/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java new file mode 100644 index 0000000..4f86798 --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java @@ -0,0 +1,9 @@ +package com.amadornes.artifactural.api.cache; + +import com.amadornes.artifactural.api.artifact.Artifact; + +public interface ArtifactCache { + + Artifact store(Artifact artifact, String specifier); + +} diff --git a/src/api/java/com/amadornes/artifactural/api/repository/ArtifactProvider.java b/src/api/java/com/amadornes/artifactural/api/repository/ArtifactProvider.java new file mode 100644 index 0000000..c9f21ee --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/repository/ArtifactProvider.java @@ -0,0 +1,28 @@ +package com.amadornes.artifactural.api.repository; + +import com.amadornes.artifactural.api.artifact.Artifact; + +import java.util.function.Function; +import java.util.function.Predicate; + +public interface ArtifactProvider<I> { + + Artifact getArtifact(I info); + + interface Builder<S, I> { + + Builder<S, I> filter(Predicate<I> filter); + + <D> Builder<S, D> mapInfo(Function<I, D> mapper); + + Complete<S, I> provide(ArtifactProvider<I> provider); + + interface Complete<S, I> extends ArtifactProvider<S> { + + Complete<S, I> provide(ArtifactProvider<I> provider); + + } + + } + +} diff --git a/src/api/java/com/amadornes/artifactural/api/repository/Repository.java b/src/api/java/com/amadornes/artifactural/api/repository/Repository.java new file mode 100644 index 0000000..3cf456d --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/repository/Repository.java @@ -0,0 +1,10 @@ +package com.amadornes.artifactural.api.repository; + +import com.amadornes.artifactural.api.artifact.Artifact; +import com.amadornes.artifactural.api.artifact.ArtifactIdentifier; + +public interface Repository { + + Artifact getArtifact(ArtifactIdentifier identifier); + +} diff --git a/src/api/java/com/amadornes/artifactural/api/transform/ArtifactPipeline.java b/src/api/java/com/amadornes/artifactural/api/transform/ArtifactPipeline.java new file mode 100644 index 0000000..014046f --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/transform/ArtifactPipeline.java @@ -0,0 +1,11 @@ +package com.amadornes.artifactural.api.transform; + +import com.amadornes.artifactural.api.cache.ArtifactCache; + +public interface ArtifactPipeline extends ArtifactTransformer { + + ArtifactPipeline apply(ArtifactTransformer transformer); + + ArtifactPipeline cache(ArtifactCache cache, String specifier); + +} diff --git a/src/api/java/com/amadornes/artifactural/api/transform/ArtifactTransformer.java b/src/api/java/com/amadornes/artifactural/api/transform/ArtifactTransformer.java new file mode 100644 index 0000000..e24e790 --- /dev/null +++ b/src/api/java/com/amadornes/artifactural/api/transform/ArtifactTransformer.java @@ -0,0 +1,54 @@ +package com.amadornes.artifactural.api.transform; + +import com.amadornes.artifactural.api.artifact.Artifact; +import com.amadornes.artifactural.api.artifact.ArtifactMetadata; + +import java.util.Set; +import java.util.function.UnaryOperator; + +public interface ArtifactTransformer { + + static ArtifactTransformer of(UnaryOperator<Artifact> operator) { + return new ArtifactTransformer() { + @Override + public Artifact transform(Artifact artifact) { + return operator.apply(artifact); + } + + @Override + public ArtifactMetadata withInfo(ArtifactMetadata metadata) { + return metadata; + } + }; + } + + static ArtifactTransformer exclude(Set<String> filters) { + return (ArtifactTransformer) new Object(); + } + + default boolean appliesTo(Artifact artifact) { + return true; + } + + Artifact transform(Artifact artifact); + + ArtifactMetadata withInfo(ArtifactMetadata metadata); + + default ArtifactTransformer andThen(ArtifactTransformer other) { + ArtifactTransformer current = this; + return new ArtifactTransformer() { + + @Override + public Artifact transform(Artifact artifact) { + return other.transform(current.transform(artifact)); + } + + @Override + public ArtifactMetadata withInfo(ArtifactMetadata metadata) { + return other.withInfo(current.withInfo(metadata)); + } + + }; + } + +} diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java new file mode 100644 index 0000000..12364d7 --- /dev/null +++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java @@ -0,0 +1,83 @@ +package com.amadornes.artifactural.gradle; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.*; +import org.gradle.internal.impldep.com.google.common.cache.Cache; +import org.gradle.internal.impldep.com.google.common.cache.CacheBuilder; + +import java.io.File; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class DependencyResolver { + + private final Project project; + private final AtomicInteger counter = new AtomicInteger(0); + private final Cache<String, CompletableFuture<Set<File>>> resolved = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build(); + + public DependencyResolver(Project project) { + this.project = project; + } + + /** + * Resolves a dependency, downloading the file and its transitives + * if not cached and returns the set of files. + */ + public Set<File> resolveDependency(Dependency dependency) { + if (dependency instanceof FileCollectionDependency) { + return ((FileCollectionDependency) dependency).getFiles().getFiles(); + } + String name = dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion(); + if (dependency instanceof ModuleDependency) { + Set<DependencyArtifact> artifacts = ((ModuleDependency) dependency).getArtifacts(); + if (!artifacts.isEmpty()) { + DependencyArtifact artifact = artifacts.iterator().next(); + name += ":" + artifact.getClassifier() + "@" + artifact.getExtension(); + } + } + + // If this dep is being resolved on another thread, let it do it + CompletableFuture<Set<File>> future; + boolean found = true; + synchronized (resolved) { + future = resolved.getIfPresent(name); + if (future == null) { + resolved.put(name, future = new CompletableFuture<>()); + found = false; + } + } + + if (found) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException ex) { + throw new RuntimeException(ex); + } + } + + // No other thread is resolving this dep and we've claimed it, so let's go! + int currentID = counter.getAndIncrement(); + Configuration cfg = project.getConfigurations().maybeCreate("resolve_dep_" + currentID); + cfg.getDependencies().add(dependency); + Set<File> files = cfg.resolve(); + project.getConfigurations().remove(cfg); + future.complete(files); + return files; + } + + /** + * Resolves a dependency, downloading the file and its transitives + * if not cached and returns the set of files. + */ + public Set<File> resolveDependency(Object dependency, boolean transitive) { + Dependency dep = project.getDependencies().create(dependency); + if (dep instanceof ClientModule) { + dep = ((ClientModule) dep).copy().setTransitive(transitive); + } + return resolveDependency(dep); + } + +} diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java new file mode 100644 index 0000000..2e742ed --- /dev/null +++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java @@ -0,0 +1,26 @@ +package com.amadornes.artifactural.gradle; + +import com.amadornes.artifactural.api.artifact.Artifact; +import com.amadornes.artifactural.api.artifact.ArtifactIdentifier; +import com.amadornes.artifactural.api.artifact.ArtifactType; +import com.amadornes.artifactural.base.artifact.StreamableArtifact; + +import java.io.File; +import java.util.Set; + +public class GradleArtifact { + + public static Artifact maven(DependencyResolver resolver, ArtifactIdentifier identifier, ArtifactType type) { + Set<File> files = resolver.resolveDependency( + identifier.getGroup() + + ":" + identifier.getName() + + ":" + identifier.getVersion() + + (identifier.getClassifier().isEmpty() ? "" : ":" + identifier.getClassifier()) + + (identifier.getExtension().isEmpty() ? "" : "@" + identifier.getExtension()), + false + ); + if (files.isEmpty()) return Artifact.none(); + return StreamableArtifact.ofJar(identifier, type, files.iterator().next()); + } + +} diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java new file mode 100644 index 0000000..a601a1e --- /dev/null +++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java @@ -0,0 +1,303 @@ +package com.amadornes.artifactural.gradle; + +import com.amadornes.artifactural.api.artifact.Artifact; +import com.amadornes.artifactural.api.artifact.ArtifactIdentifier; +import com.amadornes.artifactural.api.repository.Repository; +import com.amadornes.artifactural.base.artifact.ArtifactIdentifierImpl; +import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectCollection; +import org.gradle.api.Transformer; +import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.ArtifactRepository; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleComponentRepository; +import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository; +import org.gradle.api.internal.artifacts.repositories.DefaultMavenArtifactRepository; +import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository; +import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceArtifactResolver; +import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver; +import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver; +import org.gradle.api.resources.ResourceException; +import org.gradle.internal.impldep.com.google.common.io.CountingInputStream; +import org.gradle.internal.impldep.org.apache.commons.io.IOUtils; +import org.gradle.internal.resource.*; +import org.gradle.internal.resource.metadata.DefaultExternalResourceMetaData; +import org.gradle.internal.resource.metadata.ExternalResourceMetaData; + +import javax.annotation.Nullable; +import java.io.*; +import java.net.URI; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GradleRepositoryAdapter extends AbstractArtifactRepository implements ResolutionAwareRepository { + + private static final Pattern URL_PATTERN = Pattern.compile( + "^/(?<group>\\S+(?:/\\S+)*)/(?<name>\\S+)/(?<version>\\S+)/" + + "\\2-\\3(?:-(?<classifier>[^.\\s]+))?\\.(?<extension>\\S+)$"); + + public static GradleRepositoryAdapter add(RepositoryHandler handler, String name, Object url, Repository repository) { + // Create the real maven test we'll be using and remove it + MavenArtifactRepository maven = handler.maven($ -> { + $.setName(name); + $.setUrl(url); + }); + handler.remove(maven); + + // Add our own custom test instead, using the real one in the background + GradleRepositoryAdapter repo = new GradleRepositoryAdapter((DefaultMavenArtifactRepository) maven, repository); + handler.add(repo); + return repo; + } + + private final DefaultMavenArtifactRepository maven; + private final Repository repository; + + private GradleRepositoryAdapter(DefaultMavenArtifactRepository maven, Repository repository) { + this.maven = maven; + this.repository = repository; + } + + @Override + public String getName() { + return maven.getName(); // Proxy to the real repo + } + + @Override + public void setName(String name) { + maven.setName(name); // Proxy to the real repo + } + + @Override + public String getDisplayName() { + return maven.getDisplayName(); // Proxy to the real repo + } + + @Override + public void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container) { + // No-op. The real repo will get this already + } + + @Override + public ConfiguredModuleComponentRepository createResolver() { + MavenResolver resolver = (MavenResolver) maven.createResolver(); + ExternalResourceRepository repo = new StreamingRepo(); + + ExternalResourceArtifactResolver artifactResolver = ReflectionUtils.invoke(resolver, ExternalResourceResolver.class, "createArtifactResolver"); + ReflectionUtils.alter(resolver, "repository", prev -> repo); + ReflectionUtils.alter(resolver, "mavenMetaDataLoader.cacheAwareExternalResourceAccessor.delegate", prev -> repo); + ReflectionUtils.alter(artifactResolver, "repository", prev -> repo); + + return resolver; + } + + private class StreamingRepo implements ExternalResourceRepository { + + @Override + public ExternalResourceRepository withProgressLogging() { + return this; + } + + @Override + public ExternalResource resource(ExternalResourceName name, boolean revalidate) { + URI uri = name.getUri(); + Matcher matcher = URL_PATTERN.matcher(uri.getPath()); + if (!matcher.matches()) return new NullExternalResource(uri); + ArtifactIdentifier identifier = new ArtifactIdentifierImpl( + matcher.group("group").replace('/', '.'), + matcher.group("name"), + matcher.group("version"), + matcher.group("classifier"), + matcher.group("extension")); + Artifact artifact = repository.getArtifact(identifier); + if (!artifact.isPresent()) return new NullExternalResource(uri); + return new CustomArtifactExternalResource(uri, artifact); + } + + @Override + public ExternalResource resource(ExternalResourceName name) { + return resource(name, false); + } + + } + + private class CustomArtifactExternalResource extends AbstractExternalResource { + + private final URI uri; + private final Artifact artifact; + + private CustomArtifactExternalResource(URI uri, Artifact artifact) { + this.uri = uri; + this.artifact = artifact; + } + + @Override + public String getDisplayName() { + return uri.toString(); + } + + @Override + public URI getURI() { + return uri; + } + + @Nullable + @Override + public ExternalResourceReadResult<Void> writeToIfPresent(File file) { + try { + if (!artifact.isPresent()) return null; + FileOutputStream out = new FileOutputStream(file); + ExternalResourceReadResult<Void> result = writeTo(out); + out.close(); + return result; + } catch (IOException ex) { + return null; + } + } + + @Override + public ExternalResourceReadResult<Void> writeTo(OutputStream out) throws ResourceException { + return withContent(in -> { + try { + IOUtils.copy(in, out); + } catch (IOException ex) { + throw ResourceExceptions.failure(uri, "Failed to write resource!", ex); + } + }); + } + + @Override + public ExternalResourceReadResult<Void> withContent(Action<? super InputStream> action) throws ResourceException { + try { + if (!artifact.isPresent()) throw ResourceExceptions.getMissing(uri); + CountingInputStream in = new CountingInputStream(artifact.openStream()); + action.execute(in); + in.close(); + return ExternalResourceReadResult.of(in.getCount()); + } catch (IOException ex) { + throw ResourceExceptions.failure(uri, "Failed to write resource!", ex); + } + } + + @Nullable + @Override + public <T> ExternalResourceReadResult<T> withContentIfPresent(Transformer<? extends T, ? super InputStream> transformer) { + try { + if (!artifact.isPresent()) return null; + CountingInputStream in = new CountingInputStream(artifact.openStream()); + T result = transformer.transform(in); + in.close(); + return ExternalResourceReadResult.of(in.getCount(), result); + } catch (IOException ex) { + return null; + } + } + + @Nullable + @Override + public <T> ExternalResourceReadResult<T> withContentIfPresent(ContentAction<? extends T> contentAction) { + try { + if (!artifact.isPresent()) return null; + CountingInputStream in = new CountingInputStream(artifact.openStream()); + T result = contentAction.execute(in, getMetaData()); + in.close(); + return ExternalResourceReadResult.of(in.getCount(), result); + } catch (IOException ex) { + return null; + } + } + + @Override + public ExternalResourceWriteResult put(ReadableContent readableContent) throws ResourceException { + throw ResourceExceptions.putFailed(uri, null); + } + + @Nullable + @Override + public List<String> list() throws ResourceException { + return null; + } + + @Nullable + @Override + public ExternalResourceMetaData getMetaData() { + try { + if (!artifact.isPresent()) return null; + InputStream stream = artifact.openStream(); + int length = stream.available(); + stream.close(); + return new DefaultExternalResourceMetaData(uri, 0, length); + } catch (IOException ex) { + return null; + } + } |
