Skip to content

Commit

Permalink
make PreparedTemplate instances clean up when discarded (#12)
Browse files Browse the repository at this point in the history
Originally a PreparedTemplate instance needs a temporary folder in the file system to hold its contents unzipped. Now the instances can clean up these directories when discarded either by calling cleanup() or finalize(). Also, these directories are marked with deleteOnExit.
  • Loading branch information
erdos authored and Janos Erdos committed Oct 22, 2018
1 parent 47ea515 commit d52148a
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 36 deletions.
1 change: 1 addition & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The public Java API is accessible in the `io.github.erdos.stencil.API` class.

1. First, we have to prepare a template file. Call `API.prepare()` funtion with the template file.
2. Second, we can render the prepared template using the `API.render()` function.
3. When you do not use a prepared template instance any more, call its `cleanup()` method to free allocated resources.

The following example takes a template from the file system, fills it with data
from the arguments and writes the rendered document back to the file system.
Expand Down
13 changes: 6 additions & 7 deletions java-src/io/github/erdos/stencil/PreparedTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@
public interface
PreparedTemplate {

/**
* Name of the original file.
*
* @return original template name
*/
String getName();

/**
* Original template file that was preprocessed.
*
Expand Down Expand Up @@ -56,4 +49,10 @@ default TemplateDocumentFormats getTemplateFormat() {
* Set of template variables found in file.
*/
TemplateVariables getVariables();

/**
* Makes the templace clean up any resources allocated for it. Subsequential invocations of this method have no
* effects. Rendering the template after this method call will throw an IllegalStateException.
*/
void cleanup();
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public PreparedTemplate prepareTemplateFile(File templateFile) throws IOExceptio
if (cache.containsKey(templateFile)) {
PreparedTemplate stored = cache.get(templateFile);
if (stored.creationDateTime().toEpochSecond(ZoneOffset.UTC) <= templateFile.lastModified()) {
// TODO: this is so not thread safe.
stored.cleanup();
stored = templateFactory.prepareTemplateFile(templateFile);
cache.put(templateFile, stored);
}
Expand Down
30 changes: 18 additions & 12 deletions java-src/io/github/erdos/stencil/impl/ClojureHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,40 @@
public class ClojureHelper {

/**
* Clojure :stream keyword
* Clojure :data keyword
*/
public static final Keyword KV_STREAM = Keyword.intern("stream");
public static final Keyword KV_DATA = Keyword.intern("data");

/**
* Clojure :variables keyword
* Clojure :format keyword
*/
public static final Keyword KV_VARIABLES = Keyword.intern("variables");
public static final Keyword KV_FORMAT = Keyword.intern("format");

/**
* Clojure :template keyword
* Clojure :function keyword
*/
public static final Keyword KV_TEMPLATE = Keyword.intern("template");
public static final Keyword KV_FUNCTION = Keyword.intern("function");

/**
* Clojure :data keyword
* Clojure :stream keyword
*/
public static final Keyword KV_DATA = Keyword.intern("data");
public static final Keyword KV_STREAM = Keyword.intern("stream");

/**
* Clojure :format keyword
* Clojure :template keyword
*/
public static final Keyword KV_FORMAT = Keyword.intern("format");
public static final Keyword KV_TEMPLATE = Keyword.intern("template");

/**
* Clojure :function keyword
* Clojure :variables keyword
*/
public static final Keyword KV_FUNCTION = Keyword.intern("function");
public static final Keyword KV_VARIABLES = Keyword.intern("variables");

/**
* Clojure :zip-dir keyword
*/

public static final Keyword KV_ZIP_DIR = Keyword.intern("zip-dir");

// requires stencil.process namespace so stencil is loaded.
static {
Expand Down
32 changes: 32 additions & 0 deletions java-src/io/github/erdos/stencil/impl/FileHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,36 @@ public static void forceMkdir(final File directory) throws IOException {
}
}
}

/**
* Recursively deletes a directory or a file.
*
* @param file to delete, not null
* @throws NullPointerException on null or invalid file
*/
@SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"})
public static void forceDelete(final File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
forceDelete(child);
}
}
file.delete();
}

/**
* Recursively marks a directory or a file for deletion on exit.
*
* @param file to delete, not null
* @throws NullPointerException on null or invalid file
*/
@SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"})
public static void forceDeleteOnExit(final File file) {
file.deleteOnExit();
if (file.isDirectory()) {
for (File child : file.listFiles()) {
forceDeleteOnExit(child);
}
}
}
}
2 changes: 1 addition & 1 deletion java-src/io/github/erdos/stencil/impl/NativeEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public EvaluatedDocument render(PreparedTemplate template, TemplateData data) {
}

