diff --git a/framework/build.xml b/framework/build.xml
index 50d0275817..1d917b0fa8 100644
--- a/framework/build.xml
+++ b/framework/build.xml
@@ -290,6 +290,10 @@
+
+
+
+
diff --git a/framework/src/play/mvc/ActionInvoker.java b/framework/src/play/mvc/ActionInvoker.java
index d758174932..ee5ca3fd66 100644
--- a/framework/src/play/mvc/ActionInvoker.java
+++ b/framework/src/play/mvc/ActionInvoker.java
@@ -218,7 +218,7 @@ public static void invoke(Http.Request request, Http.Response response) {
private static void invokeControllerCatchMethods(Throwable throwable) throws Exception {
// @Catch
Object[] args = new Object[] {throwable};
- List catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class);
+ List catches = Java.findAllAnnotatedMethods(getControllerClass(), Catch.class);
ControllerInstrumentation.stopActionCall();
for (Method mCatch : catches) {
Class[] exceptions = mCatch.getAnnotation(Catch.class).value();
@@ -268,7 +268,7 @@ public static Method findActionMethod(String name, Class clazz) {
}
private static void handleBefores(Http.Request request) throws Exception {
- List befores = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Before.class);
+ List befores = Java.findAllAnnotatedMethods(getControllerClass(), Before.class);
ControllerInstrumentation.stopActionCall();
for (Method before : befores) {
String[] unless = before.getAnnotation(Before.class).unless();
@@ -302,7 +302,7 @@ private static void handleBefores(Http.Request request) throws Exception {
}
private static void handleAfters(Http.Request request) throws Exception {
- List afters = Java.findAllAnnotatedMethods(Controller.getControllerClass(), After.class);
+ List afters = Java.findAllAnnotatedMethods(getControllerClass(), After.class);
ControllerInstrumentation.stopActionCall();
for (Method after : afters) {
String[] unless = after.getAnnotation(After.class).unless();
@@ -348,13 +348,13 @@ private static void handleAfters(Http.Request request) throws Exception {
*/
static void handleFinallies(Http.Request request, Throwable caughtException) throws PlayException {
- if (Controller.getControllerClass() == null) {
+ if (getControllerClass() == null) {
// skip it
return;
}
try {
- List allFinally = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Finally.class);
+ List allFinally = Java.findAllAnnotatedMethods(Request.current().controllerClass, Finally.class);
ControllerInstrumentation.stopActionCall();
for (Method aFinally : allFinally) {
String[] unless = aFinally.getAnnotation(Finally.class).unless();
@@ -455,9 +455,7 @@ public static Object invokeControllerMethod(Method method, Object[] forceArgs) t
if (declaringClassName.endsWith("$class")) {
args[0] = scalaInstance; // Scala trait method
} else {
- request.controllerInstance = (Controller) scalaInstance; // Scala
- // object
- // method
+ request.controllerInstance = (PlayController) scalaInstance; // Scala object method
}
} catch (NoSuchFieldException e) {
// not Scala
@@ -500,11 +498,11 @@ static Object invoke(Method method, Object instance, Object ... realArgs) throws
static Object invokeWithContinuation(Method method, Object instance, Object[] realArgs) throws Exception {
// Callback case
- if (Http.Request.current().args.containsKey(A)) {
+ if (Request.current().args.containsKey(A)) {
// Action0
- instance = Http.Request.current().args.get(A);
- Future f = (Future) Http.Request.current().args.get(F);
+ instance = Request.current().args.get(A);
+ Future f = (Future) Request.current().args.get(F);
Scope.RenderArgs renderArgs = (Scope.RenderArgs) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_RENDER_ARGS);
Scope.RenderArgs.current.set(renderArgs);
if (f == null) {
@@ -520,7 +518,7 @@ static Object invokeWithContinuation(Method method, Object instance, Object[] re
}
// Continuations case
- Continuation continuation = (Continuation) Http.Request.current().args.get(C);
+ Continuation continuation = (Continuation) Request.current().args.get(C);
if (continuation == null) {
continuation = new Continuation(new StackRecorder((Runnable) null));
}
@@ -541,7 +539,7 @@ static Object invokeWithContinuation(Method method, Object instance, Object[] re
}
Object trigger = pStackRecorder.value;
Continuation nextContinuation = new Continuation(pStackRecorder);
- Http.Request.current().args.put(C, nextContinuation);
+ Request.current().args.put(C, nextContinuation);
if (trigger instanceof Long) {
throw new Suspend((Long) trigger);
@@ -555,7 +553,7 @@ static Object invokeWithContinuation(Method method, Object instance, Object[] re
throw new UnexpectedException("Unexpected continuation trigger -> " + trigger);
} else {
- Http.Request.current().args.remove(C);
+ Request.current().args.remove(C);
}
} finally {
pStackRecorder.deregisterThread(old);
@@ -577,10 +575,10 @@ public static Object[] getActionMethod(String fullAction) {
if (controllerClass == null) {
throw new ActionNotFoundException(fullAction, new Exception("Controller " + controller + " not found"));
}
- if (!ControllerSupport.class.isAssignableFrom(controllerClass)) {
+ if (!PlayController.class.isAssignableFrom(controllerClass)) {
// Try the scala way
controllerClass = Play.classloader.getClassIgnoreCase(controller + "$");
- if (!ControllerSupport.class.isAssignableFrom(controllerClass)) {
+ if (!PlayController.class.isAssignableFrom(controllerClass)) {
throw new ActionNotFoundException(fullAction,
new Exception("class " + controller + " does not extend play.mvc.Controller"));
}
@@ -636,4 +634,7 @@ public static Object[] getActionMethodArgs(Method method, Object o) throws Excep
return rArgs;
}
+ private static Class extends PlayController> getControllerClass() {
+ return Http.Request.current().controllerClass;
+ }
}
diff --git a/framework/src/play/mvc/Controller.java b/framework/src/play/mvc/Controller.java
index e230f30b4d..5f81c9d122 100644
--- a/framework/src/play/mvc/Controller.java
+++ b/framework/src/play/mvc/Controller.java
@@ -69,10 +69,9 @@
* Application controller support: The controller receives input and initiates a
* response by making calls on model objects.
*
- * This is the class that your controllers should extend.
- *
+ * This is the class that your controllers should extend in most cases.
*/
-public class Controller implements ControllerSupport, LocalVariablesSupport {
+public class Controller implements PlayController, ControllerSupport, LocalVariablesSupport {
/**
* The current HTTP request: the message sent by the client to the server.
@@ -963,8 +962,9 @@ protected static T getControllerInheritedAnnotation(Class
*
* @return Annotation object or null if not found
*/
+ @SuppressWarnings("unchecked")
protected static Class extends Controller> getControllerClass() {
- return Http.Request.current().controllerClass;
+ return (Class extends Controller>) Http.Request.current().controllerClass;
}
/**
diff --git a/framework/src/play/mvc/Http.java b/framework/src/play/mvc/Http.java
index 3bb9f87941..63b8ff07b8 100644
--- a/framework/src/play/mvc/Http.java
+++ b/framework/src/play/mvc/Http.java
@@ -265,11 +265,11 @@ public static class Request implements Serializable {
/**
* The invoked controller class
*/
- public transient Class extends Controller> controllerClass;
+ public transient Class extends PlayController> controllerClass;
/**
* The instance of invoked controller in case it uses non-static action methods.
*/
- public transient Controller controllerInstance;
+ public transient PlayController controllerInstance;
/**
* Free space to store your request specific data
*/
diff --git a/framework/src/play/mvc/PlayController.java b/framework/src/play/mvc/PlayController.java
new file mode 100644
index 0000000000..69d7029684
--- /dev/null
+++ b/framework/src/play/mvc/PlayController.java
@@ -0,0 +1,10 @@
+package play.mvc;
+
+/**
+ * Marker interface for play controllers
+ *
+ * This is the class that your controllers should implement.
+ * In most cases, you can extend play.mvc.Controller that contains all needed methods for controllers.
+ */
+public interface PlayController {
+}
diff --git a/samples-and-tests/nonstatic-app/app/controllers/Application.java b/samples-and-tests/nonstatic-app/app/controllers/Application.java
new file mode 100644
index 0000000000..ffd470260f
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/app/controllers/Application.java
@@ -0,0 +1,23 @@
+package controllers;
+
+import play.mvc.PlayController;
+import play.mvc.results.RenderTemplate;
+import play.mvc.results.RenderText;
+import play.mvc.results.Result;
+import play.templates.Template;
+import play.templates.TemplateLoader;
+
+import static java.util.Collections.emptyMap;
+
+public class Application implements PlayController {
+
+ public Result index() {
+ Template template = TemplateLoader.load("Application/index.html");
+ return new RenderTemplate(template, emptyMap());
+ }
+
+ public Result hello() {
+ return new RenderText("Hello world!");
+ }
+
+}
\ No newline at end of file
diff --git a/samples-and-tests/nonstatic-app/app/views/Application/index.html b/samples-and-tests/nonstatic-app/app/views/Application/index.html
new file mode 100644
index 0000000000..16f814b355
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/app/views/Application/index.html
@@ -0,0 +1,4 @@
+#{extends 'main.html' /}
+#{set title:'Home' /}
+
+Welcome to the non-static world!
\ No newline at end of file
diff --git a/samples-and-tests/nonstatic-app/app/views/main.html b/samples-and-tests/nonstatic-app/app/views/main.html
new file mode 100644
index 0000000000..8456f2096d
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/app/views/main.html
@@ -0,0 +1,13 @@
+
+
+
+
+ #{get 'title' /}
+
+ #{get 'moreStyles' /}
+
+
+ #{doLayout /}
+ #{get 'moreScripts' /}
+
+
diff --git a/samples-and-tests/nonstatic-app/conf/application.conf b/samples-and-tests/nonstatic-app/conf/application.conf
new file mode 100644
index 0000000000..81a9b3db1d
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/conf/application.conf
@@ -0,0 +1,240 @@
+# This is the main configuration file for the application.
+# ~~~~~
+application.name=nonstatic-app
+
+# Application mode
+# ~~~~~
+# Set to dev to enable instant reloading and other development help.
+# Otherwise set to prod.
+application.mode=dev
+%prod.application.mode=prod
+
+# Secret key
+# ~~~~~
+# The secret key is used to secure cryptographics functions
+# If you deploy your application to several instances be sure to use the same key !
+application.secret=LGhGcdAqQYyEG5GX7sauvQxeZXQBoQNpFflbeHT0mGM9u7oRxzShTrRYZNIc4tvR
+
+# i18n
+# ~~~~~
+# Define locales used by your application.
+# You can then place localized messages in conf/messages.{locale} files
+# application.langs=fr,en,ja
+
+# Date format
+# ~~~~~
+date.format=yyyy-MM-dd
+# date.format.fr=dd/MM/yyyy
+
+# Server configuration
+# ~~~~~
+# If you need to change the HTTP port, uncomment this (default is set to 9000)
+# http.port=9000
+#
+# By default the server listen for HTTP on the wildcard address.
+# You can restrict this.
+# http.address=127.0.0.1
+#
+# Use this if you don't host your Play application at the root of the domain
+# you're serving it from. This parameter has no effect when deployed as a
+# war, because the path will be handled by the application server.
+# http.path=/
+
+# Session configuration
+# ~~~~~~~~~~~~~~~~~~~~~~
+# By default, session will be written to the transient PLAY_SESSION cookie.
+# The cookies are not secured by default, only set it to true
+# if you're serving your pages through https.
+# application.session.cookie=PLAY
+# application.session.maxAge=1h
+# application.session.secure=false
+
+# Session/Cookie sharing between subdomain
+# ~~~~~~~~~~~~~~~~~~~~~~
+# By default a cookie is only valid for a specific domain. By setting
+# application.defaultCookieDomain to '.example.com', the cookies
+# will be valid for all domains ending with '.example.com', ie:
+# foo.example.com and bar.example.com
+# application.defaultCookieDomain=.example.com
+
+# JVM configuration
+# ~~~~~
+# Define which port is used by JPDA when application is in debug mode (default is set to 8000)
+# jpda.port=8000
+#
+# Java source level => 1.8
+# java.source=1.8
+
+# Log level
+# ~~~~~
+# Specify log level for your application.
+# If you want a very customized log, create a log4j.properties file in the conf directory
+# application.log=INFO
+#
+# More logging configuration
+# application.log.path=/log4j.properties
+# application.log.system.out=off
+
+# Database configuration
+# ~~~~~
+# Enable a database engine if needed.
+#
+# To quickly set up a development database, use either:
+# - mem : for a transient in memory database (H2 in memory)
+# - fs : for a simple file written database (H2 file stored)
+# db.default=mem
+#
+# To connect to a local MySQL5 database, use:
+# db.default=mysql://user:pwd@host/database
+#
+# To connect to a local PostgreSQL9 database, use:
+# db=postgres://user:pwd@host/database
+#
+# If you need a full JDBC configuration use the following :
+# db.default.url=jdbc:postgresql:database_name
+# db.default.driver=org.postgresql.Driver
+# db.default.user=root
+# db.default.pass=secret
+#
+# Connections pool configuration :
+# db.default.pool.timeout=1000
+# db.default.pool.maxSize=30
+# db.default.pool.minSize=10
+#
+# If you want to reuse an existing Datasource from your application server, use:
+# db.default=java:/comp/env/jdbc/myDatasource
+#
+# When using an existing Datasource, it's sometimes needed to destroy it when
+# the application is stopped. Depending on the datasource, you can define a
+# generic "destroy" method :
+# db.default.destroyMethod=close
+
+# JPA Configuration (Hibernate)
+# ~~~~~
+#
+# Specify the custom JPA dialect to use here (default to guess):
+# jpa.default.dialect=org.hibernate.dialect.PostgreSQLDialect
+#
+# Specify the ddl generation pattern to use. Set to none to disable it
+# (default to update in DEV mode, and none in PROD mode):
+# jpa.default.ddl=update
+#
+# Debug SQL statements (logged using DEBUG level):
+# jpa.default.debugSQL=true
+#
+# You can even specify additional hibernate properties here:
+# default.hibernate.use_sql_comments=true
+# ...
+#
+# Store path for Blob content
+attachments.path=data/attachments
+
+# Memcached configuration
+# ~~~~~
+# Enable memcached if needed. Otherwise a local cache is used.
+# memcached=enabled
+#
+# Specify memcached host (default to 127.0.0.1:11211)
+# memcached.host=127.0.0.1:11211
+#
+# Or you can specify multiple host to build a distributed cache
+# memcached.1.host=127.0.0.1:11211
+# memcached.2.host=127.0.0.1:11212
+#
+# Use plain SASL to authenticate for memcached
+# memcached.user=
+# memcached.password=
+
+# HTTP Response headers control for static files
+# ~~~~~
+# Set the default max-age, telling the user's browser how long it should cache the page.
+# Default is 3600 (one hour). Set it to 0 to send no-cache.
+# This is only read in prod mode, in dev mode the cache is disabled.
+# http.cacheControl=3600
+
+# If enabled, Play will generate entity tags automatically and send a 304 when needed.
+# Default is true, set it to false to deactivate use of entity tags.
+# http.useETag=true
+
+# Custom mime types
+# mimetype.xpi=application/x-xpinstall
+
+# WS configuration
+# ~~~~~
+# Default engine is Async Http Client, uncomment to use
+# the JDK's internal implementation
+# webservice = urlfetch
+# If you need to set proxy params for WS requests
+# http.proxyHost = localhost
+# http.proxyPort = 3128
+# http.proxyUser = jojo
+# http.proxyPassword = jojo
+
+# Mail configuration
+# ~~~~~
+# Default is to use a mock Mailer
+mail.smtp=mock
+
+# Or, specify mail host configuration
+# mail.smtp.host=127.0.0.1
+# mail.smtp.user=admin
+# mail.smtp.pass=
+# mail.smtp.channel=ssl
+
+# Url-resolving in Jobs
+# ~~~~~~
+# When rendering templates with reverse-url-resoling (@@{..}) in Jobs (which do not have an inbound Http.Request),
+# ie if sending a HtmlMail, Play need to know which url your users use when accessing your app.
+# %test.application.baseUrl=http://localhost:9000/
+# %prod.application.baseUrl=http://www.yourdomain.com/
+
+# Templates additional precompilation
+# ~~~~~~
+# yaml files can use template expressions and are located in the conf folder.
+# The default precompile task will not pickup those files.
+# You can manually add some files with the following property
+# (default: system property play.templates.compile, fallback system environment PLAY_TEMPLATES_COMPILE)
+# play.templates.compile=conf/initialdata.yml;{module:reporting}test/reporting/unittests/export.yml
+#
+# Override the path separator if you want to use the Play!-module notation like {module:modname} on unix.
+# (default: system property path.separator)
+# play.templates.compile.path.separator=;
+
+# Jobs executor
+# ~~~~~~
+# Size of the Jobs pool
+# play.jobs.pool=10
+
+# Execution pool
+# ~~~~~
+# Default to 1 thread in DEV mode or (nb processors + 1) threads in PROD mode.
+# Try to keep a low as possible. 1 thread will serialize all requests (very useful for debugging purpose)
+# play.pool=3
+
+# Netty pipeline configuration (advanced settings)
+# You can default netty settings by overriding the following line. Each handler must be comma separated.
+# The last value must be the PlayHandler class (or your own that extends PlayHandler)
+# Default values are
+# play.netty.pipeline = play.server.FlashPolicyHandler,org.jboss.netty.handler.codec.http.HttpRequestDecoder,play.server.StreamChunkAggregator,org.jboss.netty.handler.codec.http.HttpResponseEncoder,org.jboss.netty.handler.stream.ChunkedWriteHandler,play.server.PlayHandler
+# For example, to enable Netty response compression
+# play.netty.pipeline = play.server.FlashPolicyHandler,org.jboss.netty.handler.codec.http.HttpRequestDecoder,play.server.StreamChunkAggregator,org.jboss.netty.handler.codec.http.HttpResponseEncoder,org.jboss.netty.handler.codec.http.HttpContentCompressor,org.jboss.netty.handler.stream.ChunkedWriteHandler,play.server.PlayHandler
+# For SSL, use the play.ssl.netty.pipeline property
+# play.ssl.netty.pipeline = play.server.FlashPolicyHandler,org.jboss.netty.handler.codec.http.HttpRequestDecoder,play.server.StreamChunkAggregator,org.jboss.netty.handler.codec.http.HttpResponseEncoder,org.jboss.netty.handler.codec.http.HttpContentCompressor,org.jboss.netty.handler.stream.ChunkedWriteHandler,play.server.ssl.SslPlayHandler
+
+
+# Open file from errors pages
+# ~~~~~
+# If your text editor supports opening files by URL, Play! will
+# dynamically link error pages to files
+#
+# Example, for textmate:
+# play.editor=txmt://open?url=file://%s&line=%s
+
+# Testing. Set up a custom configuration for test mode
+# ~~~~~
+#%test.module.cobertura=${play.path}/modules/cobertura
+%test.application.mode=dev
+%test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0
+%test.jpa.ddl=create
+%test.mail.smtp=mock
+
diff --git a/samples-and-tests/nonstatic-app/conf/dependencies.yml b/samples-and-tests/nonstatic-app/conf/dependencies.yml
new file mode 100644
index 0000000000..85373765a1
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/conf/dependencies.yml
@@ -0,0 +1,4 @@
+# Application dependencies
+
+require:
+ - play
diff --git a/samples-and-tests/nonstatic-app/conf/messages b/samples-and-tests/nonstatic-app/conf/messages
new file mode 100644
index 0000000000..b51f29459d
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/conf/messages
@@ -0,0 +1,3 @@
+# You can specialize this file for each language.
+# For example, for French create a messages.fr file
+#
diff --git a/samples-and-tests/nonstatic-app/conf/routes b/samples-and-tests/nonstatic-app/conf/routes
new file mode 100644
index 0000000000..fcd1dbf193
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/conf/routes
@@ -0,0 +1,13 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+
+# Home page
+GET / Application.index
+GET /hello Application.hello
+
+# Ignore favicon requests
+GET /favicon.ico 404
+
+# Map static resources from the /app/public folder to the /public path
+GET /public/ staticDir:public
diff --git a/samples-and-tests/nonstatic-app/test/ApplicationTest.java b/samples-and-tests/nonstatic-app/test/ApplicationTest.java
new file mode 100644
index 0000000000..55da40b5ed
--- /dev/null
+++ b/samples-and-tests/nonstatic-app/test/ApplicationTest.java
@@ -0,0 +1,24 @@
+import org.junit.Test;
+import play.mvc.Http.Response;
+import play.test.FunctionalTest;
+
+public class ApplicationTest extends FunctionalTest {
+
+ @Test
+ public void indexPage() {
+ Response response = GET("/");
+ assertIsOk(response);
+ assertContentType("text/html", response);
+ assertCharset(play.Play.defaultWebEncoding, response);
+ assertContentMatch("Welcome to the non-static world!", response);
+ }
+
+ @Test
+ public void helloPage() {
+ Response response = GET("/hello");
+ assertIsOk(response);
+ assertContentType("text/plain", response);
+ assertCharset(play.Play.defaultWebEncoding, response);
+ assertContentMatch("Hello world!", response);
+ }
+}
\ No newline at end of file