You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by sg...@apache.org on 2020/03/17 08:36:07 UTC

[freemarker-generator] branch master updated: Change "DataSource#location" to "DataSource#uri" and support data sources based on environment variables

This is an automated email from the ASF dual-hosted git repository.

sgoeschl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/freemarker-generator.git


The following commit(s) were added to refs/heads/master by this push:
     new 59b3e4b  Change "DataSource#location" to "DataSource#uri" and support data sources based on environment variables
59b3e4b is described below

commit 59b3e4b03e59103f1293f14e4c5c2690428af8d7
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Tue Mar 17 09:35:51 2020 +0100

    Change "DataSource#location" to "DataSource#uri" and support data sources based on environment variables
---
 CHANGELOG.md                                       |   2 +
 .../generator/base/FreeMarkerConstants.java        |   7 +-
 .../base/activation/ByteArrayDataSource.java       |   4 +-
 .../base/activation/InputStreamDataSource.java     |   4 +-
 .../generator/base/activation/Mimetypes.java       |  20 +++
 .../activation/MimetypesFileTypeMapFactory.java    |  40 ++++--
 .../base/activation/StringDataSource.java          |   9 +-
 .../generator/base/datasource/DataSource.java      |  40 +++---
 .../base/datasource/DataSourceFactory.java         | 137 +++++++++++++++++----
 .../generator/base/datasource/DataSources.java     |   2 +-
 .../base/datasource/DataSourcesSupplier.java       |  74 +++++------
 .../generator/base/tools/ToolsFactory.java         |   2 +-
 .../Transformation.java}                           |  32 ++---
 .../freemarker/generator/base/uri/NamedUri.java    |  37 +++++-
 ...medUriParser.java => NamedUriStringParser.java} |  22 ++--
 ...iFragmentParser.java => UriFragmentParser.java} |   2 +-
 .../generator/base/util/StringUtils.java           |  11 ++
 .../freemarker/generator/base/util/UriUtils.java   |  56 +++++++++
 ...FactoryTest.java => DataSourceFactoryTest.java} |  43 ++++---
 .../{DatasourceTest.java => DataSourceTest.java}   |  37 +++---
 ...plierTest.java => DataSourcesSupplierTest.java} |  27 +++-
 .../{DatasourcesTest.java => DataSourcesTest.java} |  30 ++---
 .../freemarker/generator/datasource/UriTest.java   |  20 +++
 ...rserTest.java => NamedUriStringParserTest.java} |  31 ++++-
 .../org/apache/freemarker/generator/cli/Main.java  |  44 ++++++-
 .../generator/cli/task/FreeMarkerTask.java         |   5 +-
 .../freemarker/generator/cli/ManualTest.java       |   3 +-
 .../freemarker/generator/cli/PicocliTest.java      |   4 +-
 .../src/test/templates/echo.ftl                    |   2 +-
 freemarker-generator-cli/templates/demo.ftl        |   4 +-
 freemarker-generator-cli/templates/info.ftl        |   4 +-
 .../tools/commonscsv/CommonsCSVToolTest.java       |   2 +-
 .../generator/tools/excel/ExcelToolTest.java       |   2 +-
 .../tools/properties/PropertiesToolTest.java       |   4 +-
 .../tools/snakeyaml/SnakeYamlToolTest.java         |   5 +-
 .../generator/tools/xml/XmlToolTest.java           |   5 +-
 src/site/markdown/index.md                         |   2 +-
 37 files changed, 568 insertions(+), 207 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index aad20cc..1867116 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. We try to a
 ## 0.1.0-SNAPSHOT
 
 ### Added
+* An environment variable can bes passed as `DataSource`
 * [FREEMARKER-135] Support user-supplied names for `DataSource` on the command line
 * [FREEMARKER-129] Support `DataSource` exclude pattern in addition to include pattern
 * [FREEMARKER-129] User-defined parameters are passed as `-Pkey=value` instead of using system properties
@@ -12,6 +13,7 @@ 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))
 
 ### Changed
+* `DataSource` use `uri` instead of `location`
 * [FREEMARKER-138] freemarker-generator: Rename `Datasource` to `DataSource`
 * [FREEMARKER-136] Fix broken `site:stage` build
 * [FREEMARKER-134] Rename `Document` to `Datasource` which also changes `--document` to `--datasource`
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/FreeMarkerConstants.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/FreeMarkerConstants.java
index f11b636..0f7aab9 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/FreeMarkerConstants.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/FreeMarkerConstants.java
@@ -30,10 +30,7 @@ public class FreeMarkerConstants {
     private FreeMarkerConstants() {
     }
 
-    /** Content type for binary data */
-    public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
-
-    /** Unknown length for a <code>Datasource</code> */
+    /** Unknown length for a <code>DataSource</code> */
     public static final int DATASOURCE_UNKNOWN_LENGTH = -1;
 
     /** Default locale for rendering templates */
@@ -71,9 +68,11 @@ public class FreeMarkerConstants {
         }
 
         public static final String BYTES = "bytes";
+        public static final String ENVIRONMENT = "env";
         public static final String INTERACTIVE = "interactive";
         public static final String INPUTSTREAM = "inputstream";
         public static final String STDIN = "stdin";
+        public static final String SYSTEM = "system";
         public static final String STRING = "string";
         public static final String URL = "url";
     }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java