final Consumer<Supplier<String>> stopwatch = debugStopWatch(LOGGER);
stopwatch.accept(() -> "Starting document rendering for template " + template.getName());
stopwatch.accept(() -> "Starting document rendering for template " + template.getTemplateFile());

final IFn fn = ClojureHelper.findFunction("do-eval-stream");

Expand Down
45 changes: 34 additions & 11 deletions java-src/io/github/erdos/stencil/impl/NativeTemplateFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

import static io.github.erdos.stencil.TemplateDocumentFormats.ofExtension;
import static io.github.erdos.stencil.impl.ClojureHelper.KV_ZIP_DIR;
import static io.github.erdos.stencil.impl.FileHelper.forceDeleteOnExit;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;

Expand All @@ -31,7 +34,7 @@ public PreparedTemplate prepareTemplateFile(final File inputTemplateFile) throws
}

try (InputStream input = new FileInputStream(inputTemplateFile)) {
return prepareTemplateImpl(templateDocFormat.get(), input);
return prepareTemplateImpl(templateDocFormat.get(), input, inputTemplateFile);
}
}

Expand All @@ -46,7 +49,7 @@ private Set variableNames(Map prepared) {
}

@SuppressWarnings("unchecked")
private PreparedTemplate prepareTemplateImpl(TemplateDocumentFormats templateDocFormat, InputStream input) {
private PreparedTemplate prepareTemplateImpl(TemplateDocumentFormats templateDocFormat, InputStream input, File originalFile) {
final IFn prepareFunction = ClojureHelper.findFunction("prepare-template");

final String format = templateDocFormat.name();
Expand All @@ -60,20 +63,20 @@ private PreparedTemplate prepareTemplateImpl(TemplateDocumentFormats templateDoc
throw ParsingException.wrapping("Could not parse template file!", e);
}

final String templateName = (String) prepared.get(Keyword.intern("template-name"));
final File templateFile = (File) prepared.get(Keyword.intern("template-file"));
final LocalDateTime now = LocalDateTime.now();
final TemplateVariables vars = TemplateVariables.fromPaths(variableNames(prepared));

final File zipDirResource = (File) prepared.get(KV_ZIP_DIR);
if (zipDirResource != null) {
forceDeleteOnExit(zipDirResource);
}

return new PreparedTemplate() {
@Override
public String getName() {
return templateName;
}
final LocalDateTime now = LocalDateTime.now();
final AtomicBoolean valid = new AtomicBoolean(true);

@Override
public File getTemplateFile() {
return templateFile;
return originalFile;
}

@Override
Expand All @@ -88,13 +91,33 @@ public LocalDateTime creationDateTime() {

@Override
public Object getSecretObject() {
return prepared;
if (!valid.get()) {
throw new IllegalStateException("Can not render destroyed template!");
} else {
return prepared;
}
}

@Override
public TemplateVariables getVariables() {
return vars;
}

@Override
public void cleanup() {
if (valid.compareAndSet(false, true)) {
// deletes unused temporary zip directory
if (zipDirResource != null) {
FileHelper.forceDelete(zipDirResource);
}
}
}

// in case we forgot to call it by hand.
@Override
public void finalize() {
cleanup();
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ public PreparedTemplate prepareTemplateFile(File templateFile) {
calledFiles.add(templateFile);
return new PreparedTemplate() {

@Override
public String getName() {
return templateFile.getName();
}

@Override
public File getTemplateFile() {
return templateFile;
Expand All @@ -110,6 +105,12 @@ public Object getSecretObject() {
public TemplateVariables getVariables() {
return null;
}

@Override
public void cleanup() {
// NOP

}
};
}
}

0 comments on commit d52148a

Please sign in to comment.