-Dupload.filesize-max=2097152
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<head>
<link rel="stylesheet" type="text/css" href="${context:css/examples/javascript/fileupload.css}"/>
</head>
<body>
<h1>File Upload</h1>
<noscript class="js-required">
${message:javascript_required}
</noscript>
This example demonstrates Tapestry's Upload component for uploading files. <br/><br/>
To improve the user experience, we've added some JavaScript. It hides stale results messages, shows a progress message,
and enables cancel during long uploads.<br/><br/>
To keep the page class simple, we've moved all file handling into a service that we've called Filer.<br/>
<div class="eg">
<form t:type="CustomForm" t:id="uploadForm">
<div id="notes">
${fileSizeMaxMessage}
</div>
<input t:type="Upload" t:id="file" t:clientid="prop:fileId" t:validate="required" onClick="fileUpload.hideResults();" t:disabled="demoMode"/><br/>
<input t:type="Submit" t:id="upload" value="Upload" onClick="fileUpload.showProgress();" t:disabled="demoMode"/><br/>
<div id="${progressId}" style="display: none;">
${message:progress}
<button t:type="chenillekit/Button" class="cancel" type="button" t:pageName="prop:thisPageName"
title="Cancel" onClick="this.form.reset(); return true;">Cancel</button>
</div>
<div id="${resultId}">
<t:customerror for="file"/>
<div id="success">
${successMessage}
</div>
<t:errors />
</div>
<t:if test="demoMode">
<div id="demo-mode">Sorry, but this function is not allowed in Demo mode.</div>
</t:if>
</form>
</div>
The symbols <em>jumpstart.upload-path</em> and <em>upload.filesize-max</em> were provided at runtime as Java options,
eg. <code>-Dupload.filesize-max=${fileSizemax}</code><br/><br/>
The Upload component is NOT part of Tapestry's corelib. To use it, put the following jars in the classpath:
<ul>
<li>tapestry-upload-5.3.n.jar, at compile and runtime.</li>
<li>commons-fileupload-1.2.2.jar, at compile and runtime.</li>
<li>commons-io-2.0.1.jar, at runtime.</li>
</ul>
For an AJAX uploader try <a href="http://tawus.wordpress.com/2011/06/25/ajax-upload-for-tapestry/">Ajax Upload for Tapestry</a>.<br/><br/>
References:
<a href="http://tapestry.apache.org/uploading-files.html">Uploading Files</a>,
<a href="http://tapestry.apache.org/5.3.7/apidocs/org/apache/tapestry5/upload/components/Upload.html">Upload</a>,
<a href="http://wiki.apache.org/tapestry/Tapestry5AndJavaScriptExplained">Tapestry 5 and JavaScript Explained</a>,
<a href="http://tapestry.apache.org/5.3.7/apidocs/org/apache/tapestry5/annotations/Import.html">@Import</a>,
<a href="http://tapestry.apache.org/5.3.7/apidocs/org/apache/tapestry5/services/javascript/JavaScriptSupport.html">JavaScriptSupport</a>,
<a href="http://tapestry.apache.org/defining-tapestry-ioc-services.html">Defining Tapestry IOC Services</a>.<br/><br/>
<a t:type="pagelink" t:page="Index" href="#">Home</a><br/><br/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/javascript/FileUpload.tml"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/javascript/FileUpload.properties"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/javascript/FileUpload.js"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/javascript/FileUpload.java"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/css/examples/javascript/fileupload.css"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/services/Filer.java"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/services/AppModule.java"/>
</body>
</html>
file-size-max=Maximum file size is %2.1fMB.
file-required-message=Choose file to upload.
progress=Uploading now... This may take a few minutes if the file is large (over, say, 1MB).
file-name-illegal-characters=File name "%s" contains illegal characters.
file-already-exists=File "%s" has been uploaded previously.
// A FileUpload provides functions that show/hide the progress and results blocks.
// Written in Protoype style because Tapestry includes the Protoype library (http://www.prototypejs.org/).
var fileUpload;
FileUpload = Class.create({
initialize : function(fileId, progressId, resultId) {
this.fileElem = this.resolve(fileId);
this.progressElem = this.resolve(progressId);
this.resultElem = this.resolve(resultId);
// Can't use Event.observe on the submit button because it conflicts with the form submission.
// So instead, of this... Event.observe(this.submitElem, 'click', this.showProgress.bindAsEventListener(this));
// ...add this to the submit button: onclick="fileUpload.showProgress(); return true;".
this.hideProgress();
},
hideProgress: function() {
this.progressElem.hide();
this.resultElem.show();
return true;
},
showProgress: function() {
// If a file has been chosen, then hide any previous results and show the progress
if (this.fileElem.value != "") {
this.progressElem.show();
this.resultElem.hide();
}
else {
this.resultElem.show();
}
return true;
},
hideResults: function() {
this.resultElem.hide();
return true;
},
resolve: function(elementId) {
var element = $(elementId);
if (!element) {
alert("To the developer: element id \"" + elementId + "\" does not exist.");
}
return element;
}
})
// Extend the Tapestry.Initializer with a function that instantiates a FileUpload object.
Tapestry.Initializer.fileUpload = function(spec) {
fileUpload = new FileUpload(spec.fileId, spec.progressId, spec.resultId);
}
package jumpstart.web.pages.examples.javascript;
import jumpstart.util.ExceptionUtil;
import jumpstart.web.components.CustomForm;
import jumpstart.web.services.IFiler;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.PersistenceConstants;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.upload.services.UploadedFile;
// The @Import tells Tapestry to put a link to the file in the head of the page so that the browser will pull it in.
@Import(library = "FileUpload.js")
public class FileUpload {
private final String demoModeStr = System.getProperty("jumpstart.demo-mode");
// Screen fields
@Property
private UploadedFile file;
@Property
@Persist(PersistenceConstants.FLASH)
private String successMessage;
@Property
private String fileId;
@Property
private String progressId;
@Property
private String resultId;
// Generally useful bits and pieces.
@Inject
private IFiler filer;
@Inject
private ComponentResources componentResources;
@InjectComponent
private CustomForm uploadForm;
@Inject
private Messages messages;
@Inject
private JavaScriptSupport javaScriptSupport;
// The code
void setupRender() {
fileId = "file";
progressId = "progress";
resultId = "result";
}
void afterRender() {
// Tell Tapestry to add some javascript that sets up our event handling.
// Tapestry will put it at the end of the page in a section that runs once the DOM has been loaded.
JSONObject spec = new JSONObject();
spec.put("fileId", fileId);
spec.put("progressId", progressId);
spec.put("resultId", resultId);
javaScriptSupport.addInitializerCall("fileUpload", spec);
}
void onValidateFromUploadForm() {
try {
String savedAsFileName = filer.save(file, messages);
successMessage = "Successfully uploaded file \"" + savedAsFileName + "\".";
}
catch (Exception e) {
// Display the cause. In a real system we would try harder to get a user-friendly message.
uploadForm.recordError(ExceptionUtil.getRootCauseMessage(e));
}
}
Object onUploadException(FileUploadException e) {
// Display the cause. In a real system we would try harder to get a user-friendly message.
uploadForm.recordError(ExceptionUtil.getRootCauseMessage(e));
return this;
}
public String getFileSizeMaxMessage() {
double fileSizeMaxMB = filer.getFileSizeMax() / 1048576.0;
return messages.format("file-size-max", (Double) fileSizeMaxMB);
}
public boolean isDemoMode() {
return (demoModeStr != null && demoModeStr.equals("true"));
}
public String getThisPageName() {
return componentResources.getPageName();
}
public long getFileSizeMax() {
return filer.getFileSizeMax();
}
}
body, td { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; color: #333;
line-height: 17px; }
h1 { font-size: 26px; line-height: 20px; } /* For IE 7 */
.eg { margin: 20px 0; padding: 20px;
border: 1px solid #ddd; border-radius: 4px; -webkit-border-radius: 4px; -mox-border-radius: 4px; }
.eg form { margin: 0; }
a { text-decoration: none; color: #3D69B6; }
a:hover { text-decoration: underline; }
.js-required { color: red; display: block; margin-bottom: 14px; }
#notes { margin: 0 0 10px 0; color: gray; }
input[type="file"] { color: gray; font-size: 13px; }
#result { margin: 10px 0 0 0; }
#success { color: green; }
.error-msg { color: red; }
#progress { margin: 10px 0 0 0; padding: 5px 10px; display: inline-block;
background-color: #999; color: white; font-weight: bold; }
.cancel { margin-left: 5px; }
#demo-mode { margin: 0 0 10px 0; color: red; }
package jumpstart.web.services;
import java.io.File;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.upload.services.UploadedFile;
import org.slf4j.Logger;
public class Filer implements IFiler {
static final String FILE_SEPARATOR = System.getProperty("file.separator");
@SuppressWarnings("unused")
private final Logger logger;
private String dirPathSymbol;
private String dirPath;
private String fileSizeMaxSymbol;
private long fileSizeMax;
public Filer(Logger logger, String dirPathSymbol, String dirPath, String fileSizeMaxSymbol, long fileSizeMax) {
super();
this.logger = logger;
this.dirPathSymbol = dirPathSymbol;
this.dirPath = sanitiseDirPath(dirPath);
this.fileSizeMaxSymbol = fileSizeMaxSymbol;
this.fileSizeMax = sanitiseFileSizeMax(fileSizeMax);
}
@Override
public String save(UploadedFile uploadedFile, Messages messages) throws Exception {
try {
String targetFileName = sanitiseFileName(uploadedFile.getFileName());
// This check is optional: Error if sanitised file name is different to original file name.
if (!targetFileName.equals(uploadedFile.getFileName())) {
// In a real system we would throw a exception of our own
throw new Exception(messages.format("file-name-illegal-characters", uploadedFile.getFileName()));
}
File targetFile = new File(dirPath + targetFileName);
// This check is optional: Error if the file already exists (else it will be overwritten).
if (targetFile.exists()) {
// In a real system we would throw a exception of our own
throw new Exception(messages.format("file-already-exists", uploadedFile.getFileName(), targetFileName));
}
uploadedFile.write(targetFile);
return targetFileName;
}
catch (Exception e) {
// In a real system we would throw a user-friendly message
throw e;
}
}
@Override
public long getFileSizeMax() {
return fileSizeMax;
}
private String sanitiseDirPath(String dirPath) {
String path = dirPath.trim();
if (!path.endsWith("/") || path.endsWith("\\") || path.endsWith(":")) {
path += FILE_SEPARATOR;
}
File dir = new File(path);
if (!dir.exists()) {
throw new IllegalStateException(
"File uploads cannot proceed because silly directory specified by system property " + dirPathSymbol
+ " does not exist. Value = " + path + ".");
}
return path;
}
private long sanitiseFileSizeMax(long fileSizeMax) {
if (fileSizeMax <= 10240 || fileSizeMax > 100000000) {
throw new IllegalStateException(
"File uploads cannot proceed because silly value found for system property " + fileSizeMaxSymbol
+ ", value = " + fileSizeMax + ".");
}
return fileSizeMax;
}
private String sanitiseFileName(String fileName) {
String s = fileName.replaceAll("[\\:\\*\\?\\<\\>\\|\\'\\\"\\/\\\\]+", "_");
return s;
}
}
package jumpstart.web.services;
import java.util.Arrays;
import java.util.HashSet;
import jumpstart.util.JodaTimeUtil;
import jumpstart.web.translators.MoneyTranslator;
import jumpstart.web.translators.YesNoTranslator;
import jumpstart.web.validators.Letters;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.Translator;
import org.apache.tapestry5.Validator;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Primary;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.BeanBlockContribution;
import org.apache.tapestry5.services.ComponentRequestFilter;
import org.apache.tapestry5.services.DisplayBlockContribution;
import org.apache.tapestry5.services.EditBlockContribution;
import org.apache.tapestry5.services.PageRenderLinkSource;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.security.WhitelistAnalyzer;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.upload.services.UploadSymbols;
import org.got5.tapestry5.jquery.JQuerySymbolConstants;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.slf4j.Logger;
/**
* This module is automatically included as part of the Tapestry IoC Registry, it's a good place to configure and extend
* Tapestry, or to place your own service definitions. See http://tapestry.apache.org/5.3.4/tapestry-ioc/module.html
*/
public class AppModule {
private static final String UPLOADS_PATH = "jumpstart.upload-path";
@Inject
@Symbol(SymbolConstants.PRODUCTION_MODE)
@Property(write = false)
private static boolean productionMode;
// Add 2 services to those provided by Tapestry.
// - CountryNames, and SelectIdModelFactory are used by pages which ask Tapestry to @Inject them.
public static void bind(ServiceBinder binder) {
binder.bind(CountryNames.class);
binder.bind(SelectIdModelFactory.class, SelectIdModelFactoryImpl.class);
// This next line addresses an issue affecting GlassFish and JBoss - see http://blog.progs.be/?p=52
javassist.runtime.Desc.useContextClassLoader = true;
}
// Tell Tapestry about our custom translators, validators, and their message files.
// We do this by contributing configuration to Tapestry's TranslatorAlternatesSource service, FieldValidatorSource
// service, and ComponentMessagesSource service.
@SuppressWarnings("rawtypes")
public static void contributeTranslatorAlternatesSource(MappedConfiguration<String, Translator> configuration,
ThreadLocale threadLocale) {
configuration.add("yesno", new YesNoTranslator("yesno"));
configuration.add("money2", new MoneyTranslator("money2", 2, threadLocale));
}
@SuppressWarnings("rawtypes")
public static void contributeFieldValidatorSource(MappedConfiguration<String, Validator> configuration) {
configuration.add("letters", new Letters());
}
public void contributeComponentMessagesSource(OrderedConfiguration<String> configuration) {
configuration.add("myTranslationMessages", "jumpstart/web/translators/TranslationMessages");
configuration.add("myValidationMessages", "jumpstart/web/validators/ValidationMessages");
}
// Tell Tapestry about our custom ValueEncoders.
// We do this by contributing configuration to Tapestry's ValueEncoderSource service.
// @SuppressWarnings("rawtypes")
// public static void contributeValueEncoderSource(MappedConfiguration<Class, Object> configuration) {
// configuration.addInstance(Person.class, PersonEncoder.class);
// }
// Tell Tapestry which locales we support, and tell Tapestry5jQuery not to suppress Tapestry's built-in Prototype
// and Scriptaculous (see the JQuery example for more information).
// We do this by contributing configuration to Tapestry's ApplicationDefaults service.
public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration) {
configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en_US,en_GB,fr");
// We have Tapestry5jQuery installed. Tell it we don't want it to suppress Prototype and Scriptaculous.
configuration.add(JQuerySymbolConstants.SUPPRESS_PROTOTYPE, "false");
// We don't use $j in our javascript - instead we use function scoping (see
// http://api.jquery.com/jQuery.noConflict/)
// but we need this next line to keep Tapestry happy (since Tapestry 5.3.4).
configuration.add(JQuerySymbolConstants.JQUERY_ALIAS, "$j");
}
// Tell Tapestry how to block access to WEB-INF/, META-INF/, and assets that are not in our assets "whitelist".
// We do this by contributing a custom RequestFilter to Tapestry's RequestHandler service.
// - This is necessary due to https://issues.apache.org/jira/browse/TAP5-815 .
// - RequestHandler is shown in http://tapestry.apache.org/request-processing.html#RequestProcessing-Overview .
// - RequestHandler is described in http://tapestry.apache.org/request-processing.html
// - Based on an entry in the Tapestry Users mailing list by martijn.list on 15 Aug 09.
public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
PageRenderLinkSource pageRenderLinkSource) {
final HashSet<String> ASSETS_WHITE_LIST = new HashSet<String>(Arrays.asList("jpg", "jpeg", "png", "gif", "js",
"css", "ico"));
configuration.add("AssetProtectionFilter", new AssetProtectionFilter(ASSETS_WHITE_LIST, pageRenderLinkSource),
"before:*");
}
// Tell Tapestry how to detect and protect pages that require security.
// We do this by contributing a custom ComponentRequestFilter to Tapestry's ComponentRequestHandler service.
// - ComponentRequestHandler is shown in
// http://tapestry.apache.org/request-processing.html#RequestProcessing-Overview
// - Based on http://tapestryjava.blogspot.com/2009/12/securing-tapestry-pages-with.html
public void contributeComponentRequestHandler(OrderedConfiguration<ComponentRequestFilter> configuration) {
configuration.addInstance("PageProtectionFilter", PageProtectionFilter.class);
}
// Tell Tapestry how to handle JBoss 7's classpath URLs - JBoss uses a "virtual file system".
// See "Running Tapestry on JBoss" in http://wiki.apache.org/tapestry/Tapestry5HowTos .
@SuppressWarnings("rawtypes")
public static void contributeServiceOverride(MappedConfiguration<Class, Object> configuration) {
configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterJBoss7());
}
// Tell Tapestry how to handle @EJB in page and component classes.
// We do this by contributing configuration to Tapestry's ComponentClassTransformWorker service.
// - Based on http://wiki.apache.org/tapestry/JEE-Annotation.
@Primary
public static void contributeComponentClassTransformWorker(
OrderedConfiguration<ComponentClassTransformWorker2> configuration) {
configuration.addInstance("EJB", EJBAnnotationWorker.class, "before:Property");
}
// Tell Tapestry how to handle pages annotated with @WhitelistAccessOnly, eg. Tapestry's ServiceStatus and
// PageCatalog.
// The default WhitelistAnalyzer allows localhost only and only in non-production mode.
// Our aim is to make the servicestatus page available to ALL clients when not in production mode.
// We do this by contributing our own WhitelistAnalyzer to Tapestry's ClientWhiteList service.
public static void contributeClientWhiteList(OrderedConfiguration<WhitelistAnalyzer> configuration) {
if (!productionMode) {
configuration.add("NonProductionWhitelistAnalyzer", new WhitelistAnalyzer() {
@Override
public boolean isRequestOnWhitelist(Request request) {
if (request.getPath().startsWith("/core/servicestatus")) {
return true;
}
else {
// This is copied from org.apache.tapestry5.internal.services.security.LocalhostOnly
String remoteHost = request.getRemoteHost();
return remoteHost.equals("localhost") || remoteHost.equals("127.0.0.1")
|| remoteHost.equals("0:0:0:0:0:0:0:1%0") || remoteHost.equals("0:0:0:0:0:0:0:1");
}
}
}, "before:*");
}
}
// Tell Tapestry how to build our Filer service (used in the FileUpload example).
// Annotate it with EagerLoad to force resolution of symbols at startup rather than when it is first used.
@EagerLoad
public static IFiler buildFiler(Logger logger, @Inject @Symbol(UPLOADS_PATH) final String uploadsPath,
@Inject @Symbol(UploadSymbols.FILESIZE_MAX) final long fileSizeMax) {
return new Filer(logger, UPLOADS_PATH, uploadsPath, UploadSymbols.FILESIZE_MAX, fileSizeMax);
}
// Tell Tapestry how to coerce Joda Time types to and from Java Date types for the TypeCoercers example.
// We do this by contributing configuration to Tapestry's TypeCoercer service.
// - Based on http://tapestry.apache.org/typecoercer-service.html
@SuppressWarnings("rawtypes")
public static void contributeTypeCoercer(Configuration<CoercionTuple> configuration) {
// From java.util.Date to DateMidnight
Coercion<java.util.Date, DateMidnight> toDateMidnight = new Coercion<java.util.Date, DateMidnight>() {
public DateMidnight coerce(java.util.Date input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toDateMidnight(input);
}
};
configuration.add(new CoercionTuple<java.util.Date, DateMidnight>(java.util.Date.class, DateMidnight.class,
toDateMidnight));
// From DateMidnight to java.util.Date
Coercion<DateMidnight, java.util.Date> fromDateMidnight = new Coercion<DateMidnight, java.util.Date>() {
public java.util.Date coerce(DateMidnight input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toJavaDate(input);
}
};
configuration.add(new CoercionTuple<DateMidnight, java.util.Date>(DateMidnight.class, java.util.Date.class,
fromDateMidnight));
// From java.util.Date to LocalDate
Coercion<java.util.Date, LocalDate> toLocalDate = new Coercion<java.util.Date, LocalDate>() {
public LocalDate coerce(java.util.Date input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toLocalDate(input);
}
};
configuration.add(new CoercionTuple<java.util.Date, LocalDate>(java.util.Date.class, LocalDate.class,
toLocalDate));
// From LocalDate to java.util.Date
Coercion<LocalDate, java.util.Date> fromLocalDate = new Coercion<LocalDate, java.util.Date>() {
public java.util.Date coerce(LocalDate input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toJavaDate(input);
}
};
configuration.add(new CoercionTuple<LocalDate, java.util.Date>(LocalDate.class, java.util.Date.class,
fromLocalDate));
}
// Tell Tapestry how its BeanDisplay and BeanEditor can handle the JodaTime types.
// We do this by contributing configuration to Tapestry's DefaultDataTypeAnalyzer and BeanBlockSource services.
// - Based on http://tapestry.apache.org/tapestry5/guide/beaneditform.html .
public static void contributeDefaultDataTypeAnalyzer(
@SuppressWarnings("rawtypes") MappedConfiguration<Class, String> configuration) {
configuration.add(DateTime.class, "dateTime");
configuration.add(DateMidnight.class, "dateMidnight");
configuration.add(LocalDateTime.class, "localDateTime");
configuration.add(LocalDate.class, "localDate");
configuration.add(LocalTime.class, "localTime");
}
public static void contributeBeanBlockSource(Configuration<BeanBlockContribution> configuration) {
configuration.add(new DisplayBlockContribution("dateTime", "infra/AppPropertyDisplayBlocks", "dateTime"));
configuration
.add(new DisplayBlockContribution("dateMidnight", "infra/AppPropertyDisplayBlocks", "dateMidnight"));
configuration.add(new DisplayBlockContribution("localDateTime", "infra/AppPropertyDisplayBlocks",
"localDateTime"));
configuration.add(new DisplayBlockContribution("localDate", "infra/AppPropertyDisplayBlocks", "localDate"));
configuration.add(new DisplayBlockContribution("localTime", "infra/AppPropertyDisplayBlocks", "localTime"));
configuration.add(new EditBlockContribution("dateMidnight", "infra/AppPropertyEditBlocks", "dateMidnight"));
configuration.add(new EditBlockContribution("localDate", "infra/AppPropertyEditBlocks", "localDate"));
}
}