Skip to content
Permalink
Browse files
FREEMARKER-195 Improve exposure of DataSources using TemplateHashMode…
…lEx2 (#36)
  • Loading branch information
sgoeschl committed Oct 8, 2021
1 parent 51f8ee1 commit 45c391f98cd91299ab192084bcfb38f779bb6134
Showing 68 changed files with 515 additions and 208 deletions.
@@ -22,7 +22,7 @@
<parent>
<groupId>org.apache.freemarker.generator</groupId>
<artifactId>freemarker-generator</artifactId>
<version>0.1.0-SNAPSHOT</version>
<version>0.2.0-SNAPSHOT</version>
</parent>

<artifactId>freemarker-generator-base</artifactId>
@@ -410,7 +410,8 @@ public Map<String, String> getMetadata() {
}

/**
* Matches a metadata key with a wildcard expression.
* Matches a metadata key with a wildcard expression. If the wildcard is prefixed
* with a "!" than the match will be negated.
*
* @param key metadata key, e.g. "name", "fileName", "baseName", "extension", "uri", "group"
* @param wildcard the wildcard string to match against
@@ -419,7 +420,11 @@ public Map<String, String> getMetadata() {
*/
public boolean match(String key, String wildcard) {
final String value = getMetadata(key);
return FilenameUtils.wildcardMatch(value, wildcard);
if (wildcard != null && wildcard.startsWith("!")) {
return !FilenameUtils.wildcardMatch(value, wildcard.substring(1));
} else {
return FilenameUtils.wildcardMatch(value, wildcard);
}
}

/**
@@ -22,7 +22,7 @@ public interface DataSourceLoader {
* Check if the data source can be loaded by this instance.
*
* @param source source to be loaded from
* @return true if the instance wold be able to load a data source
* @return true if the instance would be able to load a data source
*/
boolean accept(String source);

@@ -23,6 +23,7 @@
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;
@@ -39,38 +40,43 @@ 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, "No data sources provided");
this.dataSources = new ArrayList<>(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 data source names
* @return list of data source names
*/
public List<String> getNames() {
return dataSources.stream()
.map(DataSource::getName)
.collect(Collectors.toList());
return new ArrayList<>(dataSourcesMap.keySet());
}

/**
* Get the requested metadata value for all data sources.
* 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 unique groups of all data sources.
* Get a list of distinct group names of all data sources.
*
* @return list of groups
* @return list of group names
*/
public List<String> getGroups() {
return dataSources.stream()
@@ -80,27 +86,46 @@ public List<String> getGroups() {
.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 new ArrayList<>(dataSources);
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
* iterate over data source, so we need to return a
* <code>LinkedHashMap</code>.
* <p>
* The implementation also throws as <code>IllegalStateException</code>
@@ -109,15 +134,15 @@ public List<DataSource> toList() {
* @return linked hasp map of data sources
*/
public Map<String, DataSource> toMap() {
return dataSources.stream().collect(Collectors.toMap(
DataSource::getName,
identity(),
(ds1, ds2) -> {
throw new IllegalStateException("Duplicate key detected when generating map: " + ds1 + ", " + ds2);
},
LinkedHashMap::new));
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);
}
@@ -130,7 +155,7 @@ public DataSource get(int index) {
* @return data source
*/
public DataSource get(String name) {
final List<DataSource> list = find(name);
final List<DataSource> list = findByName(name);

if (list.isEmpty()) {
throw new IllegalArgumentException("Data source not found : " + name);
@@ -144,16 +169,14 @@ public DataSource get(String name) {
}

/**
* Find data sources based on their name using a wildcard string..
* 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> find(String wildcard) {
return dataSources.stream()
.filter(dataSource -> dataSource.match("name", wildcard))
.collect(Collectors.toList());
public List<DataSource> findByName(String wildcard) {
return find(DataSource.METADATA_NAME, wildcard);
}

/**
@@ -170,6 +193,19 @@ public List<DataSource> find(String key, String 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));
}

@Override
public void close() {
dataSources.forEach(ClosableUtils::closeQuietly);
@@ -181,4 +217,21 @@ public String toString() {
"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));
}
}
@@ -22,7 +22,7 @@
import static java.util.Objects.requireNonNull;

/**
* Wraps a writer (usually the internal FreeMarker's writer instance
* Wraps a writer (usually the internal FreeMarker's writer instance)
* and avoids closing it since this would crash FreeMarker. E.g. the
* Commons CSV integration uses the FreeMarker writer directly but
* some implementation could call "CSVPrinter#close"
@@ -45,28 +45,32 @@ public class DataSourcesTest {
private static final String GROUP_PART = "group";

@Test
public void shouldFindByName() {
public void shouldFindDataSourcesByName() {
try (DataSources dataSources = dataSources()) {
assertEquals(0, dataSources.find(null).size());
assertEquals(0, dataSources.find("").size());
assertEquals(0, dataSources.find("*.bar").size());
assertEquals(0, dataSources.find("foo.*").size());
assertEquals(0, dataSources.find("foo.bar").size());

assertEquals(2, dataSources.find("*.*").size());
assertEquals(1, dataSources.find("*." + ANY_FILE_EXTENSION).size());
assertEquals(1, dataSources.find("*.???").size());
assertEquals(1, dataSources.find("*om*").size());
assertEquals(1, dataSources.find("*o*.xml").size());

assertEquals(3, dataSources.find("*").size());
assertEquals(0, dataSources.findByName(null).size());
assertEquals(0, dataSources.findByName("").size());
assertEquals(0, dataSources.findByName("*.bar").size());
assertEquals(0, dataSources.findByName("foo.*").size());
assertEquals(0, dataSources.findByName("foo.bar").size());

assertEquals(2, dataSources.findByName("*.*").size());
assertEquals(1, dataSources.findByName("*." + ANY_FILE_EXTENSION).size());
assertEquals(1, dataSources.findByName("*.???").size());
assertEquals(1, dataSources.findByName("*om*").size());
assertEquals(1, dataSources.findByName("*o*.xml").size());

assertEquals(3, dataSources.findByName("*").size());

assertEquals(2, dataSources.findByName("!pom.xml").size());
assertEquals(3, dataSources.findByName("!").size());
assertEquals(0, dataSources.findByName("!*").size());
assertEquals(1, dataSources.findByName("!*.*").size());
}
}

@Test
public void shouldFindByGroupPart() {
public void shouldFindDataSourcesByMetadata() {
try (DataSources dataSources = dataSources()) {

assertEquals(0, dataSources.find(GROUP_PART, null).size());
assertEquals(0, dataSources.find(GROUP_PART, "").size());

@@ -76,6 +80,10 @@ public void shouldFindByGroupPart() {
assertEquals(3, dataSources.find(GROUP_PART, "default").size());
assertEquals(3, dataSources.find(GROUP_PART, "d*").size());
assertEquals(3, dataSources.find(GROUP_PART, "d??????").size());

assertEquals(0, dataSources.find(GROUP_PART, "!*").size());
assertEquals(3, dataSources.find(GROUP_PART, "!unknown").size());
assertEquals(0, dataSources.find(GROUP_PART, "!default").size());
}
}

@@ -87,10 +95,10 @@ public void shouldGetDataSource() {
@Test
public void shouldGetAllDataSource() {
try (DataSources dataSources = dataSources()) {

assertEquals("unknown", dataSources.get(0).getName());
assertEquals("pom.xml", dataSources.get(1).getName());
assertEquals("server.invalid?foo=bar", dataSources.get(2).getName());
assertEquals(3, dataSources.toArray().length);
assertEquals(3, dataSources.toList().size());
assertEquals(3, dataSources.toMap().size());
assertEquals(3, dataSources.size());
@@ -100,9 +108,9 @@ public void shouldGetAllDataSource() {

@Test
public void shouldGetMetadataParts() {
assertEquals(asList("", "pom.xml", ""), dataSources().getMetadata("fileName"));
assertEquals(asList("default", "default", "default"), dataSources().getMetadata("group"));
assertEquals(asList("", "xml", ""), dataSources().getMetadata("extension"));
assertEquals(asList("pom.xml"), dataSources().getMetadata("fileName"));
assertEquals(asList("default"), dataSources().getMetadata("group"));
assertEquals(asList("xml"), dataSources().getMetadata("extension"));
assertEquals(asList("unknown", "pom.xml", "server.invalid?foo=bar"), dataSources().getMetadata("name"));
}

@@ -2,7 +2,12 @@

All notable changes to this project will be documented in this file. We try to adhere to https://github.com/olivierlacan/keep-a-changelog.

## 0.1.0-SNAPSHOT
## 0.2.0-SNAPSHOT

### Changed
* [FREEMARKER-195] Improve exposure of DataSources using TemplateHashModelEx2

## 0.1.0-SNAPSHOT (unreleased)

### Added
* Use `-Xverify:none -XX:TieredStopAtLevel=1` to improve startup time of CLI
@@ -22,7 +27,6 @@ All notable changes to this project will be documented in this file. We try to a
* [FREEMARKER-129] Migrate `freemarker-cli` into `freemarker-generator` project (see [https://github.com/sgoeschl/freemarker-cli](https://github.com/sgoeschl/freemarker-cli))
* [FREEMARKER-129] Provide a `toString()` method for all tools

### Changed
* [FREEMARKER-182] Upgrade to Apache FreeMarker 2.3.31
* [FREEMARKER-175] Use latest FreeMarker version
* [FREEMARKER-173] Allow to pass arbitrary key/value pairs to DataSource when using NamedURIs
@@ -85,4 +89,5 @@ All notable changes to this project will be documented in this file. We try to a
[FREEMARKER-181]: https://issues.apache.org/jira/browse/FREEMARKER-181
[FREEMARKER-182]: https://issues.apache.org/jira/browse/FREEMARKER-182
[FREEMARKER-188]: https://issues.apache.org/jira/browse/FREEMARKER-188
[FREEMARKER-195]: https://issues.apache.org/jira/browse/FREEMARKER-195

@@ -22,7 +22,7 @@
<parent>
<groupId>org.apache.freemarker.generator</groupId>
<artifactId>freemarker-generator</artifactId>
<version>0.1.0-SNAPSHOT</version>
<version>0.2.0-SNAPSHOT</version>
</parent>

<artifactId>freemarker-generator-cli</artifactId>
@@ -1,4 +1,4 @@
<#assign env = tools.properties.parse(dataSources?values[0])>
<#assign env = tools.properties.parse(dataSources[0])>

server {
listen 80;
@@ -16,7 +16,7 @@
under the License.
-->
<#assign grok = tools.grok.create("%{COMBINEDAPACHELOG}")>
<#assign dataSource = dataSources?values[0]>
<#assign dataSource = dataSources[0]>
<#assign lines = dataSource.getLineIterator()>

<#compress>
@@ -15,7 +15,7 @@
specific language governing permissions and limitations
under the License.
-->
<#assign dataSource = dataSources?values[0]>
<#assign dataSource = dataSources[0]>
<#assign cvsFormat = tools.csv.formats.DEFAULT.withDelimiter(';')>
<#assign csvParser = tools.csv.parse(dataSource, cvsFormat)>
<#assign csvRecords = csvParser.records>

0 comments on commit 45c391f

Please sign in to comment.