blob: a4479525f30997f0e8333c80ce93b259ae4b01fd [file] [log] [blame]
/*
* 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
*
* https://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.ant.s3;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.PatternSet;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.Union;
import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
import org.apache.tools.ant.util.StringUtils;
import software.amazon.awssdk.services.s3.S3Client;
/**
* {@link ResourceCollection} of {@link ObjectResource}s.
*/
public class ObjectResources extends S3DataType implements ResourceCollection {
/**
* Default delimiter.
*/
public static final String DEFAULT_DELIMITER = "/";
private static <T> Stream<T> sequentialStream(Iterator<T> iterator) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}
private Supplier<S3Client> s3;
private String bucket;
private String delimiter = DEFAULT_DELIMITER;
private Precision as = Precision.object;
private final PatternSet patterns = new PatternSet();
private final List<ResourceSelector> selectors = new ArrayList<>();
private volatile boolean caseSensitive = true;
private volatile Optional<ResourceCollection> resourceCache;
private volatile boolean cache = true;
private volatile boolean includePrefixes;
/**
* Create a new {@link ObjectResources} instance.
*
* @param project
* Ant {@link Project}
*/
public ObjectResources(final Project project) {
super(project);
patterns.setProject(project);
resetResourceCache();
}
/**
* Add the nested {@link Client}.
*
* @param s3
* {@link Client}
*/
public void addConfigured(final Client s3) {
checkChildrenAllowed();
Exceptions.raiseUnless(this.s3 == null, buildException(),
() -> String.format("%s already specified", componentName(Client.class)));
this.s3 = Objects.requireNonNull(s3);
resetResourceCache();
}
/**
* Set {@link Client} by reference.
*
* @param refid
* of {@link Client}
*/
public void setClientRefid(final String refid) {
checkAttributesAllowed();
Exceptions.raiseIf(StringUtils.trimToNull(refid) == null, buildException(),
"@clientrefid must not be null/empty/blank");
addConfigured(getProject().<Client> getReference(refid));
}
/**
* Add a configured {@link ResourceSelector}.
*
* @param selector
* to add
*/
public void addConfigured(ResourceSelector selector) {
checkChildrenAllowed();
if (selector == null) {
return;
}
selectors.add(selector);
resetResourceCache();
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<Resource> iterator() {
if (isReference()) {
getCheckedRef(ObjectResources.class).iterator();
}
if (resourceCache.isPresent()) {
return resourceCache.get().iterator();
}
final S3Finder finder = new S3Finder(getProject(), s3(), require(getBucket(), "@bucket"), as,
require(getDelimiter(), "@delimiter"), patterns, isCaseSensitive(), isIncludePrefixes());
final Optional<Set<ObjectResource>> cacheSet;
if (isCache()) {
cacheSet = Optional.of(new LinkedHashSet<>());
} else {
cacheSet = Optional.empty();
}
final Iterator<Resource> result = new Iterator<Resource>() {
Optional<ObjectResource> next = finder.get();
@Override
public Resource next() {
try {
final ObjectResource e = next.orElseThrow(NoSuchElementException::new);
cacheSet.ifPresent(c -> c.add(e));
return e;
} finally {
next = finder.get();
if (!next.isPresent()) {
cacheSet.ifPresent(c -> {
final Union u = new Union(getProject());
u.addAll(c);
resourceCache = Optional.of(u);
});
}
}
}
@Override
public boolean hasNext() {
return next.isPresent();
}
};
return selectors.isEmpty() ? result : sequentialStream(result).filter(this::isSelected).iterator();
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
if (isReference()) {
return getCheckedRef(ObjectResources.class).size();
}
if (resourceCache.isPresent()) {
return resourceCache.get().size();
}
if (patterns.hasPatterns(getProject()) || !selectors.isEmpty()) {
final AtomicInteger result = new AtomicInteger();
iterator().forEachRemaining(junk -> result.incrementAndGet());
return result.intValue();
}
return new Counter(s3(), require(getBucket(), "@bucket")).getAsInt();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isFilesystemOnly() {
return false;
}
/**
* Get the bucket of this {@link ObjectResources}.
*
* @return {@link String}
*/
public String getBucket() {
return bucket;
}
/**
* Set the bucket of this {@link ObjectResources}.
*
* @param bucket
* where resources exist
*/
public void setBucket(String bucket) {
checkAttributesAllowed();
bucket = StringUtils.trimToNull(bucket);
if (!Objects.equals(bucket, this.bucket)) {
this.bucket = bucket;
resetResourceCache();
}
}
/**
* Get the precision, semantically expressed "as".
*
* @return {@link Precision}
*/
public Precision getAs() {
return as;
}
/**
* Set the precision, semantically expressed "as". This affects only the
* name property and {@link Object#toString()} value of generated
* {@link ObjectResource}s.
*
* @param as
* {@link Precision}
*/
public void setAs(Precision as) {
checkAttributesAllowed();
if (as != this.as) {
this.as = Objects.requireNonNull(as);
resetResourceCache();
}
}
/**
* Learn whether the include/exclude patterns are to be applied with case
* sensitivity.
*
* @return {@code boolean}
*/
public boolean isCaseSensitive() {
return caseSensitive;
}
/**
* Set whether to apply include/exclude patterns with case sensitivity
* (default {@code true}).
*
* @param caseSensitive
* {@code boolean}
*/
public void setCaseSensitive(boolean caseSensitive) {
checkAttributesAllowed();
if (caseSensitive != this.caseSensitive) {
this.caseSensitive = caseSensitive;
resetResourceCache();
}
}
/**
* Get the delimiter of this {@link ObjectResources}.
*
* @return {@link String}
*/
public String getDelimiter() {
return delimiter;
}
/**
* Set the delimiter of this {@link ObjectResources}.
*
* @param delimiter
* empty to use no delimiter
*/
public void setDelimiter(String delimiter) {
checkAttributesAllowed();
delimiter = StringUtils.trimToNull(delimiter);
if (!Objects.equals(delimiter, this.delimiter)) {
this.delimiter = delimiter;
resetResourceCache();
}
}
/**
* Learn whether the discovered {@link ObjectResource}s will be cached.
*
* @return {@code boolean}
*/
public boolean isCache() {
return cache;
}
/**
* Set whether the discovered {@link ObjectResource}s should be cached,
* default {@code true}.
*
* @param cache
* flag
*/
public void setCache(boolean cache) {
this.cache = cache;
if (!cache) {
resetResourceCache();
}
}
/**
* Learn whether to include S3 object prefixes as "directory" type
* {@link Resource}s, default {@code false}.
*
* @return {@code boolean}
*/
public boolean isIncludePrefixes() {
return includePrefixes;
}
/**
* Set whether to include S3 object prefixes as "directory" type
* {@link Resource}s, default {@code false}.
*
* @param includePrefixes
* {@code boolean}
*/
public void setIncludePrefixes(boolean includePrefixes) {
checkAttributesAllowed();
if (includePrefixes != this.includePrefixes) {
this.includePrefixes = includePrefixes;
resetResourceCache();
}
}
/**
* Add a nested {@link PatternSet}.
*
* @param patternSet
* to add
*/
public void addConfigured(PatternSet patternSet) {
checkChildrenAllowed();
patterns.addConfiguredPatternset(patternSet);
resetResourceCache();
}
/**
* Add a nested include.
*
* @return {@link PatternSet.NameEntry}
*/
public PatternSet.NameEntry createInclude() {
checkChildrenAllowed();
resetResourceCache();
return patterns.createInclude();
}
/**
* Add a nested includesfile.
*
* @return {@link PatternSet.PatternFileNameEntry}
*/
public PatternSet.NameEntry createIncludesFile() {
checkChildrenAllowed();
resetResourceCache();
return patterns.createIncludesFile();
}
/**
* Add a nested exclude.
*
* @return {@link PatternSet.NameEntry}
*/
public PatternSet.NameEntry createExclude() {
checkChildrenAllowed();
resetResourceCache();
return patterns.createExclude();
}
/**
* Add a nested excludesfile.
*
* @return {@link PatternSet.PatternFileNameEntry}
*/
public PatternSet.NameEntry createExcludesFile() {
checkChildrenAllowed();
resetResourceCache();
return patterns.createExcludesFile();
}
/**
* Set includes patterns via attribute.
*
* @param includes
* patterns
*/
public void setIncludes(String includes) {
checkAttributesAllowed();
resetResourceCache();
patterns.setIncludes(includes);
}
/**
* Set includes file via attribute.
*
* @param includesFile
* {@link File}
*/
public void setIncludesFile(File includesFile) {
checkAttributesAllowed();
resetResourceCache();
patterns.setIncludesfile(includesFile);
}
/**
* Set excludes patterns via attribute.
*
* @param excludes
* patterns
*/
public void setExcludes(String excludes) {
checkAttributesAllowed();
resetResourceCache();
patterns.setExcludes(excludes);
}
/**
* Set excludes file via attribute.
*
* @param excludesFile
* {@link File}
*/
public void setExcludesFile(File excludesFile) {
checkAttributesAllowed();
resetResourceCache();
patterns.setExcludesfile(excludesFile);
}
private S3Client s3() {
return requireComponent(s3, Client.class).get();
}
private boolean isSelected(Resource resource) {
return selectors.stream().anyMatch(sel -> sel.isSelected(resource));
}
private void resetResourceCache() {
resourceCache = Optional.empty();
}
}