index 619f667..8933ea6 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java
@@ -23,7 +23,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.freemarker.generator.base.FreeMarkerConstants.APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 
 public class ByteArrayDataSource implements DataSource {
 
@@ -32,7 +32,7 @@ public class ByteArrayDataSource implements DataSource {
     private final String contentType;
 
     public ByteArrayDataSource(String name, byte[] content) {
-        this(name, content, APPLICATION_OCTET_STREAM);
+        this(name, content, MIME_APPLICATION_OCTET_STREAM);
     }
 
     public ByteArrayDataSource(String name, byte[] content, String contentType) {
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java
index da5f6bd..11b54b1 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java
@@ -21,7 +21,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.freemarker.generator.base.FreeMarkerConstants.APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 
 /**
  * Wraps an InputStream into a DataSource. Please note that the input stream
@@ -35,7 +35,7 @@ public class InputStreamDataSource implements DataSource {
     private final String contentType;
 
     public InputStreamDataSource(String name, InputStream is) {
-        this(name, is, APPLICATION_OCTET_STREAM);
+        this(name, is, MIME_APPLICATION_OCTET_STREAM);
     }
 
     public InputStreamDataSource(String name, InputStream is, String contentType) {
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/Mimetypes.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/Mimetypes.java
new file mode 100644
index 0000000..5bb4ebe
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/Mimetypes.java
@@ -0,0 +1,20 @@
+package org.apache.freemarker.generator.base.activation;
+
+public class Mimetypes {
+
+    public static final String MIME_APPLICATION_JSON = "application/json";
+    public static final String MIME_APPLICATION_OCTET_STREAM = "application/octet-stream";
+    public static final String MIME_APPLICATION_XML = "application/xml";
+    public static final String MIME_APPLICATION_XHTML = "application/xhtml+xml";
+
+    public static final String MIME_TEXT_CSV = "text/csv";
+    public static final String MIME_TEXT_HTML = "text/html";
+    public static final String MIME_TEXT_MARKDOWM = "text/markdown";
+    public static final String MIME_TEXT_PLAIN = "text/plain";
+    public static final String MIME_TEXT_RTF = "text/rtf";
+    public static final String MIME_TEXT_TSV = "text/tab-separated-values";
+    public static final String MIME_TEXT_YAML = "text/yaml";
+
+    public static final String MIME_VENDOR_MS_EXCEL = "application/vnd.ms-excel xls XLS";
+    public static final String MIME_VENDOR_OPEN_XML_SPREADSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx XLSX\"";
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java
index 955dbb9..4fff085 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java
@@ -14,11 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.freemarker.generator.base.activation;
 
 import javax.activation.MimetypesFileTypeMap;
 
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_JSON;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XHTML;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XML;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_CSV;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_HTML;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_MARKDOWM;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_PLAIN;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_RTF;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_TSV;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_YAML;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_VENDOR_MS_EXCEL;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_VENDOR_OPEN_XML_SPREADSHEET;
+
 public class MimetypesFileTypeMapFactory {
 
     private static MimetypesFileTypeMap mimeTypes;
@@ -26,18 +39,19 @@ public class MimetypesFileTypeMapFactory {
     public static synchronized MimetypesFileTypeMap create() {
         if (mimeTypes == null) {
             mimeTypes = new MimetypesFileTypeMap();
-            mimeTypes.addMimeTypes("application/json json JSON");
-            mimeTypes.addMimeTypes("application/octet-stream bin");
-            mimeTypes.addMimeTypes("application/vnd.ms-excel xls XLS");
-            mimeTypes.addMimeTypes("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx XLSX");
-            mimeTypes.addMimeTypes("application/xml xml XML");
-            mimeTypes.addMimeTypes("application/xhtml+xml xhtml XHTML");
-            mimeTypes.addMimeTypes("text/csv csv CSV");
-            mimeTypes.addMimeTypes("text/plain env txt TXT log LOG ini INI properties");
-            mimeTypes.addMimeTypes("text/html htm HTML HTM HTML");
-            mimeTypes.addMimeTypes("text/markdown md MD");
-            mimeTypes.addMimeTypes("text/tab-separated-values tsv TSV");
-            mimeTypes.addMimeTypes("text/yaml yml YML yaml YAML");
+            mimeTypes.addMimeTypes(MIME_APPLICATION_JSON + " json JSON");
+            mimeTypes.addMimeTypes(MIME_APPLICATION_OCTET_STREAM + " bin BIN");
+            mimeTypes.addMimeTypes(MIME_VENDOR_MS_EXCEL + " xls XLS");
+            mimeTypes.addMimeTypes(MIME_VENDOR_OPEN_XML_SPREADSHEET + " xlsx XLSX");
+            mimeTypes.addMimeTypes(MIME_APPLICATION_XML + " xml XML");
+            mimeTypes.addMimeTypes(MIME_APPLICATION_XHTML + " xhtml XHTML");
+            mimeTypes.addMimeTypes(MIME_TEXT_CSV + " csv CSV");
+            mimeTypes.addMimeTypes(MIME_TEXT_PLAIN + " adoc ADOC env ENV ini INI log LOG properties txt TXT");
+            mimeTypes.addMimeTypes(MIME_TEXT_HTML + " htm HTM html HTML");
+            mimeTypes.addMimeTypes(MIME_TEXT_MARKDOWM + " md MD");
+            mimeTypes.addMimeTypes(MIME_TEXT_RTF + " rtf RTF");
+            mimeTypes.addMimeTypes(MIME_TEXT_TSV + " tsv TSV");
+            mimeTypes.addMimeTypes(MIME_TEXT_YAML + " yml YML yaml YAML");
         }
 
         return mimeTypes;
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java
index 7e9c29d..fd60cae 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java
@@ -24,20 +24,23 @@ import java.nio.charset.Charset;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_PLAIN;
 
 public class StringDataSource implements DataSource {
 
     private final String name;
     private final String content;
+    private final String contentType;
     private final Charset charset;
 
     public StringDataSource(String name, String content) {
-        this(name, content, UTF_8);
+        this(name, content, MIME_TEXT_PLAIN, UTF_8);
     }
 
-    public StringDataSource(String name, String content, Charset charset) {
+    public StringDataSource(String name, String content, String contentType, Charset charset) {
         this.name = requireNonNull(name);
         this.content = requireNonNull(content);
+        this.contentType = requireNonNull(contentType);
         this.charset = requireNonNull(charset);
     }
 
@@ -53,7 +56,7 @@ public class StringDataSource implements DataSource {
 
     @Override
     public String getContentType() {
-        return "plain/text";
+        return contentType;
     }
 
     @Override
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
index 9b9354f..9f2ee51 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
@@ -28,6 +28,7 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
+import java.net.URI;
 import java.nio.charset.Charset;
 import java.util.List;
 
@@ -38,7 +39,7 @@ import static org.apache.freemarker.generator.base.FreeMarkerConstants.DATASOURC
 import static org.apache.freemarker.generator.base.util.StringUtils.emptyToNull;
 
 /**
- * Datasource which encapsulates data to be used for rendering
+ * Data source which encapsulates data to be used for rendering
  * a template. When accessing content it is loaded on demand on not
  * kept in memory to allow processing of large volumes of data.
  */
@@ -50,23 +51,33 @@ public class DataSource implements Closeable {
     /** Optional group of data source */
     private final String group;
 
-    /** Charset for directly accessing text-based content */
-    private final Charset charset;
+    /** The URI for loading the content of the data source */
+    private final URI uri;
 
     /** The underlying "javax.activation.DataSource" */
     private final javax.activation.DataSource dataSource;
 
-    /** The location of the content, e.g. file name */
-    private final String location;
+    /** Optional user-supplied content type */
+    private final String contentType;
+
+    /** Charset for directly accessing text-based content */
+    private final Charset charset;
 
     /** Collect all closables handed out to the caller to be closed when the data source is closed itself */
     private final CloseableReaper closables;
 
-    public DataSource(String name, String group, javax.activation.DataSource dataSource, String location, Charset charset) {
+    public DataSource(
+            String name,
+            String group,
+            URI uri,
+            javax.activation.DataSource dataSource,
+            String contentType,
+            Charset charset) {
         this.name = requireNonNull(name);
         this.group = emptyToNull(group);
+        this.uri = requireNonNull(uri);
         this.dataSource = requireNonNull(dataSource);
-        this.location = requireNonNull(location);
+        this.contentType = requireNonNull(contentType);
         this.charset = requireNonNull(charset);
         this.closables = new CloseableReaper();
     }
@@ -92,11 +103,11 @@ public class DataSource implements Closeable {
     }
 
     public String getContentType() {
-        return dataSource.getContentType();
+        return contentType;
     }
 
-    public String getLocation() {
-        return location;
+    public URI getUri() {
+        return uri;
     }
 
     /**
@@ -223,11 +234,12 @@ public class DataSource implements Closeable {
 
     @Override
     public String toString() {
-        return "Datasource{" +
+        return "DataSource{" +
                 "name='" + name + '\'' +
-                "group='" + group + '\'' +
-                ", location=" + location +
-                ", charset='" + charset + '\'' +
+                ", group='" + group + '\'' +
+                ", uri=" + uri +
+                ", contentType='" + contentType + '\'' +
+                ", charset=" + charset +
                 '}';
     }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
index 78a5556..cfddab1 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
@@ -21,11 +21,17 @@ import org.apache.freemarker.generator.base.activation.ByteArrayDataSource;
 import org.apache.freemarker.generator.base.activation.InputStreamDataSource;
 import org.apache.freemarker.generator.base.activation.MimetypesFileTypeMapFactory;
 import org.apache.freemarker.generator.base.activation.StringDataSource;
+import org.apache.freemarker.generator.base.uri.NamedUri;
+import org.apache.freemarker.generator.base.uri.NamedUriStringParser;
+import org.apache.freemarker.generator.base.util.StringUtils;
+import org.apache.freemarker.generator.base.util.UriUtils;
 
 import javax.activation.FileDataSource;
 import javax.activation.URLDataSource;
 import java.io.File;
 import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
 
@@ -33,56 +39,141 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
 
 /**
- * Creates a Datasource from various sources.
+ * Creates a FreeMarker data source from various sources.
  */
 public class DataSourceFactory {
 
     private DataSourceFactory() {
     }
 
-    public static DataSource create(URL url) {
-        final String location = url.toString();
-        final URLDataSource dataSource = new URLDataSource(url);
-        return create(url.getHost(), DEFAULT_GROUP, dataSource, location, UTF_8);
+    // == NamedUri ==========================================================
+
+    public static DataSource fromNamedUri(String str) {
+        return fromNamedUri(NamedUriStringParser.parse(str));
     }
 
-    public static DataSource create(String name, String group, URL url, Charset charset) {
-        final String location = url.toString();
+    public static DataSource fromNamedUri(NamedUri namedUri) {
+        final URI uri = namedUri.getUri();
+        final String group = namedUri.getGroupOrElse(DEFAULT_GROUP);
+        final Charset charset = getCharsetOrElse(namedUri, UTF_8);
+
+        if (UriUtils.isHttpURI(uri)) {
+            final URL url = toURL(uri);
+            return fromUrl(url.getHost(), group, url, charset);
+        } else if (UriUtils.isFileUri(uri)) {
+            final File file = namedUri.getFile();
+            return fromFile(file.getName(), group, file, charset);
+        } else if (UriUtils.isEnvUri(uri)) {
+            final String key = uri.getPath().substring(1);
+            final String name = StringUtils.firstNonEmpty(namedUri.getName(), key, "env");
+            final String contentType = getMimeTypeOrElse(namedUri, "text/plain");
+            return fromEnvironment(name, group, key, contentType);
+        } else {
+            throw new IllegalArgumentException("Don't knowm how to handle: " + namedUri);
+        }
+    }
+
+    // == URL ===============================================================
+
+    public static DataSource fromUrl(String name, String group, URL url, Charset charset) {
+        return fromUrl(name, group, url, "application/octet-stream", charset);
+    }
+
+    public static DataSource fromUrl(String name, String group, URL url, String contentType, Charset charset) {
         final URLDataSource dataSource = new URLDataSource(url);
-        return create(name, group, dataSource, location, charset);
+        final URI uri = UriUtils.toURI(url);
+        return create(name, group, uri, dataSource, contentType, charset);
+    }
+
+    // == String ============================================================
+
+    public static DataSource fromString(String content, String contentType) {
+        return fromString(Location.STRING, DEFAULT_GROUP, content, contentType);
     }
 
-    public static DataSource create(String name, String group, String content) {
-        final StringDataSource dataSource = new StringDataSource(name, content, UTF_8);
-        return create(name, group, dataSource, Location.STRING, UTF_8);
+    public static DataSource fromString(String name, String group, String content, String contentType) {
+        final StringDataSource dataSource = new StringDataSource(name, content, contentType, UTF_8);
+        final URI uri = UriUtils.toURI(Location.STRING, Integer.toString(content.hashCode()));
+        return create(name, group, uri, dataSource, contentType, UTF_8);
     }
 
-    public static DataSource create(File file, Charset charset) {
-        return create(file.getName(), DEFAULT_GROUP, file, charset);
+    // == File ==============================================================
+
+    public static DataSource fromFile(File file, Charset charset) {
+        return fromFile(file.getName(), DEFAULT_GROUP, file, charset);
     }
 
-    public static DataSource create(String name, String group, File file, Charset charset) {
+    public static DataSource fromFile(String name, String group, File file, Charset charset) {
         final FileDataSource dataSource = new FileDataSource(file);
         dataSource.setFileTypeMap(MimetypesFileTypeMapFactory.create());
-        return create(name, group, dataSource, file.getAbsolutePath(), charset);
+        final String contentType = dataSource.getContentType();
+        return create(name, group, file.toURI(), dataSource, contentType, charset);
     }
 
-    public static DataSource create(String name, String group, byte[] content) {
+    // == Bytes ============================================================
+
+    public static DataSource fromBytes(String name, String group, byte[] content, String contentType) {
         final ByteArrayDataSource dataSource = new ByteArrayDataSource(name, content);
-        return create(name, group, dataSource, Location.BYTES, UTF_8);
+        final URI uri = UriUtils.toURI(Location.BYTES + ":///");
+        return create(name, group, uri, dataSource, contentType, UTF_8);
     }
 
-    public static DataSource create(String name, String group, InputStream is, Charset charset) {
+    // == InputStream =======================================================
+
+    public static DataSource fromInputStream(String name, String group, InputStream is, String contentType, Charset charset) {
         final InputStreamDataSource dataSource = new InputStreamDataSource(name, is);
-        return create(name, group, dataSource, Location.INPUTSTREAM, charset);
+        final URI uri = UriUtils.toURI(Location.INPUTSTREAM + ":///");
+        return create(name, group, uri, dataSource, contentType, charset);
     }
 
-    public static DataSource create(String name, String group, InputStream is, String location, Charset charset) {
+    public static DataSource fromInputStream(String name, String group, URI uri, InputStream is, String contentType, Charset charset) {
         final InputStreamDataSource dataSource = new InputStreamDataSource(name, is);
-        return create(name, group, dataSource, location, charset);
+        return create(name, group, uri, dataSource, contentType, charset);
+    }
+
+    // == Environment =======================================================
+
+    public static DataSource fromEnvironment(String name, String group, String key, String contentType) {
+        final String value = System.getenv(key);
+        final StringDataSource dataSource = new StringDataSource(name, value, contentType, UTF_8);
+        final URI uri = UriUtils.toURI(Location.ENVIRONMENT, key);
+        return create(name, group, uri, dataSource, contentType, UTF_8);
+    }
+
+    // == General ===========================================================
+
+    public static DataSource create(String str) {
+        if (UriUtils.isUri(str)) {
+            return fromNamedUri(str);
+        } else {
+            final File file = new File(str);
+            return fromFile(file.getName(), DEFAULT_GROUP, file, UTF_8);
+        }
+    }
+
+    public static DataSource create(
+            String name,
+            String group,
+            URI uri,
+            javax.activation.DataSource dataSource,
+            String contentType,
+            Charset charset) {
+        return new DataSource(name, group, uri, dataSource, contentType, charset);
+    }
+
+    private static String getMimeTypeOrElse(NamedUri namedUri, String def) {
+        return namedUri.getParameter(NamedUri.MIMETYPE, def);
+    }
+
+    private static Charset getCharsetOrElse(NamedUri namedUri, Charset def) {
+        return Charset.forName(namedUri.getParameter(NamedUri.CHARSET, def.name()));
     }
 
-    public static DataSource create(String name, String group, javax.activation.DataSource dataSource, String location, Charset charset) {
-        return new DataSource(name, group, dataSource, location, charset);
+    private static URL toURL(URI uri) {
+        try {
+            return uri.toURL();
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException(uri.toString(), e);
+        }
     }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java
index cda71cb..4c6e156 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java
@@ -137,7 +137,7 @@ public class DataSources implements Closeable {
 
     @Override
     public String toString() {
-        return "Datasources{" +
+        return "DataSource{" +
                 "dataSources=" + dataSources +
                 ", names=" + getNames() +
                 ", groups=" + getGroups() +
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
index d137d1b..54e0291 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
@@ -18,18 +18,15 @@ package org.apache.freemarker.generator.base.datasource;
 
 import org.apache.freemarker.generator.base.file.RecursiveFileSupplier;
 import org.apache.freemarker.generator.base.uri.NamedUri;
-import org.apache.freemarker.generator.base.uri.NamedUriParser;
+import org.apache.freemarker.generator.base.uri.NamedUriStringParser;
+import org.apache.freemarker.generator.base.util.Validate;
 
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Supplier;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
@@ -37,7 +34,7 @@ import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_G
 
 /**
  * Create a list of <code>DataSource</code> based on a list of sources consisting of
- * URLs, directories and files.
+ * URIs, directories and files.
  */
 public class DataSourcesSupplier implements Supplier<List<DataSource>> {
 
@@ -76,10 +73,20 @@ public class DataSourcesSupplier implements Supplier<List<DataSource>> {
                 .collect(toList());
     }
 
-    private List<DataSource> get(String source) {
+    /**
+     * Resolve a <code>source</code> to a <code>DataSource</code>.
+     *
+     * @param source the source being a file name, an URI or <code>NamedUri</code>
+     * @return list of <code>DataSource</code>
+     */
+    protected List<DataSource> get(String source) {
+        Validate.notEmpty(source, "source is empty");
+
         try {
-            if (isHttpUrl(source)) {
+            if (isHttpUri(source)) {
                 return singletonList(resolveHttpUrl(source));
+            } else if (isEnvUri(source)) {
+                return singletonList(resolveEnvironment(source));
             } else {
                 return resolveFile(source, include, exclude, charset);
             }
@@ -89,51 +96,46 @@ public class DataSourcesSupplier implements Supplier<List<DataSource>> {
     }
 
     private static DataSource resolveHttpUrl(String source) {
-        final NamedUri namedUri = NamedUriParser.parse(source);
-        final URI uri = namedUri.getUri();
-        final String name = getNameOrElse(namedUri, uri.toString());
-        final String group = getGroupOrElse(namedUri, DEFAULT_GROUP);
-        final Charset currCharset = getCharsetOrElse(namedUri, UTF_8);
-        return DataSourceFactory.create(name, group, toUrl(uri), currCharset);
+        return DataSourceFactory.create(source);
     }
 
     private static List<DataSource> resolveFile(String source, String include, String exclude, Charset charset) {
-        final NamedUri namedUri = NamedUriParser.parse(source);
-        final String path = namedUri.getUri().getPath();
-        final String name = getNameOrElse(namedUri, path);
-        final String group = getGroupOrElse(namedUri, DEFAULT_GROUP);
+        final NamedUri namedUri = NamedUriStringParser.parse(source);
+        final String path = namedUri.getFile().getPath();
+        final String name = getDataSourceName(namedUri);
+        final String group = namedUri.getGroupOrElse(DEFAULT_GROUP);
         final Charset currCharset = getCharsetOrElse(namedUri, charset);
         return fileResolver(path, include, exclude).get().stream()
-                .map(file -> DataSourceFactory.create(name, group, file, currCharset))
+                .map(file -> DataSourceFactory.fromFile(name, group, file, currCharset))
                 .collect(toList());
     }
 
-    private static RecursiveFileSupplier fileResolver(String source, String include, String exclude) {
-        return new RecursiveFileSupplier(singletonList(source), singletonList(include), singletonList(exclude));
-    }
-
-    private static boolean isHttpUrl(String value) {
-        return value.contains("http://") || value.contains("https://");
+    private static DataSource resolveEnvironment(String source) {
+        final NamedUri namedUri = NamedUriStringParser.parse(source);
+        return DataSourceFactory.fromNamedUri(namedUri);
     }
 
-    private static URL toUrl(URI uri) {
-        try {
-            return uri.toURL();
-        } catch (MalformedURLException e) {
-            throw new IllegalArgumentException(uri.toString(), e);
-        }
+    private static RecursiveFileSupplier fileResolver(String source, String include, String exclude) {
+        return new RecursiveFileSupplier(singletonList(source), singletonList(include), singletonList(exclude));
     }
 
     private static Charset getCharsetOrElse(NamedUri namedUri, Charset def) {
-        return Charset.forName(namedUri.getParameters().getOrDefault("charset", def.name()));
+        return Charset.forName(namedUri.getParameter(NamedUri.CHARSET, def.name()));
     }
 
-    private static String getNameOrElse(NamedUri namedUri, String def) {
-        return namedUri.hasName() ? namedUri.getName() : def;
+    private static boolean isHttpUri(String value) {
+        return value.contains("http://") || value.contains("https://");
     }
 
-    private static String getGroupOrElse(NamedUri namedUri, String def) {
-        return namedUri.hasGroup() ? namedUri.getGroup() : def;
+    private static boolean isEnvUri(String value) {
+        return value.contains("env:///");
     }
 
+    private static String getDataSourceName(NamedUri namedUri) {
+        if (namedUri.hasName()) {
+            return namedUri.getName();
+        } else {
+            return namedUri.getFile().getName();
+        }
+    }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/tools/ToolsFactory.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/tools/ToolsFactory.java
index b8eacc1..00a202b 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/tools/ToolsFactory.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/tools/ToolsFactory.java
@@ -89,7 +89,7 @@ public class ToolsFactory {
                 return Class.forName(className, true, contextClassLoader);
             }
         } catch (ClassNotFoundException | SecurityException e) {
-            ;// Intentionally ignored
+            // Intentionally ignored
         }
 
         // Fall back to the defining class loader of the FreeMarker classes
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/transformation/Transformation.java
similarity index 56%
copy from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
copy to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/transformation/Transformation.java
index 4dd8184..d1bd2e2 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/transformation/Transformation.java
@@ -14,24 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.freemarker.generator.base.util;
+package org.apache.freemarker.generator.base.transformation;
 
-public class StringUtils {
+import org.apache.freemarker.generator.base.datasource.DataSource;
+import org.apache.freemarker.generator.base.template.TemplateSource;
 
-    public static boolean isEmpty(String value) {
-        return value == null || value.trim().isEmpty();
-    }
+import java.io.Writer;
+import java.util.List;
 
-    public static boolean isNotEmpty(String value) {
-        return !isEmpty(value);
-    }
+/**
+ *
+ */
+public class Transformation {
 
-    public static String emptyToNull(String value) {
-        return value != null && value.trim().isEmpty() ? null : value;
-    }
+    private final TemplateSource templateSource;
 
-    public static String nullToEmpty(String value) {
-        return value == null ? "" : value;
-    }
+    private final List<DataSource> dataSources;
 
+    private final Writer writer;
+
+    public Transformation(TemplateSource templateSource, List<DataSource> dataSources, Writer writer) {
+        this.templateSource = templateSource;
+        this.dataSources = dataSources;
+        this.writer = writer;
+    }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
index 0b1211d..8eb7382 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
@@ -16,10 +16,15 @@
  */
 package org.apache.freemarker.generator.base.uri;
 
+import org.apache.freemarker.generator.base.util.StringUtils;
+import org.apache.freemarker.generator.base.util.UriUtils;
+
+import java.io.File;
 import java.net.URI;
 import java.util.Map;
 
 import static java.util.Objects.requireNonNull;
+import static org.apache.freemarker.generator.base.util.StringUtils.emptyToNull;
 import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
 
 /**
@@ -27,6 +32,9 @@ import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
  */
 public class NamedUri {
 
+    public static final String CHARSET = "charset";
+    public static final String MIMETYPE = "mimetype";
+
     /** User-supplied name */
     private final String name;
 
@@ -57,10 +65,18 @@ public class NamedUri {
         return name;
     }
 
+    public String getNameOrElse(String def) {
+        return isEmpty(name) ? def : name;
+    }
+
     public String getGroup() {
         return group;
     }
 
+    public String getGroupOrElse(String def) {
+        return isEmpty(group) ? def : group;
+    }
+
     public URI getUri() {
         return uri;
     }
@@ -69,6 +85,14 @@ public class NamedUri {
         return parameters;
     }
 
+    public String getParameter(String key) {
+        return parameters.get(key);
+    }
+
+    public String getParameter(String key, String defaultValue) {
+        return parameters.getOrDefault(key, defaultValue);
+    }
+
     public boolean hasName() {
         return !isEmpty(this.name);
     }
@@ -77,6 +101,15 @@ public class NamedUri {
         return !isEmpty(this.group);
     }
 
+    public File getFile() {
+        if (UriUtils.isFileUri(uri)) {
+            return new File(uri.getPath().substring(1));
+        }
+        else {
+            return new File(uri.getPath());
+        }
+    }
+
     @Override
     public String toString() {
         return "NamedUri{" +
@@ -86,8 +119,4 @@ public class NamedUri {
                 ", parameters=" + parameters +
                 '}';
     }
-
-    private static String emptyToNull(String value) {
-        return value != null && value.trim().isEmpty() ? null : value;
-    }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java
similarity index 85%
rename from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java
rename to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java
index e70bf86..76d961d 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java
@@ -16,10 +16,10 @@
  */
 package org.apache.freemarker.generator.base.uri;
 
+import org.apache.freemarker.generator.base.util.UriUtils;
 import org.apache.freemarker.generator.base.util.Validate;
 
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -36,7 +36,7 @@ import static java.util.regex.Pattern.compile;
  *     <li>users=file:///users.csv#charset=UTF-16&amp;mimetype=text/csv</li>
  * </ul>
  */
-public class NamedUriParser {
+public class NamedUriStringParser {
 
     private static final String NAME = "name";
     private static final String GROUP = "group";
@@ -55,20 +55,20 @@ public class NamedUriParser {
             final URI uri = uri(matcher.group(URI));
             return new NamedUri(name, group, uri, parameters(uri));
         } else {
-            final URI uri = uri(value);
+            final URI uri = UriUtils.toURI(value);
             return new NamedUri(uri, parameters(uri));
         }
     }
 
-    private static URI uri(String value) {
-        try {
-            return new URI(value);
-        } catch (URISyntaxException e) {
-            throw new RuntimeException("Failed to parse URI: " + value, e);
-        }
+    private static Map<String, String> parameters(URI uri) {
+        return UriFragmentParser.parse(uri.getFragment());
     }
 
-    private static Map<String, String> parameters(URI uri) {
-        return NamedUriFragmentParser.parse(uri.getFragment());
+    private static URI uri(String str) {
+        if (!str.contains("://")) {
+            return UriUtils.toURI("file:///" + str);
+        } else {
+            return UriUtils.toURI(str);
+        }
     }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/UriFragmentParser.java
similarity index 97%
rename from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
rename to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/UriFragmentParser.java
index 304a233..839cba7 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/UriFragmentParser.java
@@ -26,7 +26,7 @@ import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
 /**
  * Parses the URI fragment as list of name/value pairs seperated by an ampersand.
  */
-public class NamedUriFragmentParser {
+public class UriFragmentParser {
 
     public static Map<String, String> parse(String fragment) {
         if (isEmpty(fragment)) {
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
index 4dd8184..24b81af 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
@@ -34,4 +34,15 @@ public class StringUtils {
         return value == null ? "" : value;
     }
 
+    public static String firstNonEmpty(final String... values) {
+        if (values != null) {
+            for (final String value : values) {
+                if (isNotEmpty(value)) {
+                    return value;
+                }
+            }
+        }
+        return null;
+    }
+
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/UriUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/UriUtils.java
new file mode 100644
index 0000000..cbc401b
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/UriUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class UriUtils {
+
+    public static URI toURI(String str) {
+        try {
+            return new URI(str);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Failed to create URI: " + str, e);
+        }
+    }
+
+    public static URI toURI(String scheme, String path) {
+        return toURI(scheme + ":///" + path);
+    }
+
+    public static URI toURI(URL url) {
+        return toURI(url.toString());
+    }
+
+    public static boolean isUri(String str) {
+        return str.contains("://");
+    }
+
+    public static boolean isHttpURI(URI uri) {
+        return uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equalsIgnoreCase("https");
+    }
+
+    public static boolean isFileUri(URI uri) {
+        return "file".equalsIgnoreCase(uri.getScheme());
+    }
+
+    public static boolean isEnvUri(URI uri) {
+        return "env".equalsIgnoreCase(uri.getScheme());
+    }
+}
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourceFactoryTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java
similarity index 59%
rename from freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourceFactoryTest.java
rename to freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java
index cdebe2a..13ef213 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourceFactoryTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.freemarker.generator.datasource;
 
+import org.apache.freemarker.generator.base.activation.Mimetypes;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
 import org.junit.Test;
@@ -27,59 +28,73 @@ import java.io.InputStream;
 import java.nio.charset.Charset;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XML;
 import static org.junit.Assert.assertEquals;
 
-public class DatasourceFactoryTest {
+public class DataSourceFactoryTest {
 
     private static final String ANY_TEXT = "Hello World";
     private static final String ANY_FILE_NAME = "pom.xml";
+    private static final String ANY_FILE_URI = "file:///pom.xml";
     private static final Charset ANY_CHAR_SET = UTF_8;
     private static final File ANY_FILE = new File(ANY_FILE_NAME);
 
     @Test
-    public void shouldCreateFileBasedDatasource() throws IOException {
-        final DataSource dataSource = DataSourceFactory.create(ANY_FILE, ANY_CHAR_SET);
+    public void shouldCreateDataSourceFromFile() throws IOException {
+        final DataSource dataSource = DataSourceFactory.fromFile(ANY_FILE, ANY_CHAR_SET);
 
         assertEquals(ANY_FILE_NAME, dataSource.getName());
         assertEquals(UTF_8, dataSource.getCharset());
-        assertEquals(ANY_FILE.getAbsolutePath(), dataSource.getLocation());
+        assertEquals(MIME_APPLICATION_XML, dataSource.getContentType());
+        assertEquals(ANY_FILE.toURI(), dataSource.getUri());
+        assertFalse(dataSource.getLines().isEmpty());
+    }
+
+    @Test
+    public void shouldCreateDataSourceFromFileUri() throws IOException {
+        final DataSource dataSource = DataSourceFactory.create(ANY_FILE_URI);
+
+        assertEquals(ANY_FILE_NAME, dataSource.getName());
+        assertEquals(UTF_8, dataSource.getCharset());
+        assertEquals(MIME_APPLICATION_XML, dataSource.getContentType());
+        assertEquals(ANY_FILE.toURI(), dataSource.getUri());
         assertTrue(!dataSource.getLines().isEmpty());
     }
 
     @Test
-    public void shouldCreateStringBasedDatasource() throws IOException {
-        final DataSource dataSource = DataSourceFactory.create("test.txt", "default", ANY_TEXT);
+    public void shouldCreateDataSourceFromString() throws IOException {
+        final DataSource dataSource = DataSourceFactory.fromString("test.txt", "default", ANY_TEXT, "text/plain");
 
         assertEquals("test.txt", dataSource.getName());
         assertEquals("default", dataSource.getGroup());
         assertEquals(UTF_8, dataSource.getCharset());
-        assertEquals("string", dataSource.getLocation());
+        assertTrue(dataSource.getUri().toString().startsWith("string:///"));
         assertEquals(ANY_TEXT, dataSource.getText());
         assertEquals(1, dataSource.getLines().size());
     }
 
     @Test
-    public void shouldCreateByteArrayBasedDatasource() throws IOException {
-        final DataSource dataSource = DataSourceFactory.create("test.txt", "default", ANY_TEXT.getBytes(UTF_8));
+    public void shouldCreateDataSourceFromBytes() throws IOException {
+        final DataSource dataSource = DataSourceFactory.fromBytes("test.txt", "default", ANY_TEXT.getBytes(UTF_8), "text/plain");
 
         assertEquals("test.txt", dataSource.getName());
         assertEquals("default", dataSource.getGroup());
         assertEquals(UTF_8, dataSource.getCharset());
-        assertEquals("bytes", dataSource.getLocation());
+        assertTrue(dataSource.getUri().toString().startsWith("bytes:///"));
         assertEquals(ANY_TEXT, dataSource.getText());
         assertEquals(1, dataSource.getLines().size());
     }
 
     @Test
-    public void shouldCreateInputStreamBasedDatasource() throws IOException {
+    public void shouldCreateDataSourceFromInputStream() throws IOException {
         final InputStream is = new ByteArrayInputStream(ANY_TEXT.getBytes(UTF_8));
-        final DataSource dataSource = DataSourceFactory.create("test.txt", "default", is, UTF_8);
+        final DataSource dataSource = DataSourceFactory.fromInputStream("test.txt", "default", is, "text/plain", UTF_8);
 
         assertEquals("test.txt", dataSource.getName());
         assertEquals(UTF_8, dataSource.getCharset());
-        assertEquals("inputstream", dataSource.getLocation());
+        assertTrue(dataSource.getUri().toString().startsWith("inputstream:///"));
         assertEquals(ANY_TEXT, dataSource.getText());
     }
-
 }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourceTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
similarity index 76%
rename from freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourceTest.java
rename to freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
index 5fe5f72..040658e 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourceTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
@@ -17,6 +17,7 @@
 package org.apache.freemarker.generator.datasource;
 
 import org.apache.commons.io.LineIterator;
+import org.apache.freemarker.generator.base.activation.Mimetypes;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
 import org.junit.Ignore;
@@ -25,47 +26,47 @@ import org.junit.Test;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
-import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.Iterator;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-public class DatasourceTest {
+public class DataSourceTest {
 
     private static final String ANY_GROUP = "group";
     private static final String ANY_TEXT = "Hello World";
-    private static final String ANY_FILE_NAME = "pom.xml";
+    private static final String ANY_XML_FILE_NAME = "pom.xml";
     private static final Charset ANY_CHAR_SET = UTF_8;
-    private static final File ANY_FILE = new File(ANY_FILE_NAME);
+    private static final File ANY_FILE = new File(ANY_XML_FILE_NAME);
 
     @Test
-    public void shouldSupportTextDatasource() throws IOException {
-        try (DataSource dataSource = DataSourceFactory.create("stdin", ANY_GROUP, ANY_TEXT)) {
+    public void shouldSupportTextDataSource() throws IOException {
+        try (DataSource dataSource = DataSourceFactory.fromString("stdin", ANY_GROUP, ANY_TEXT, Mimetypes.MIME_TEXT_PLAIN)) {
             assertEquals("stdin", dataSource.getName());
             assertEquals(ANY_GROUP, dataSource.getGroup());
             assertEquals("stdin", dataSource.getBaseName());
             assertEquals("", dataSource.getExtension());
-            assertEquals("string", dataSource.getLocation());
+            assertTrue(dataSource.getUri().toString().startsWith("string:///"));
             assertEquals(UTF_8, dataSource.getCharset());
-            assertEquals("plain/text", dataSource.getContentType());
+            assertEquals("text/plain", dataSource.getContentType());
             assertTrue(dataSource.getLength() > 0);
             assertEquals(ANY_TEXT, dataSource.getText());
         }
     }
 
     @Test
-    public void shouldSupportFileDatasource() throws IOException {
-        try (DataSource dataSource = DataSourceFactory.create(ANY_FILE, ANY_CHAR_SET)) {
-            assertEquals(ANY_FILE_NAME, dataSource.getName());
+    public void shouldSupportFileDataSource() throws IOException {
+        try (DataSource dataSource = DataSourceFactory.fromFile(ANY_FILE, ANY_CHAR_SET)) {
+            assertEquals(ANY_XML_FILE_NAME, dataSource.getName());
             assertEquals(DEFAULT_GROUP, dataSource.getGroup());
             assertEquals("pom", dataSource.getBaseName());
             assertEquals("xml", dataSource.getExtension());
-            assertEquals(ANY_FILE.getAbsolutePath(), dataSource.getLocation());
+            assertEquals(ANY_FILE.toURI().toString(), dataSource.getUri().toString());
             assertEquals(Charset.defaultCharset(), dataSource.getCharset());
             assertEquals("application/xml", dataSource.getContentType());
             assertTrue(dataSource.getLength() > 0);
@@ -75,14 +76,14 @@ public class DatasourceTest {
 
     @Ignore("Requires internet conenection")
     @Test
-    public void shouldSupportUrlDatasource() throws IOException {
-        try (DataSource dataSource = DataSourceFactory.create(new URL("https://google.com?foo=bar"))) {
+    public void shouldSupportUrlDataSource() throws IOException {
+        try (DataSource dataSource = DataSourceFactory.create("https://google.com?foo=bar")) {
             assertEquals("google.com", dataSource.getName());
             assertEquals(DEFAULT_GROUP, dataSource.getGroup());
             assertEquals("google", dataSource.getBaseName());
             assertEquals("com", dataSource.getExtension());
-            assertEquals("https://google.com", dataSource.getLocation());
-            assertEquals("text/html; charset=ISO-8859-1", dataSource.getContentType());
+            assertEquals("https://google.com?foo=bar", dataSource.getUri().toString());
+            assertEquals(MIME_APPLICATION_OCTET_STREAM, dataSource.getContentType());
             assertEquals(UTF_8, dataSource.getCharset());
             assertEquals(-1, dataSource.getLength());
             assertFalse(dataSource.getText().isEmpty());
@@ -114,7 +115,7 @@ public class DatasourceTest {
     }
 
     @Test
-    public void shouldCloseDatasource() {
+    public void shouldCloseDataSource() {
         final DataSource dataSource = textDataSource();
         final TestClosable closable1 = dataSource.addClosable(new TestClosable());
         final TestClosable closable2 = dataSource.addClosable(new TestClosable());
@@ -135,7 +136,7 @@ public class DatasourceTest {
     }
 
     private static DataSource textDataSource() {
-        return DataSourceFactory.create("stdin", "default", ANY_TEXT);
+        return DataSourceFactory.fromString("stdin", "default", ANY_TEXT, "text/plain");
     }
 
     private static final class TestClosable implements Closeable {
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourcesSupplierTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java
similarity index 75%
rename from freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourcesSupplierTest.java
rename to freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java
index 5f985c3..2bc5d73 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourcesSupplierTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java
@@ -28,15 +28,21 @@ import static java.util.Collections.singletonList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
-public class DatasourcesSupplierTest {
+public class DataSourcesSupplierTest {
 
     private static final String NO_EXCLUDE = null;
-    private static final String ANY_FILE = "./pom.xml";
     private static final String ANY_DIRECTORY = "./src/test/data";
+    private static final String ANY_ENVIRONMENT_VARIABLE = "PATH";
 
     @Test
     public void shouldResolveSingleFile() {
-        assertEquals(1, supplier(ANY_FILE, "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("./pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("file://./pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=./pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=file://./pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=file://./pom.xml?mimetype=application/xml", "*", NO_EXCLUDE).get().size());
     }
 
     @Test
@@ -84,6 +90,12 @@ public class DatasourcesSupplierTest {
     }
 
     @Test
+    public void shouldResolveEnvironmentVariable() {
+        assertEquals(1, supplier("env:///PATH", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("path=env:///PATH", "*", NO_EXCLUDE).get().size());
+    }
+
+    @Test
     public void shouldResolveLargeDirectory() {
         final List<DataSource> dataSources = supplier(".", null, null).get();
         assertFalse(dataSources.isEmpty());
@@ -100,6 +112,15 @@ public class DatasourcesSupplierTest {
         assertEquals(2, supplier(sources, "*.xml", null).get().size());
     }
 
+    @Test
+    public void shouldNormalizeDataSourceNameBasedOnFilePath() {
+        assertEquals("pom.xml", supplier("pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
+        assertEquals("pom.xml", supplier("./pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
+        assertEquals("pom.xml", supplier("file://./pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
+        assertEquals("pom.xml", supplier("file:///pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
+        assertEquals("pom.xml", supplier("file://tmp/pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
+    }
+
     private static DataSourcesSupplier supplier(String directory, String include, String exclude) {
         return new DataSourcesSupplier(singletonList(directory), include, exclude, Charset.defaultCharset());
     }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourcesTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
similarity index 84%
rename from freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourcesTest.java
rename to freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
index 1b64b56..b4cf812 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DatasourcesTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
@@ -22,8 +22,6 @@ import org.apache.freemarker.generator.base.datasource.DataSources;
 import org.junit.Test;
 
 import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.asList;
@@ -33,7 +31,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 
-public class DatasourcesTest {
+public class DataSourcesTest {
 
     private static final String UNKNOWN = "unknown";
     private static final String ANY_TEXT = "Hello World";
@@ -81,12 +79,12 @@ public class DatasourcesTest {
     }
 
     @Test
-    public void shouldGetDatasource() {
+    public void shouldGetDataSource() {
         assertNotNull(dataSources().get(ANY_FILE_NAME));
     }
 
     @Test
-    public void shouldGetAllDatasources() {
+    public void shouldGetAllDataSource() {
         final DataSources dataSources = dataSources();
 
         assertEquals("unknown", dataSources().get(0).getName());
@@ -109,32 +107,28 @@ public class DatasourcesTest {
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void shouldThrowExceptionWhenGetDoesNotFindDatasource() {
+    public void shouldThrowExceptionWhenGetDoesNotFindDataSource() {
         dataSources().get("file-does-not-exist");
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void shouldThrowExceptionWhenGetFindsMultipleDatasources() {
+    public void shouldThrowExceptionWhenGetFindsMultipleDataSources() {
         dataSources().get("*");
     }
 
     private static DataSources dataSources() {
-        return new DataSources(asList(textDatasource(), fileDatasource(), urlDatasource()));
+        return new DataSources(asList(textDataSource(), fileDataSource(), urlDataSource()));
     }
 
-    private static DataSource textDatasource() {
-        return DataSourceFactory.create(UNKNOWN, DEFAULT_GROUP, ANY_TEXT);
+    private static DataSource textDataSource() {
+        return DataSourceFactory.fromString(UNKNOWN, DEFAULT_GROUP, ANY_TEXT, "text/plain");
     }
 
-    private static DataSource fileDatasource() {
-        return DataSourceFactory.create(ANY_FILE, UTF_8);
+    private static DataSource fileDataSource() {
+        return DataSourceFactory.fromFile(ANY_FILE, UTF_8);
     }
 
-    private static DataSource urlDatasource() {
-        try {
-            return DataSourceFactory.create(new URL(ANY_URL));
-        } catch (MalformedURLException e) {
-            throw new RuntimeException(e);
-        }
+    private static DataSource urlDataSource() {
+        return DataSourceFactory.create(ANY_URL);
     }
 }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/UriTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/UriTest.java
index cce6273..a231d77 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/UriTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/UriTest.java
@@ -21,6 +21,7 @@ import org.junit.Test;
 import java.net.URI;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 public class UriTest {
 
@@ -50,6 +51,24 @@ public class UriTest {
     }
 
     @Test
+    public void shouldParseSystemInUri() throws Exception {
+        final URI uri = new URI("system:///in");
+
+        assertEquals("system", uri.getScheme());
+        assertEquals("/in", uri.getPath());
+        assertEquals("system:///in", uri.toASCIIString());
+    }
+
+    @Test
+    public void shouldParseInputStreamUri() throws Exception {
+        final URI uri = new URI("inputstream:///1234");
+
+        assertEquals("inputstream", uri.getScheme());
+        assertEquals("/1234", uri.getPath());
+        assertEquals("inputstream:///1234", uri.toASCIIString());
+    }
+
+    @Test
     public void shouldParseFileUri() throws Exception {
         final URI uri = new URI("file:///tmp/my/file.json");
 
@@ -62,6 +81,7 @@ public class UriTest {
     public void shouldParseFileNameOnlyUri() throws Exception {
         final URI uri = new URI("file.json");
 
+        assertNull(uri.getScheme());
         assertEquals("file.json", uri.getPath());
         assertEquals("file.json", uri.toASCIIString());
     }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java
similarity index 83%
rename from freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java
rename to freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java
index 06718a0..f007064 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java
@@ -17,13 +17,15 @@
 package org.apache.freemarker.generator.uri;
 
 import org.apache.freemarker.generator.base.uri.NamedUri;
-import org.apache.freemarker.generator.base.uri.NamedUriParser;
+import org.apache.freemarker.generator.base.uri.NamedUriStringParser;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
-public class NamedUriParserTest {
+public class NamedUriStringParserTest {
 
     @Test
     public void shouldParseRelativeFileName() {
@@ -32,6 +34,7 @@ public class NamedUriParserTest {
         assertNull(namedURI.getName());
         assertNull(namedURI.getGroup());
         assertEquals("users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
@@ -42,6 +45,7 @@ public class NamedUriParserTest {
         assertNull(namedURI.getName());
         assertNull(namedURI.getGroup());
         assertEquals("/data/users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
@@ -52,6 +56,7 @@ public class NamedUriParserTest {
         assertNull(namedURI.getName());
         assertNull(namedURI.getGroup());
         assertEquals("users/", namedURI.getUri().toString());
+        assertEquals("users", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
@@ -62,16 +67,31 @@ public class NamedUriParserTest {
         assertNull(namedURI.getName());
         assertNull(namedURI.getGroup());
         assertEquals("file:///users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
     @Test
+    public void shouldParseNamedFileName() {
+        final NamedUri namedURI = parse("users=users.csv");
+
+        assertEquals("users", namedURI.getName());
+        assertNull(namedURI.getGroup());
+        assertEquals("file:///users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
+        assertEquals(0, namedURI.getParameters().size());
+        assertTrue(namedURI.hasName());
+        assertFalse(namedURI.hasGroup());
+    }
+
+    @Test
     public void shouldParseNamedFileUri() {
         final NamedUri namedURI = parse("users=file:///users.csv");
 
         assertEquals("users", namedURI.getName());
         assertNull(namedURI.getGroup());
         assertEquals("file:///users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
@@ -82,7 +102,10 @@ public class NamedUriParserTest {
         assertEquals("users", namedURI.getName());
         assertEquals("admin", namedURI.getGroup());
         assertEquals("file:///some-admin-users.csv", namedURI.getUri().toString());
+        assertEquals("some-admin-users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
+        assertTrue(namedURI.hasName());
+        assertTrue(namedURI.hasGroup());
     }
 
     @Test
@@ -92,6 +115,7 @@ public class NamedUriParserTest {
         assertEquals("users", namedURI.getName());
         assertNull(namedURI.getGroup());
         assertEquals("file:///some-admin-users.csv", namedURI.getUri().toString());
+        assertEquals("some-admin-users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
@@ -102,6 +126,7 @@ public class NamedUriParserTest {
         assertNull(namedURI.getName());
         assertEquals("admin", namedURI.getGroup());
         assertEquals("file:///some-admin-users.csv", namedURI.getUri().toString());
+        assertEquals("some-admin-users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
     }
 
@@ -187,6 +212,6 @@ public class NamedUriParserTest {
     }
 
     private static NamedUri parse(String value) {
-        return NamedUriParser.parse(value);
+        return NamedUriStringParser.parse(value);
     }
 }
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
index 798c803..e890120 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
@@ -25,8 +25,11 @@ import org.apache.freemarker.generator.cli.task.FreeMarkerTask;
 import picocli.CommandLine;
 import picocli.CommandLine.ArgGroup;
 import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
 import picocli.CommandLine.Option;
+import picocli.CommandLine.ParameterException;
 import picocli.CommandLine.Parameters;
+import picocli.CommandLine.Spec;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -68,7 +71,7 @@ public class Main implements Callable<Integer> {
     @Option(names = { "-b", "--basedir" }, description = "Optional template base directory")
     String baseDir;
 
-    @Option(names = { "-d", "--data-source" }, description = "Datasource used for rendering")
+    @Option(names = { "-d", "--data-source" }, description = "Data source used for rendering")
     List<String> dataSources;
 
     @Option(names = { "-D", "--system-property" }, description = "Set system property")
@@ -119,6 +122,9 @@ public class Main implements Callable<Integer> {
     /** User-supplied writer (used mainly for unit testing) */
     Writer userSuppliedWriter;
 
+    /** Injected by Picolci */
+    @Spec private CommandSpec spec;
+
     Main() {
         this.args = new String[0];
     }
@@ -158,6 +164,7 @@ public class Main implements Callable<Integer> {
 
     @Override
     public Integer call() {
+        validate();
         return IntStream.range(0, times).map(i -> onCall()).max().orElse(0);
     }
 
@@ -178,6 +185,25 @@ public class Main implements Callable<Integer> {
         }
     }
 
+    private void validate() {
+        // "-d" or "--data-source" parameter shall not contain wildcard characters
+        if (dataSources != null) {
+            for (String source : dataSources) {
+                if (isFileSource(source) && (source.contains("*") || source.contains("?"))) {
+                    throw new ParameterException(spec.commandLine(), "No wildcards supported for data source: " + source);
+                }
+            }
+        }
+
+        // "-t" or "--template" parameter shall not contain wildcard characters
+        if (StringUtils.isNotEmpty(templateSourceOptions.template)) {
+            final String source = templateSourceOptions.template;
+            if (isFileSource(source) && (source.contains("*") || source.contains("?"))) {
+                throw new ParameterException(spec.commandLine(), "No wildcards supported for template: " + source);
+            }
+        }
+    }
+
     private Settings settings(Properties configuration, List<File> templateDirectories) {
         return Settings.builder()
                 .isEnvironmentExposed(isEnvironmentExposed)
@@ -192,7 +218,7 @@ public class Main implements Callable<Integer> {
                 .setOutputEncoding(outputEncoding)
                 .setOutputFile(outputFile)
                 .setParameters(parameters != null ? parameters : new HashMap<>())
-                .setDataSources(getCombindedDatasources())
+                .setDataSources(getCombindedDataSources())
                 .setSystemProperties(systemProperties != null ? systemProperties : new Properties())
                 .setTemplateDirectories(templateDirectories)
                 .setTemplateName(templateSourceOptions.template)
@@ -221,12 +247,12 @@ public class Main implements Callable<Integer> {
     }
 
     /**
-     * Datasources can be passed via command line option and/or
+     * Data sources can be passed via command line option and/or
      * positional parameter so we need to merge them.
      *
      * @return List of data sources
      */
-    private List<String> getCombindedDatasources() {
+    private List<String> getCombindedDataSources() {
         if (isTemplateDrivenGeneration()) {
             return Stream.of(dataSources, sources)
                     .filter(Objects::nonNull)
@@ -253,4 +279,14 @@ public class Main implements Callable<Integer> {
             throw new RuntimeException("FreeMarker CLI configuration file not found: " + fileName);
         }
     }
+
+    private static boolean isFileSource(String source) {
+        if (source.contains("file://")) {
+            return true;
+        } else if (source.contains("://")) {
+            return false;
+        } else {
+            return true;
+        }
+    }
 }
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
index 7271644..0fa4482 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
@@ -23,11 +23,13 @@ import org.apache.freemarker.generator.base.FreeMarkerConstants.Location;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
 import org.apache.freemarker.generator.base.datasource.DataSources;
+import org.apache.freemarker.generator.base.util.UriUtils;
 import org.apache.freemarker.generator.cli.config.Settings;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.Writer;
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -90,7 +92,8 @@ public class FreeMarkerTask implements Callable<Integer> {
         // Add optional data source from STDIN at the start of the list since
         // this allows easy sequence slicing in FreeMarker.
         if (settings.isReadFromStdin()) {
-            dataSources.add(0, DataSourceFactory.create(STDIN, DEFAULT_GROUP, System.in, STDIN, UTF_8));
+            final URI uri = UriUtils.toURI(Location.SYSTEM, "in");
+            dataSources.add(0, DataSourceFactory.fromInputStream(STDIN, DEFAULT_GROUP, uri, System.in, "text/plain", UTF_8));
         }
 
         return new DataSources(dataSources);
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
index 325ec50..6df1d79 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
@@ -36,7 +36,8 @@ public class ManualTest {
     // private static final String CMD = "-b ./src/test -t templates/properties/csv/locker-test-users.ftl site/sample/properties";
     // private static final String CMD = "-b ./src/test -e UTF-8 -l de_AT -Dcolumn=Order%20ID -Dvalues=226939189,957081544 -Dformat=DEFAULT -Ddelimiter=COMMA -t templates/csv/md/filter.ftl site/sample/csv/sales-records.csv";
     // private static final String CMD = "-E -b ./src/test -t templates/environment.ftl";
-    private static final String CMD = "-b ./src/test -l de_AT -DFOO=foo -DBAR=bar -t templates/info.ftl -d user:admin=site/sample/csv/contract.csv#charset=UTF-16 google:www=https://www.google.com?foo=bar#contenttype=application/json";
+    // private static final String CMD = "-b ./src/test -l de_AT -DFOO=foo -DBAR=bar -t templates/info.ftl -d user:admin=site/sample/csv/contract.csv#charset=UTF-16 google:www=https://www.google.com?foo=bar#contenttype=application/json";
+    private static final String CMD = "-b ./src/test -t templates/info.ftl -d env:///";
 
     public static void main(String[] args) {
         Main.execute(toArgs(CMD));
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
index 2c54c44..fdd7fc2 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
@@ -50,7 +50,7 @@ public class PicocliTest {
     }
 
     @Test
-    public void testSingleNamedDatasource() {
+    public void testSingleNamedDataSource() {
         assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE).sources.get(0));
         assertEquals(ANY_FILE, parse("-t", TEMPLATE, "-d", ANY_FILE).dataSources.get(0));
         assertEquals(ANY_FILE, parse("-t", TEMPLATE, "--data-source", ANY_FILE).dataSources.get(0));
@@ -58,7 +58,7 @@ public class PicocliTest {
     }
 
     @Test
-    public void testMultipleNamedDatasource() {
+    public void testMultipleNamedDataSource() {
         final Main main = parse("-t", TEMPLATE, "-d", ANY_FILE, "--data-source", OTHER_FILE_URI);
 
         assertEquals(ANY_FILE, main.dataSources.get(0));
diff --git a/freemarker-generator-cli/src/test/templates/echo.ftl b/freemarker-generator-cli/src/test/templates/echo.ftl
index b7c7b88..2614f49 100644
--- a/freemarker-generator-cli/src/test/templates/echo.ftl
+++ b/freemarker-generator-cli/src/test/templates/echo.ftl
@@ -16,7 +16,7 @@
   under the License.
 -->
 <#list DataSources.list as dataSource>
-${dataSource.name}, ${dataSource.location}
+${dataSource.name}, ${dataSource.uri}
 =============================================================================
 ${dataSource.text}
 </#list>
\ No newline at end of file
diff --git a/freemarker-generator-cli/templates/demo.ftl b/freemarker-generator-cli/templates/demo.ftl
index 06359f4..ec9fe3a 100644
--- a/freemarker-generator-cli/templates/demo.ftl
+++ b/freemarker-generator-cli/templates/demo.ftl
@@ -49,7 +49,7 @@ java.math.RoundingMode#UP: ${FreeMarkerTool.enums["java.math.RoundingMode"].UP}
 ---------------------------------------------------------------------------
 List all data sources:
 <#list DataSources.list as dataSource>
-- Document: name=${dataSource.name} location=${dataSource.location} length=${dataSource.length} encoding=${dataSource.encoding!""}
+- Document: name=${dataSource.name} uri=${dataSource.uri} length=${dataSource.length} encoding=${dataSource.encoding!""}
 </#list>
 
 7) SystemTool
@@ -101,7 +101,7 @@ List all files having "md" extension
 </#list>
 Get all documents
 <#list DataSources.list as dataSource>
-- ${dataSource.name} => ${dataSource.location}
+- ${dataSource.name} => ${dataSource.uri}
 </#list>
 
 12) FreeMarker CLI Tools
diff --git a/freemarker-generator-cli/templates/info.ftl b/freemarker-generator-cli/templates/info.ftl
index e25f960..01384c3 100644
--- a/freemarker-generator-cli/templates/info.ftl
+++ b/freemarker-generator-cli/templates/info.ftl
@@ -42,8 +42,8 @@ FreeMarker CLI Tools
 FreeMarker CLI DataSources
 ------------------------------------------------------------------------------
 <#list DataSources.list as dataSource>
-[#${dataSource?counter}], name=${dataSource.name}, group=${dataSource.group}, charset=${dataSource.charset}, length= ${dataSource.length} Bytes
-Location : ${dataSource.location}
+[#${dataSource?counter}], name=${dataSource.name}, group=${dataSource.group}, contentType=${dataSource.contentType}, charset=${dataSource.charset}, length=${dataSource.length} Bytes
+URI : ${dataSource.uri}
 </#list>
 
 User Supplied Parameters
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/commonscsv/CommonsCSVToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/commonscsv/CommonsCSVToolTest.java
index 368f54c..f76e252 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/commonscsv/CommonsCSVToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/commonscsv/CommonsCSVToolTest.java
@@ -144,7 +144,7 @@ public class CommonsCSVToolTest {
     }
 
     private DataSource dataSource(File file) {
-        return DataSourceFactory.create(file, UTF_8);
+        return DataSourceFactory.fromFile(file, UTF_8);
     }
 
     private CommonsCSVTool commonsCsvTool() {
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/excel/ExcelToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/excel/ExcelToolTest.java
index 7694a95..9c8ef8b 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/excel/ExcelToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/excel/ExcelToolTest.java
@@ -110,6 +110,6 @@ public class ExcelToolTest {
     }
 
     private DataSource dataSource(File file) {
-        return DataSourceFactory.create(file, UTF_8);
+        return DataSourceFactory.fromFile(file, UTF_8);
     }
 }
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/properties/PropertiesToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/properties/PropertiesToolTest.java
index 388c1a5..8b49649 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/properties/PropertiesToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/properties/PropertiesToolTest.java
@@ -28,7 +28,7 @@ public class PropertiesToolTest {
     private static final String ANY_PROPERTIES_STRING = "foo=bar";
 
     @Test
-    public void shallParsePropertiesDatasource() {
+    public void shallParsePropertiesDataSource() {
         try (DataSource dataSource = dataSource(ANY_PROPERTIES_STRING)) {
             assertEquals("bar", propertiesTool().parse(dataSource).getProperty("foo"));
         }
@@ -44,6 +44,6 @@ public class PropertiesToolTest {
     }
 
     private DataSource dataSource(String value) {
-        return DataSourceFactory.create("test.properties", ANY_GROUP, value);
+        return DataSourceFactory.fromString("test.properties", ANY_GROUP, value, "text/plain");
     }
 }
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java
index 0f3375c..f379995 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.freemarker.generator.tools.snakeyaml;
 
+import org.apache.freemarker.generator.base.activation.Mimetypes;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
 import org.junit.Test;
@@ -36,7 +37,7 @@ public class SnakeYamlToolTest {
             "    - image: postgres:9.4.1";
 
     @Test
-    public void shallParseYamlDatasource() {
+    public void shallParseYamlDataSource() {
         try (DataSource dataSource = dataSource(ANY_YAML_STRING)) {
             final Map<String, Object> map = snakeYamlTool().parse(dataSource);
 
@@ -58,6 +59,6 @@ public class SnakeYamlToolTest {
     }
 
     private DataSource dataSource(String value) {
-        return DataSourceFactory.create("test.yml", ANY_GROUP, value);
+        return DataSourceFactory.fromString("test.yml", ANY_GROUP, value, Mimetypes.MIME_TEXT_YAML);
     }
 }
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java
index 42f9dfd..475f8e9 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java
@@ -23,6 +23,7 @@ import org.junit.Test;
 
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNotNull;
+import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XML;
 
 public class XmlToolTest {
 
@@ -37,7 +38,7 @@ public class XmlToolTest {
             "</note>";
 
     @Test
-    public void shallParseXmlDatasource() throws Exception {
+    public void shallParseXmlDataSource() throws Exception {
         try (DataSource dataSource = dataSource(ANY_XML_STRING)) {
             final NodeModel model = xmlTool().parse(dataSource);
 
@@ -59,6 +60,6 @@ public class XmlToolTest {
     }
 
     private DataSource dataSource(String value) {
-        return DataSourceFactory.create("test.xml", ANY_GROUP, value);
+        return DataSourceFactory.fromString("test.xml", ANY_GROUP, value, MIME_APPLICATION_XML);
     }
 }
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 052ade0..f530805 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -3,6 +3,6 @@ The Apache FreeMarker Generator projects provides additional tools to generate t
 | Name                  | Description                                                       |
 | --------------------- | ----------------------------------------------------------------- |
 | Base                  | Common functionality independent from Apache FreeMarker           |
-| Tools                 | Datasource processing tools for Apache FreeMarker Generator       |
+| Tools                 | Data source processing tools for Apache FreeMarker Generator      |
 | CLI                   | Command-line client for Apache FreeMarker                         |
 | Maven Plugin          | Maven plugin for Apache FreeMarker                                |
\ No newline at end of file