This package contains various helpers and common utilities or base classes. Browse the classes and check Javadoc for all details.
Holds multi-language strings in the db. Use MString instead of String where you have multilingual content editable in the backend.
There are two types of validation constraints available for MString properties:
See ContentBlock.java and contentBlockEdit.scala.html:
Entity class:
@OneToOne (cascade=CascadeType.ALL)
@MString.MStringRequired(message = "cms.error.content.required")
private MString content = new MString();
Template:
@mstringForm(action = routes.BlockController.update(data.getId().toString(), backURL), 'class -> "form-horizontal") {
@text(
editForm,
"title",
"Titel",
"",
(a: String) => a
)
@mstringTextarea(
data.getContent,
editForm,
"content",
"Content",
""
)
We have created a special class for our Forms: SmartForm. This class extends the normal form, but is much mightier.
Basically, the usage is the same:
- Create a form using SmartForm.form
- bind the form using fill()
- bind the request data using bindFromRequest
- check for errors
- and so on
There are 2 main advantages in this implementation:
BlockController.java:
AbstractBlock targetBlock = AbstractBlock.find.byId(id);
if (targetBlock == null) {
return internalServerError("Block not found: " + id);
}
Form<AbstractBlock> boundForm = (Form<AbstractBlock>) SmartForm.form(targetBlock.getClass());
boundForm = boundForm.fill(targetBlock);
boundForm = boundForm.bindFromRequest();
if (boundForm.hasErrors()) {
flash("error", "Please correct the errors below");
Logger.warn("Form errors found: " + boundForm.errors());
return badRequest(targetBlock.editForm(boundForm));
}
boundForm.get().save();
return redirect(backURL);
Note: Consider using the SecureForm subclass, which also prevents form parameter tampering (see below).
Allows to use custom types in form data binding. You need to create and register your own databinder. Example:
DataBinding.registerDataBinder(MString.class, new MStringDataBinder());
After that, SmartForm will know how to convert MString to Form Data and vice-versa.
Example: MString is @OneToOne. MString is a bit special, it has a map of <string,string> in it, which is the language key and the translation string. We pass the values like this:?content.de=german&content.en=english&content.id=<entity id>
Play forms can't handle that, so we wrote an adapter. This class converts from Map<String,String> to MString and vice versa
and also does validation.
Example: MStringDataBinder.java
Your databinder implement this interface:
public interface DataBinder {
public Object getFromData(String fieldName, Object instance, Map<String,String> data);
public Map<String,String> toData(String fieldName, Object t);
public Map<String,List<ValidationError>> validate(String fieldName, Object object);
}
The play-cms contains the feature provides a conversion between JDBC native result set and Java streams. It keeps an access to database record but it does it lazily so you can easily manage big amount of data without "OutOfMemory" limits and in functional style.
The available methods presented in QueryStream.java file. It looks like:
package ch.insign.commons.db;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* Add simple support for Java 8 Streams to JPA.
*
* @see {https://blogs.oracle.com/theaquarium/entry/jpa_and_java_se_8}
*/
public interface QueryStream<T> {
/**
* Return a stream of read-only objects.
* <p>
* The query will be executed against the Session,
* and the resulting objects will not be tracked for changes.
* The resulting objects are from the Session shared cache,
* and must not be modified.
* <p>
* The stream should be closed to release resources.
*/
Stream<T> getResultStream();
/**
* Applies a result stream to the given function and automatically releases resources.
*/
<R> Optional<R> withResultStream(Function<Stream<T>, R> function);
/**
* Bind an argument to a named query parameter.
*/
QueryStream<T> setParameter(String name, Object value);
}
To use it into your project you must inject the QueryStreamFactory interface which provides an initialization of QueryStream object, into your class using guice.
Example:
/* your other imports */
import ch.insign.commons.db.QueryStreamFactory;
class ApplicationController extends Controller {
private QueryStreamFactory queryStreamFactory;
@Inject
public ApplicationController(FormFactory formFactory, JPAApi jpaApi, QueryStreamFactory queryStreamFactory) {
...
this.queryStreamFactory = queryStreamFactory;
}
/* Some other content */
public Stream<User> getAllAdults() {
return queryStreamFactory.create("SELECT u FROM User u WHERE u.age >= 30").getResultStream();
}
}
You can create the QueryStream with three variants described as QueryStreamFactory API. It looks like:
package ch.insign.commons.db;
import javax.persistence.Query;
public interface QueryStreamFactory {
<T> QueryStream<T> create(Query query, Class<T> resultClass);
<T> QueryStream<T> create(String jpql, Class<T> resultClass);
<T> QueryStream<T> createNamed(String name, Class<T> resultClass);
}
Advanced topic of QueryStream.
The default implementation based on cross-JPA-vendor limit/offset approach, but you can easily override it with your own implementation. For example, if you use EclipseLink vendor, you can do the next stuff:
1. Implement QueryStream interface:
/**
* The QueryStream implementation using EclipseLink's ScrollableCursor.
*
* @see {http://wiki.eclipse.org/EclipseLink/Examples/JPA/Pagination#Using_a_ScrollableCursor}
* @param <T>
*/
public class EclipseLinkQueryStream<T> implements QueryStream<T> {
private final static Logger logger = LoggerFactory.getLogger(EclipseLinkQueryStream.class);
private final Query query;
private final Class<T> type;
private final Map<String, Object> parameters = new HashMap<>();
// Default fetch size as a benchmark suggested in
// http://makejavafaster.blogspot.com/2015/06/jdbc-fetch-size-performance.html
private int fetchSize = 2000;
EclipseLinkQueryStream(Query query, Class<T> type) {
this.query = query;
this.type = type;
}
@Override
public Stream<T> getResultStream() {
parameters.forEach(query::setParameter);
Optional.ofNullable(fetchSize).ifPresent(size ->
query.setHint(QueryHints.JDBC_FETCH_SIZE, size));
query.setHint(QueryHints.READ_ONLY, HintValues.TRUE);
query.setHint(QueryHints.SCROLLABLE_CURSOR, HintValues.TRUE);
ScrollableCursor cursor = (ScrollableCursor)query.getSingleResult();
return StreamSupport.stream(toSplitIterator(cursor, type), false)
.onClose(cursor::close);
}
@Override
public <R> Optional<R> withResultStream(Function<Stream<T>, R> function) {
try(Stream<T> s = getResultStream()) {
return Optional.ofNullable(function.apply(s));
}
}
@Override
public QueryStream<T> setParameter(String name, Object value) {
parameters.put(name, value);
return this;
}
public QueryStream<T> setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
return this;
}
private Spliterator<T> toSplitIterator(ScrollableCursor cursor, Class<T> type){
return Spliterators.spliteratorUnknownSize(
new ScrollableResultIterator<>(cursor, type),
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.CONCURRENT | Spliterator.IMMUTABLE
);
}
private static class ScrollableResultIterator<T> implements Iterator<T> {
private final ScrollableCursor cursor;
private final Class<T> type;
ScrollableResultIterator(ScrollableCursor cursor, Class<T> type) {
this.cursor = cursor;
this.type = type;
}
@Override
public boolean hasNext() {
return cursor.hasNext();
}
@Override
public T next() {
return type.cast(cursor.next());
}
}
}
2. Implement QueryStreamFactory interface:
public class EclipseLinkQueryStreamFactory implements QueryStreamFactory {
private JPAApi jpaApi;
@Inject
private EclipseLinkQueryStreamFactory(JPAApi jpaApi) {
this.jpaApi = jpaApi;
}
@Override
public <T> QueryStream<T> create(Query query, Class<T> resultClass) {
return new EclipseLinkQueryStream<>(query, resultClass);
}
@Override
public <T> QueryStream<T> create(String jpql, Class<T> resultClass) {
return new EclipseLinkQueryStream<>(jpaApi.em().createQuery(jpql, resultClass), resultClass);
}
@Override
public <T> QueryStream<T> createNamed(String name, Class<T> resultClass) {
return new EclipseLinkQueryStream<>(jpaApi.em().createNamedQuery(name, resultClass), resultClass);
}
}
3. Override binding using Guice application loader:
public class YourApplicationLoader extends GuiceApplicationLoader {
@Override
public GuiceApplicationBuilder builder(Context context) {
return super.builder(context)
.overrides(
//your other bindings
bind(QueryStreamFactory.class).to(EclipseLinkQueryStreamFactory.class)
);
}
}
The usage of overrided context is the same to above.
TODO
Config values in .conf files are a source of runtime errors if not found, wrong type etc. To avoid such problems (and to make using config values much more intuitive for the developer), use the Configuration base class:
Example:
import ch.insign.commons.util.Configuration;
/**
* Managed CMS config values from application.conf
*/
public class CmsConfiguration extends Configuration {
@Override
protected String configNamespace() {
return "cms";
}
@Override
protected void test() {
deleteWithoutTrashBin();
isResetRouteEnabled();
defaultUrlPrefix();
}
public boolean deleteWithoutTrashBin() {
return getConfig().getBoolean("deleteWithoutTrashBin");
}
public boolean isResetRouteEnabled() {
return getConfig().getBoolean("enableResetRoute");
}
/**
* The url to use if no virtual path was set for a page.
* Default is /page/<id>
*/
public String defaultUrlPrefix() {
return getConfig().getString("defaultUrlPrefix");
}
The corresponding entries in the application.conf
file:
# CMS config values.
# Always access these using Cms.getConfig().*
cms.deleteWithoutTrashBin = false
cms.enableResetRoute = false
cms.defaultUrlPrefix = /pages/
Overriding default values:
If you work on a module, you can define your default settings in the module's application.conf
, and the consumer of your module can override them individually in the main project's application.conf
When binding form data to a backend model class / entity directly using Play's .bindFromRequest() method, parameter tampering attacks are possible: The attacker can simply post any field the entity contains, e.g. isAdmin, along with the form and it would be saved.
There are basically 3 solutions to this:
SecureForm usage:
1. Add the @formKey to your form
@()
@import ch.insign.commons.helper.html._
<form>
@formKey()
...
This will simply add a hidden field to your form that will hold the form signature.
2. Sign the html templates before sending the form to the client:
public static void editForm() {
return ok(SecureForm.signForms(myForm.render()));
}
The generated hidden field looks e.g. like this:
<input type="hidden" name="_formsignature" value="rO0ABXNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHXlGcm9tdAAJZGlzcGxheVRvdAAIdGl0bGUuZGV0AAh0aXRsZS5mc
nQACHRpdGxlLmlkeA==@rO0ABXQALnXvv71oGy5tLl5mJe+/vQJt77+9WE4E1oXvv70c77+977+9A1JKCVvvv73vv70=" />
The first part contains the serialized field list, the second part contains a SHA256 hash signature of the first part (salted with the application.secret conf value).
3. Validate form integrity when receiving the form submission (before / when binding)
MyController.java:
// Validate the integrity of the submitted form fields: Either by signed formKey or by passed allowedFields.
// Note: If using SmartForm, the security check is already done inside .bind(), no coding needed.
try {
SecureForm.validateSubmittedForm(data, allowedFields, object);
} catch (InvalidKeyException e) {
// We're strict about tampered submissions.
throw new RuntimeException(e);
}
// Now you can bind as normal. Normal value validation happens here.
form.bindFromRequest();
Note:
SecureForm
(or SmartForm) instead of Play's Form, then it's automatically done in all bind()
methods.
Automatic field list handling
Field lists with brackets are adjusted automatically to allow for indexes, e.g.: <input name="field[]">
in the form sent to the client allows field[0], field[1]
etc. to be submitted.
Wildcards for dynamic forms
In cases where you modify the form on the client-side, e.g. by JS, you can use the * wildcard:
@formKey("myDynAddedField, myJSFieldX*, myWhat*Field")
You can also define your additional allowed fields in a separate hidden field (e.g. if you're using a extended template which already contains @formKey, like the cms block edit templates):
@formKeyAdditional("myDynAddedField, myJSFieldX*, myWhat*Field")
The TaskQueue helps with queueing persistent tasks, such as asynchroneous webservice calls where the recipient is not guaranteed to be available permanently.
Tasks extend Task and are a Model subclass, so they're db-persisted.
@Entity
@Table(name = "task_address_change")
@DiscriminatorValue("AddressChangeTask")
public class AddressChangeTask extends Task {
@Override
public boolean execute() {
try {
// Do your stuff
// Return true if it succeeds.
addLogEntry("Success!);
return true;
} catch (Exception e) {
// Returning false will requeue the task until .retry() returns false.
addLogEntry("Failed: " + e.getMessage(), Level.ERROR, e);
return false;
}
}
@Override
public void failed() {
addLogEntry("Request failed permanently: " + this, Level.ERROR);
}
}
Other Task methods / events:
validate()
- override to implement validation (validation is checked on queue().add())retry()
- decides whether a failed task should be requeued again.onEnqueue()
- called when the task was added to the queue initiallyonRequeue()
- called when the task execution failed and the task was again enqueuedHint: If you want to create your own abstract task superclass, annotate it with @MappedSuperclass.
Create and enqueue a task instance:
// Create, configure and queue a task
AddressOrderChangeTask task = new AddressOrderChangeTask();
task.setNewAddress(address);
task.setMaxTries(5);
setRetryWaitPeriod(15*60);
SoapTransmissionActor.queue().add(task);
The queue is run by an Akka actor which call the queue's process() method periodically.
Example Akka actor:
public class SoapTransmissionActor extends UntypedActor{
private final static TaskQueue queue = new TaskQueue("SoapQueue");
public static TaskQueue queue() {
return queue;
}
// This needs to be called from
public static void start() {
// Re-add any persisted waiting messages and start processing queue.
queue.load().start();
// Create the actor
Akka.system().scheduler().schedule(
Duration.create(Configuration.getOrElse("akka.actor.SoapTransmissionActor.delay", 5), TimeUnit.SECONDS),
Duration.create(Configuration.getOrElse("akka.actor.SoapTransmissionActor.interval", 2), TimeUnit.SECONDS),
Akka.system().actorOf(Props.create(SoapTransmissionActor.class)),
"Send Soap Requests to SAP",
Akka.system().dispatcher(),
null
);
}
@Override
public void onReceive(Object message) throws Exception {
if (isSapReachable()){
queue.process();
} else {
Logger.warn("SAP is not reachable! Current soap queue size: " + queue.size());
}
checkQueueSize();
}
private void checkQueueSize() {
if (queue.size() > 300) {
Logger.error("SoapTransmission queue size too big: " + queue.size());
} else if(queue.size() > 100) {
Logger.warn("SoapTransmission queue size: " + queue.size());
}
}
Usually, the queue is started at app startup, in Global.java's onStart():
if(Configuration.getOrElse("akka.actor.SoapTransmissionActor.enabled", false)) {
SoapTransmissionActor.start();
}
Note: This example uses one actor. If you need parallel execution of tasks, you can just create more actors that .process()
tasks, TaskQueue is thread-safe.
There is a readibly usable Task Queue log viewer UI in the play-cms package.
Notes:
/admin/cms/taskqueue
.addLogEntry()
. If you add a severity (e.g. Level.WARNING
), it is also sent to the logger.reportTemplatePartial()
(make sure you concatenate the superclass' output)Wrapper over the Ehcache. This class gives more control over the cache rather than Play Framework Cache API. Cache class provides more powerful way to interact with Ehcache including ability to use custom objects as keys, easely access to underlying ehchace to use features such as search. Here is few examples of usage of the play-cms Cache:
// Recieve value from cache
cache.cached(
OBJECT_CACHE, // Cache name
obj.getComplexId(), // Entry key
() -> generateCachebleEntry(obj)); // If value not present put new one
// Get net.sf.ehcache.Cache
cache.underlying(OBJECT_CACHE);
// Check if cache already contains entry
cache.isCached(OBJECT_CACHE, obj.getComplexId());
Cache object could be created with one of the constructors:
Cache(String name, URL file)
Cache(String name, Configuration configuration, CacheConfiguration cacheConfiguration)
These constructors covers two main ways of creation cache - by reading configuration file and by passing configuration objects.
We recommend to use providers in ordere to create the Cache objects. This would allow to keep your code clean. Example provider is listed below:
public class CustomCacheProvider implements Provider<Cache> {
public static final String MANGER_NAME = "customCacheManager";
private static final String CONFIG_FILE_PATH = "custom.ehcache.xml";
private final Cache cache;
@Inject
public CustomCacheProvider(Environment environment) {
this.cache = new Cache(MANGER_NAME, environment.classLoader().getResource(CONFIG_FILE_PATH));
}
@Override
public Cache get() {
return cache;
}
}
public class CacheProviderModule extends Module {
public final static String CUSTOM_CACHE = "customCache";
@Override
public Seq<Binding<?>> bindings(Environment env, Configuration configuration) {
return seq(bind(Cache.class).qualifiedWith(CUSTOM_CACHE).toProvider(CustomCacheProvider.class));
}
}
// Cache could be injected now with
@Named(CacheProviderModule.CUSTOM_CACHE) Cache customCache;