blob: 7762c41659756dbd403897db754d9c01e82af37e [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
*
* 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.apache.freemarker.generator.base.datasource;
import org.apache.freemarker.generator.base.util.ClosableUtils;
import org.apache.freemarker.generator.base.util.StringUtils;
import org.apache.freemarker.generator.base.util.Validate;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.function.Function.identity;
/**
* Container for data sources with a couple of convenience functions to select
* a subset of data sources.
*/
public class DataSources implements Closeable {
/** The underlying list of data sources */
private final List<DataSource> dataSources;
/** Map of named data sources */
private final Map<String, DataSource> dataSourcesMap;
public DataSources(Collection<DataSource> dataSources) {
Validate.notNull(dataSources, "dataSources must not be null");
this.dataSources = Collections.unmodifiableList(new ArrayList<>(dataSources));
this.dataSourcesMap = Collections.unmodifiableMap(dataSourcesMap(dataSources));
}
/**
* Get the names of all data sources.
*
* @return list of data source names
*/
public List<String> getNames() {
return new ArrayList<>(dataSourcesMap.keySet());
}
/**
* Get a list of distinct metadata values for all data sources.
*
* @param key key of the metadata part
* @return list of metadata values
*/
public List<String> getMetadata(String key) {
return dataSources.stream()
.map(ds -> ds.getMetadata(key))
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
}
/**
* Get a list of distinct group names of all data sources.
*
* @return list of group names
*/
public List<String> getGroups() {
return dataSources.stream()
.map(DataSource::getGroup)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
}
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return dataSources.size();
}
/**
* Returns <tt>true</tt> if this list contains no elements.
*
* @return <tt>true</tt> if this list contains no elements
*/
public boolean isEmpty() {
return dataSources.isEmpty();
}
/**
* Get an array representation of the underlying data sources.
*
* @return array of data sources
*/
public DataSource[] toArray() {
return dataSources.toArray(new DataSource[0]);
}
/**
* Get a list representation of the underlying data sources.
*
* @return list of data sources
*/
public List<DataSource> toList() {
return dataSources;
}
/**
* Get a map representation of the underlying data sources.
* In <code>freemarker-cli</code> the map is also used to
* iterate over data source, so we need to return a
* <code>LinkedHashMap</code>.
* <p>
* The implementation also throws as <code>IllegalStateException</code>
* when finding duplicate keys to avoid "losing" data sources.
*
* @return linked hasp map of data sources
*/
public Map<String, DataSource> toMap() {
return dataSourcesMap;
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
*/
public DataSource get(int index) {
return dataSources.get(index);
}
/**
* Get exactly one data source. If not exactly one data source
* is found an exception is thrown.
*
* @param name name of the data source
* @return data source
*/
public DataSource get(String name) {
final List<DataSource> list = findByName(name);
if (list.isEmpty()) {
throw new IllegalArgumentException("Data source not found : " + name);
}
if (list.size() > 1) {
throw new IllegalArgumentException("More than one data source found : " + name);
}
return list.get(0);
}
/**
* Find data sources based on their name using a wildcard string.
*
* @param wildcard the wildcard string to match against
* @return list of matching data sources
* @see <a href="https://commons.apache.org/proper/commons-io/javadocs/api-2.7/org/apache/commons/io/FilenameUtils.html#wildcardMatch-java.lang.String-java.lang.String-">Apache Commons IO</a>
*/
public List<DataSource> findByName(String wildcard) {
return find(DataSource.METADATA_NAME, wildcard);
}
/**
* Find <code>DataSources</code> based on their metadata key and wildcard string.
*
* @param key metadata key to match
* @param wildcard the wildcard string to match against
* @return list of matching data sources
* @see <a href="https://commons.apache.org/proper/commons-io/javadocs/api-2.7/org/apache/commons/io/FilenameUtils.html#wildcardMatch-java.lang.String-java.lang.String-">Apache Commons IO</a>
*/
public List<DataSource> find(String key, String wildcard) {
return dataSources.stream()
.filter(dataSource -> dataSource.match(key, wildcard))
.collect(Collectors.toList());
}
/**
* Create a new <code>DataSources</code> instance consisting of
* data sources matching the filter.
*
* @param key metadata key to match
* @param wildcard the wildcard string to match against
* @return list of matching data sources
* @see <a href="https://commons.apache.org/proper/commons-io/javadocs/api-2.7/org/apache/commons/io/FilenameUtils.html#wildcardMatch-java.lang.String-java.lang.String-">Apache Commons IO</a>
*/
public DataSources filter(String key, String wildcard) {
return new DataSources(find(key, wildcard));
}
/**
* Group the <code>DataSources</code> by a metadata value.
* @param key metadata key to group by
* @return groups of <code>DataSources</code>
*/
public Map<String, DataSources> groupingBy(String key) {
final Function<DataSource, String> metadataFunction = dataSource -> dataSource.getMetadata(key);
return dataSources.stream()
.collect(Collectors.groupingBy(metadataFunction))
.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, p -> new DataSources(p.getValue())));
}
@Override
public void close() {
dataSources.forEach(ClosableUtils::closeQuietly);
}
@Override
public String toString() {
return "DataSource{" +
"dataSources=" + dataSources +
'}';
}
private static List<DataSource> getNamedDataSources(Collection<DataSource> dataSources) {
return dataSources.stream()
.filter(dataSource -> StringUtils.isNotEmpty(dataSource.getName()))
.collect(Collectors.toList());
}
private Map<String, DataSource> dataSourcesMap(Collection<DataSource> dataSources) {
final List<DataSource> namedDataSources = getNamedDataSources(dataSources);
return namedDataSources.stream().collect(Collectors.toMap(
DataSource::getName,
identity(),
(ds1, ds2) -> {
throw new IllegalStateException("Duplicate names detected when generating data source map: " + ds1 + ", " + ds2);
},
LinkedHashMap::new));
}
}