diff --git a/.gitignore b/.gitignore index c1ea12a130..95da09e28b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +out bin framework/classes framework/test-classes @@ -55,11 +56,16 @@ samples-and-tests/with-gwt/nbproject samples-and-tests/jobboard/db samples-and-tests/jobboard/tmp samples-and-tests/jobboard/test-result +samples-and-tests/jobboard/modules +samples-and-tests/jobboard/lib samples-and-tests/jobboard/attachments samples-and-tests/just-test-cases/tmp samples-and-tests/just-test-cases/test-result +samples-and-tests/just-test-cases/lib +samples-and-tests/just-test-cases/modules samples-and-tests/chat/tmp samples-and-tests/chat/logs +samples-and-tests/java8Support/lib/ .git modules/spring/lib/play-spring.jar modules/search/bin @@ -78,6 +84,7 @@ support/textmate samples-and-tests/yabe/logs samples-and-tests/yabe/test-result samples-and-tests/yabe/tmp +samples-and-tests/yabe/modules *.ser modules/cobertura/lib modules/scala/lib/play-scala.jar diff --git a/.travis.yml b/.travis.yml index a8899aa734..7951a6671c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,30 @@ +sudo: required +dist: trusty language: java jdk: - - openjdk6 + - oraclejdk8 +#before_install: +# - cat /etc/hosts # optionally check the content *before* +# - sudo hostname "$(hostname | cut -c1-63)" +# - sed -e "s/^\\(127\\.0\\.0\\.1.*\\)/\\1 $(hostname | cut -c1-63)/" /etc/hosts | sudo tee /etc/hosts +# - sudo mv /tmp/hosts /etc/hosts +# - cat /etc/hosts # optionally check the content *after* +addons: + hosts: + - myshorthost + hostname: myshorthost + script: ant -buildfile ./framework/build.xml test -after_failure: - cat ./samples-and-tests/just-test-cases/test-result/*.failed.html - cat ./samples-and-tests/forum/test-result/*.failed.html - cat ./samples-and-tests/zencontact/test-result/*.failed.html - cat ./samples-and-tests/jobboard/test-result/*.failed.html - cat ./samples-and-tests/yabe/test-result/*.failed.html + +after_failure: + find samples-and-tests -name '*.failed.html' -exec echo {} \; -exec cat {} \; + notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/6da3b55e59cded84b8bd + on_success: always + on_failure: always email: on_success: change - on_failure: always \ No newline at end of file + on_failure: always diff --git a/README.textile b/README.textile index 3c5e41f9bf..c2ad1aca7d 100644 --- a/README.textile +++ b/README.textile @@ -1,4 +1,6 @@ -h1. Welcome to Play framework !https://travis-ci.org/playframework/play1.svg?branch=1.3.x!:https://travis-ci.org/playframework/play1 +h1. Welcome to Play framework + +!https://travis-ci.org/playframework/play1.svg?branch=1.4.x!:https://travis-ci.org/playframework/play1 !https://badges.gitter.im/playframework/play1.svg(Join the chat at https://gitter.im/playframework/play1)!:https://gitter.im/playframework/play1?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge Play framework makes it easier to build Web applications with Java. It is a clean alternative to bloated Enterprise Java stacks. It focuses on developer productivity and targets RESTful architectures. Play is a perfect companion to agile software development. @@ -8,11 +10,11 @@ h2. Getting started 1. Install the latest version of Play framework and unzip it anywhere you want: -bc. unzip play-1.3.0-RC1.zip -d /opt/play-1.3.0-RC1 +bc. unzip play-*.zip -d /opt/play 2. Add the **play** script to your PATH: -bc. export PATH=$PATH:/opt/play-1.3.0-RC1 +bc. export PATH=$PATH:/opt/play 3. Create a new Play application: @@ -28,7 +30,7 @@ bc. play run /opt/myFirstApp * "Your first application — the ‘Hello World’ tutorial":https://www.playframework.com/documentation/1.3.0-RC1/firstapp * "Tutorial — Play guide, a real world app step-by-step":https://www.playframework.com/documentation/1.3.0-RC1/guide1 -* "The essential documentation":https://www.playframework.com/documentation/1.3.0-RC1/home +* "The essential documentation":https://www.playframework.com/documentation/1.5.x/home * "Java API":https://www.playframework.com/@api/index.html h2. Get the source diff --git a/documentation/cheatsheets/tests/ch21-TestUnitTests.textile b/documentation/cheatsheets/tests/ch21-TestUnitTests.textile index b6442748fd..ec792557bb 100644 --- a/documentation/cheatsheets/tests/ch21-TestUnitTests.textile +++ b/documentation/cheatsheets/tests/ch21-TestUnitTests.textile @@ -14,7 +14,7 @@ Run before each unit test *==@After public void cleanupJunk()==* Run after each unit test -*==@BeforeClass void whenTestClassInstanciated();==* +*==@BeforeClass void whenTestClassInstantiated();==* Run once, when the test class gets instantiated *==@AfterClass void whenTestClassCompleted()==* diff --git a/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile b/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile index 19a517c331..4eb1e6e1b3 100644 --- a/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile +++ b/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile @@ -14,7 +14,7 @@ h2. Test - Unit Tests *==@After public void cleanupJunk()==* 各ユニットテストの後に実行されます。 -*==@BeforeClass void whenTestClassInstanciated();==* +*==@BeforeClass void whenTestClassInstantiated();==* 当該テストクラスがインスタンス化されたときに一度だけ実行されます。 *==@AfterClass void whenTestClassCompleted()==* diff --git a/documentation/commands/cmd-auto-test.txt b/documentation/commands/cmd-auto-test.txt index 6946bc6dcf..6003f4fae1 100644 --- a/documentation/commands/cmd-auto-test.txt +++ b/documentation/commands/cmd-auto-test.txt @@ -8,7 +8,7 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play auto-test [app_path] [--deps] [--unit] [--functional] [--selenium] +~ play auto-test [app_path] [--deps] [--unit] [--functional] [--selenium] [--timeout=xxx] ~ ~ Description: ~ ~~~~~~~~~~~~ @@ -44,4 +44,6 @@ ~ --functional: ~ Run the functional tests. ~ --selenium: -~ Run the selenium tests. \ No newline at end of file +~ Run the selenium tests. +~ --timeout=xxx: +~ Specify the timeout for the webclient (the value of the timeout in milliseconds, 0 for an infinite wait) \ No newline at end of file diff --git a/documentation/commands/cmd-dependencies.txt b/documentation/commands/cmd-dependencies.txt index 8f60e8ca3a..a5dfc62353 100644 --- a/documentation/commands/cmd-dependencies.txt +++ b/documentation/commands/cmd-dependencies.txt @@ -8,7 +8,7 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play dependencies [app_path] [--verbose] [--debug] [--sync] [--%fwk_id] [--forProd] [--clearcache] [--jpda] +~ play dependencies [app_path] [--verbose] [--debug] [--sync] [--nosync] [--%fwk_id] [--forProd] [--clearcache] [--jpda] [--shortModuleNames] ~ ~ Description: ~ ~~~~~~~~~~~~ @@ -34,7 +34,7 @@ ~ Verbose mode ~ ~ --debug: -~ Debug mode (even more informations logged than in verbose mode) +~ Debug mode (even more information logged than in verbose mode) ~ ~ --jpda: ~ Listen for JPDA connection. The process will be suspended until @@ -43,6 +43,11 @@ ~ --sync: ~ Keep lib/ and modules/ directory synced. ~ Delete unknown dependencies. +~ Since 1.4.4: This option is activated by default +~ +~ --nosync: +~ Keep lib/ and modules/ directory unchanged. +~ Keep unknown dependencies. ~ ~ --forceCopy: ~ Never create files pointing to the original folder, always copy the folder @@ -58,3 +63,9 @@ ~~ --clearcache: ~ Clear the ivy cache (equivalent to rm -r ~/.ivy2/cache) ~ +~~ --shortModuleNames: +~ use short module name without version number. +~ For example, if project depends on module "play-pdf 1.2.3", then +~ * command `play deps` creates folder `modules/pdf-1.2.3` +~ * command `play deps --shortModuleNames` creates folder `modules/pdf` +~ The latter option is probably convenient for configuring IDE diff --git a/documentation/commands/cmd-status.txt b/documentation/commands/cmd-status.txt index 2969bbbf40..9ccf8ceb12 100644 --- a/documentation/commands/cmd-status.txt +++ b/documentation/commands/cmd-status.txt @@ -13,7 +13,7 @@ ~ Description: ~ ~~~~~~~~~~~~ ~ This script tries to connect to the running application's /@status URL to request the application status. -~ The application status contains useful informations about the running application. +~ The application status contains useful information about the running application. ~ ~ The status command is aimed at monitoring applications running on production servers. ~ diff --git a/documentation/files/main.css b/documentation/files/main.css index e1d52c7287..baad7112da 100644 --- a/documentation/files/main.css +++ b/documentation/files/main.css @@ -60,7 +60,7 @@ body { -moz-border-radius: 16px; } -/** A little hacky to create arrows without images **/ +/** A little hack to create arrows without images **/ .about { text-indent: -999em; display: block; diff --git a/documentation/manual/asynchronous.textile b/documentation/manual/asynchronous.textile index 3574c01008..5de008ed88 100644 --- a/documentation/manual/asynchronous.textile +++ b/documentation/manual/asynchronous.textile @@ -1,6 +1,6 @@ h1. Asynchronous programming with HTTP -This section explains how to deal with asynchonism in a Play application to achieve typical long-polling, streaming and other "Comet-style":http://en.wikipedia.org/wiki/Comet_(programming%29 applications that can scale to thousands of concurrent connections. +This section explains how to deal with asynchronism in a Play application to achieve typical long-polling, streaming and other "Comet-style":http://en.wikipedia.org/wiki/Comet_(programming%29 applications that can scale to thousands of concurrent connections. h2. Suspending HTTP requests @@ -114,7 +114,7 @@ h2. Using WebSockets WebSockets are a way to open a two-way communication channel between a browser and your application. On the browser side, you open a socket using a "ws://" url: -bc. new Socket("ws://localhost:9000/helloSocket?name=Guillaume") +bc. new WebSocket("ws://localhost:9000/helloSocket?name=Guillaume") On the Play side you declare a WS route: @@ -124,7 +124,7 @@ bc. WS /helloSocket MyWebSocket.hello * It has a request object, but no response object. * It has access to the session, but read-only. -* It doesn’t have @renderArgs@, @routeArgs@ or flash scope. +* It doesn't have @renderArgs@, @routeArgs@ or flash scope. * It can read params only from the route pattern or from the QueryString. * It has two communication channels: inbound and outbound. diff --git a/documentation/manual/cache.textile b/documentation/manual/cache.textile index 801453bf8d..f79187d604 100644 --- a/documentation/manual/cache.textile +++ b/documentation/manual/cache.textile @@ -4,9 +4,9 @@ To create high-performance systems, sometimes you need to cache data. Play has a If you don’t configure Memcached, Play will use a standalone cache that stores data in the JVM heap. Caching data in the JVM application breaks the ‘share nothing’ assumption made by Play: you can’t run your application on several servers, and expect the application to behave consistently. Each application instance will have a different copy of the data. -It is important to understand that the cache contract is clear: when you put data in a cache, you can’t expect that data to remain there forever. In fact you shouldn’t. A cache is fast, but values expire, and the cache generally exists only in memory (without persistent backup). +It is important to understand that the cache contract is clear: when you put data in a cache, you can’t expect that data to remain there forever. In fact you shouldn't. A cache is fast, but values expire, and the cache generally exists only in memory (without persistent backup). -So the best way to use the cache is to repopulate it when it doesn’t have what you expect: +So the best way to use the cache is to repopulate it when it doesn't have what you expect: bc. public static void allProducts() { List products = Cache.get("products", List.class); @@ -23,7 +23,7 @@ For example, the "GAE module":https://www.playframework.com/modules/gae configur h2. The cacheFor annotation -The @cacheFor@ annotation provides an easy way to cache the ouput (HTML, JSON, etc) of an action. +The @cacheFor@ annotation provides an easy way to cache the output (HTML, JSON, etc) of an action. bc. import play.cache.*; public class Application extends Controller { diff --git a/documentation/manual/configuration.textile b/documentation/manual/configuration.textile index 48e857d033..1968376c33 100644 --- a/documentation/manual/configuration.textile +++ b/documentation/manual/configuration.textile @@ -29,6 +29,15 @@ bc. application.defaultCookieDomain=.example.com Default: a cookie is only valid for a specific domain. +h3(#application.forceSecureReverseRoutes). application.forceSecureReverseRoutes + +Forces all reverse routes and controller action redirection to be secure (https). This is useful for SSL-enabled apps to prevent insecure (http) url generation when redirecting to another action from inside your controllers, or when using the @{..} template syntax. For example: + +bc. application.forceSecureReverseRoutes=true + +Default: @false@ + + h3(#application.lang.cookie). application.lang.cookie The name of the cookie that is used to store the current language, set by @play.i18n.Lang.change(String locale)@, which you can change if you want separate language settings for separate Play applications. For example: @@ -286,7 +295,11 @@ To reuse an existing Datasource from your application server: bc. db=java:/comp/env/jdbc/myDatasource@ -If you specify a @Datasource@, the database plugin detects the pattern @db=java:@ and will de-activate the default JDBC system. +And for application server where Datasource doesn't have @java:@ prefix (Glassfish and its derivative), you could use @jndi:@ instead. The @jndi:@ prefix will be omitted: + +bc. db=jndi:jdbc/myDataSource + +If you specify a @Datasource@, the database plugin detects the pattern @db=java:@ and @db=jndi:@ and will de-activate the default JDBC system. Default: none. @@ -327,6 +340,73 @@ Database connection password, used with "db.url":#db.url. Default: no value, or an empty string when "db":#db is set to @mem@ or @fs@. +h3(#db.pool.acquireIncrement). db.pool.acquireIncrement + +Determines how many connections at a time c3p0 will try to acquire when the pool is exhausted. + +Default: @3@ + +h3(#db.pool.acquireRetryAttempts). db.pool.acquireRetryAttempts + +Defines how many times c3p0 will try to acquire a new Connection from the database before giving up. If this value is less than or equal to zero, c3p0 will keep trying to fetch a Connection indefinitely. + +Default: @10@ + +h3(#db.pool.acquireRetryDelay). db.pool.acquireRetryDelay + +Milliseconds, time c3p0 will wait between acquire attempts. + +Default: @1000@ + +h3(#db.pool.breakAfterAcquireFailure). db.pool.breakAfterAcquireFailure + +If true, a pooled DataSource will declare itself broken and be permanently closed if a Connection cannot be obtained from the database after making acquireRetryAttempts to acquire one. If false, failure to obtain a Connection will cause all Threads waiting for the pool to acquire a Connection to throw an Exception, but the DataSource will remain valid, and will attempt to acquire again following a call to getConnection(). + +Default: @false@ + +h3(#db.pool.unreturnedConnectionTimeout). db.pool.unreturnedConnectionTimeout + +Seconds. If set, if an application checks out but then fails to check-in [i.e. close()] a Connection within the specified period of time, the pool will unceremoniously destroy() the Connection. This permits applications with occasional Connection leaks to survive, rather than eventually exhausting the Connection pool. And that's a shame. Zero means no timeout, applications are expected to close() their own Connections. Obviously, if a non-zero value is set, it should be to a value longer than any Connection should reasonably be checked-out. Otherwise, the pool will occasionally kill Connections in active use, which is bad. + +Use this temporarily in combination with debugUnreturnedConnectionStackTraces to figure out where Connections are being checked-out that don't make it back into the pool! + +Default: @0@ + +h3(#db.pool.debugUnreturnedConnectionStackTraces). db.pool.debugUnreturnedConnectionStackTraces + +f true, and if unreturnedConnectionTimeout is set to a positive value, then the pool will capture the stack trace (via an Exception) of all Connection checkouts, and the stack traces will be printed when unreturned checked-out Connections timeout. This is intended to debug applications with Connection leaks, that is applications that occasionally fail to return Connections, leading to pool growth, and eventually exhaustion (when the pool hits maxPoolSize with all Connections checked-out and lost). This parameter should only be set while debugging, as capturing the stack trace will slow down every Connection check-out. + +Default: @false@ + +h3(#db.pool.testConnectionOnCheckout). db.pool.testConnectionOnCheckout + +If true, an operation will be performed at every connection checkout to verify that the connection is valid. Be sure to set an efficient preferredTestQuery or automaticTestTable if you set this to true. Performing the (expensive) default Connection test on every client checkout will harm client performance. Testing Connections in checkout is the simplest and most reliable form of Connection testing, but for better performance, consider verifying connections periodically using idleConnectionTestPeriod. + +Default: @false@ + +h3(#db.pool.testConnectionOnCheckin). db.pool.testConnectionOnCheckin + +If true, an operation will be performed asynchronously at every connection checkin to verify that the connection is valid. Use in combination with idleConnectionTestPeriod for quite reliable, always asynchronous Connection testing. Also, setting an automaticTestTable or preferredTestQuery will usually speed up all connection tests. + +Default: @true@ + +h3(#db.pool.maxConnectionAge). db.pool.maxConnectionAge + +Seconds, effectively a time to live. A Connection older than maxConnectionAge will be destroyed and purged from the pool. This differs from maxIdleTime in that it refers to absolute age. Even a Connection which has not been much idle will be purged from the pool if it exceeds maxConnectionAge. Zero means no maximum absolute age is enforced. + +Default: @0@ + +h3(#db.pool.maxIdleTime). db.pool.maxIdleTime + +Seconds a Connection can remain pooled but unused before being discarded. Zero means idle connections never expire. + +Default: @0@ + +h3(#db.pool.idleConnectionTestPeriod). db.pool.idleConnectionTestPeriod + +If this is a number greater than 0, c3p0 will test all idle, pooled but unchecked-out connections, every this number of seconds. + +Default: @10@ h3(#db.pool.maxIdleTimeExcessConnections). db.pool.maxIdleTimeExcessConnections @@ -352,6 +432,14 @@ bc. db.pool.minSize=10 Default: @1@ +h3(#db.pool.initialSize). db.pool.initialSize + +Number of Connections a pool will try to acquire upon startup. Should be between minPoolSize and maxPoolSize. See the "c3p0 documentation":http://www.mchange.com/projects/c3p0/#initialPoolSize. For example: + +bc. db.pool.initialSize=5 + +Default: @1@ + h3(#db.pool.timeout). db.pool.timeout @@ -361,6 +449,24 @@ bc. db.pool.timeout=10000 Default: @5000@ +h3(#db.pool.loginTimeout). db.pool.loginTimeout + +Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. + +Default: @0@ + +h3(#db.pool.maxStatements). db.pool.maxStatements + +The size of c3p0's global PreparedStatement cache. If both maxStatements and maxStatementsPerConnection are zero, statement caching will not be enabled. + +Default: @0@ + +h3(#db.pool.maxStatementsPerConnection). db.pool.maxStatementsPerConnection + +The number of PreparedStatements c3p0 will cache for a single pooled Connection. If both maxStatements and maxStatementsPerConnection are zero, statement caching will not be enabled. + +Default: @0@ + h3(#db.url). db.url @@ -384,6 +490,18 @@ Override query to use when keepalive connection polling is being performed. The Default: null Default on MySQL: /* ping */ SELECT 1 +h3(#db.pool.maxAdministrativeTaskTime). db.pool.maxAdministrativeTaskTime + +Seconds before c3p0's thread pool will try to interrupt an apparently hung task. Rarely useful. + +Default: @0@ + +h3(#db.pool.numHelperThreads). db.pool.numHelperThreads + +c3p0 is very asynchronous. Slow JDBC operations are generally performed by helper threads that don't hold contended locks. Spreading these operations over multiple threads can significantly improve performance by allowing multiple operations to be performed simultaneously. + +Default: @3@ + h2(#evolutions). Database evolutions @@ -428,17 +546,27 @@ h3(#headlessBrowser). headlessBrowser Specifies the web browser compatibility mode for the HtmlUnit headless web browser used when running @play auto-test@. -bc. headlessBrowser=INTERNET_EXPLORER_7 +bc. headlessBrowser=FIREFOX_38 Values: -* @FIREFOX_3@ -* @FIREFOX_3_6@ -* @INTERNET_EXPLORER_6@ -* @INTERNET_EXPLORER_7@ +* @CHROME@ +* @FIREFOX_31@ * @INTERNET_EXPLORER_8@ +* @INTERNET_EXPLORER_11@ +* @EDGE@ + +Default: @FIREFOX_38@ + +h3(#webclient.timeout). webclient.timeout + +Specifies the timeout of the WebConnection. Set to zero for an infinite wait. -Default: @INTERNET_EXPLORER_8@ +Note: The timeout is used twice. The first is for making the socket connection, the second is for data retrieval. If the time is critical you must allow for twice the time specified here. + +bc. webclient.timeout=60 + +This value can be set directly in the command @play auto-test@ by adding @--timeout=60@ h2(#hibernate). Hibernate @@ -578,11 +706,11 @@ h3(#java.source). java.source Java source level, which overrides the @java.version@ system property. For example: -bc. java.source=1.7 +bc. java.source=1.8 -Values: @1.5@ (No longer supported since 1.3.0), @1.6@, @1.7@, @1.8@ (experimental). +Values: @1.7@ (No longer supported since 1.5.0), @1.8@. -Default: @1.6@ +Default: @1.8@ h2(#jpa). JPA @@ -911,6 +1039,14 @@ bc. play.ssl.enabledCiphers=SSL_RSA_WITH_RC4_128_MD5,SSL_RSA_WITH_RC4_128_SHA Default: none - the default ciphers are chosen. +h3(#play.ssl.enabledProtocols). play.ssl.enabledProtocols + +This setting allows to specify certain SSL protocols to be used. + +bc. play.ssl.enabledProtocols=TLSv1,TLSv1.1,TLSv1.2 + +Default: none - the default protocols are chosen. + h3(#play.pool). play.pool @@ -921,6 +1057,32 @@ bc. play.pool=10 Default: @1@ (in @dev@ mode), number of processors + 1 (in @prod@ mode). +h3(#play.templates.compile). play.templates.compile + +The value is a list of files separated by a property like "@path.separator@":configuration#play.templates.compile.path.separator . + +bc. play.templates.compile=/absolue/path/file.ext;conf/relative/path/file.ext;{module:reporting}module/relative/path/file.ext + +The path notation of files can use: + +* absolute file path +* relative file path +* module relative file path. + +Default: no default the parameter is optional + + +h3(#play.templates.compile.path.separator). play.templates.compile.path.separator + +Override the path separator for separating list elements of @play.templates.compile@":configuration#play.templates.compile . +This parameter would be usefull when using the path notation for module relative file path on unix systems, +where the default value would be @:@ . That would separate module prefix notations like @{module:modname}@ on unix. + +bc. play.templates.compile.path.separator=; + +Default: the system property @path.separator@ + + h3(#play.tmp). play.tmp Folder used to store temporary files. For example: @@ -931,7 +1093,7 @@ Values: * an absolute path * a relative path, relative to the application directory -* @none@ so that no temporary directory will be used +* @none@ so that no temporary directory will be used Default: @tmp@ @@ -1003,6 +1165,21 @@ bc. upload.threshold=20480 Default: @10240@ +h3(#upload.maxRequestSize). upload.maxRequestSize + +The maximum size permitted for the complete request. A value of -1 indicates no maximum. + +bc. upload.maxRequestSize=2048 + +Default: @-1@ + +h3(#upload.maxFileSize). upload.maxFileSize + +The maximum size permitted for a single uploaded file, as opposed to. A value of -1 indicates no maximum. + +bc. upload.maxFileSize=1024 + +Default: @-1@ h2(#xforwarded). Proxy forwarding @@ -1026,12 +1203,34 @@ A comma-separated list of IP addresses that are allowed @X-Forwarded-For@ HTTP r Default: @127.0.0.1@ +h3(#XForwardedOverwriteDomainAndPort). XForwardedOverwriteDomainAndPort + +Set to true to restore the request domain and port to original domain and port. + +For example: + +bc. XForwardedOverwriteDomainAndPort=true + +Default: @false@ + h2(#enhancers). Enhancers h3(#play.propertiesEnhancer.enabled). play.propertiesEnhancer.enabled -Used to disable play enhancing of play class (can be used to switch off getter/setter generation). For example: +Used to disable play enhancing of play class (can be used to switch off default constructors/getter/setter generation). For example: bc. play.propertiesEnhancer.enabled=false Default: @true@ + +h3(#play.propertiesEnhancer.generateAccessors). play.propertiesEnhancer.generateAccessors + +Used to partially disable PropertyEnhancer. Namely, it switches off generating getters/setters and replacing +all direct field accesses with getters/setters (which is the slowest part of "play precompile"). +But it leaves generating default constructors. + +For example: + +bc. play.propertiesEnhancer.generateAccessors=false + +Default: @true@ diff --git a/documentation/manual/controllers.textile b/documentation/manual/controllers.textile index 32b634460c..9b3953d6d4 100644 --- a/documentation/manual/controllers.textile +++ b/documentation/manual/controllers.textile @@ -34,22 +34,38 @@ public class Clients extends Controller { Client client = Client.findById(id); render(client); } - - public static void delete(Long id) { + + public void delete(Long id) { Client client = Client.findById(id); client.delete(); } } -Each public, static method in a Controller is called an action. The signature for an action method is always: +Each public method in a Controller is called an action. In case an action method is not static, a new instance of the controller class is created for each request, and all controller methods are invoked on the same instance. This works only if the controller has a zero-argument constructor (or no constructors at all). + +The signature for an action method is: + +bc. public void static action_name(params...); + +or -bc. public static void action_name(params...); +bc. public void action_name(params...); You can define parameters in the action method signature. These parameters will be automatically resolved by the framework from the corresponding HTTP parameters. Usually, an action method doesn’t include a return statement. The method exit is done by the invocation of a **result** method. In this example, @render(…)@ is a result method that executes and displays a template. +h2. Static or non-static? + +In the past, all controller methods had to be static. However, non-static methods have many advantages in Java: + +* They can be overridden in subclasses. +* They are easier to mock/unit-test. +* They allow storing of request-scoped state in controller fields, if needed. + +Having that said, both static and non-static controller methods are supported. + h2. Retrieving HTTP parameters An HTTP request contains data. This data can be extracted from: @@ -162,7 +178,7 @@ bc. public static void articlesSince(@As("dd/MM/yyyy") Date from) { You can of course refine the date format according to the language. For example: -bc. public static void articlesSince(@As(lang={"fr,de","*"}, +bc. public static void articlesSince(@As(lang={"fr,de","*"}, value={"dd-MM-yyyy","MM-dd-yyyy"}) Date from) { List
articles = Article.findBy("date >= ?", from); render(articles); @@ -350,7 +366,7 @@ bc. public class MyCustomStringBinder implements TypeBinder, TypeUnbinde You can use it in any action, like: -bc. public static void anyAction(@As(binder=MyCustomStringBinder.class) +bc. public static void anyAction(@As(binder=MyCustomStringBinder.class) String name) { … } @@ -453,10 +469,10 @@ h3. Return binary content To serve binary data, such as a "file stored on the server":jpa#file, use the @renderBinary@ method. For example, if you have a @User@ model with a @play.db.jpa.Blob photo@ property, add a controller method to load the model object and render the image with the stored MIME type: -bc. public static void userPhoto(long id) { - final User user = User.findById(id); +bc. public static void userPhoto(long id) { + final User user = User.findById(id); response.setContentTypeIfNotSet(user.photo.type()); - java.io.InputStream binaryData = user.photo.get(); + InputStream binaryData = user.photo.get(); renderBinary(binaryData); } @@ -606,7 +622,7 @@ h2. Interceptions A controller can define interception methods. Interceptors are invoked for all actions of the controller class and its descendants. It’s a useful way to define treatments that are common to all actions: verifying that a user is authenticated, loading request-scope information… -These methods have to be @static@ but not @public@. You have to annotate these methods with a valid interception marker. +These methods have to be @static@ or not but not @public@, but same as action methods, they can be either @static@ or non-static. You have to annotate these methods with a valid interception marker. h3. @Before @@ -650,7 +666,7 @@ Or if you want the @Before method to intercept a list of action calls, you can s bc. public class Admin extends Controller { @Before(only={"login","logout"}) - static void doSomething() { + static void doSomething() { … } … diff --git a/documentation/manual/dependency.textile b/documentation/manual/dependency.textile index 0ed59ea8e4..2c2266a9cf 100644 --- a/documentation/manual/dependency.textile +++ b/documentation/manual/dependency.textile @@ -262,7 +262,7 @@ bc. play dependencies ~ ~ Some dependencies have been evicted, ~ -~ commons-lang 3.0 is overriden by commons-lang 2.5 +~ commons-lang 3.0 is overridden by commons-lang 2.5 ~ ~ Installing resolved dependencies, ~ diff --git a/documentation/manual/guide7.textile b/documentation/manual/guide7.textile index 376a7d5fbf..ee9327c1d4 100644 --- a/documentation/manual/guide7.textile +++ b/documentation/manual/guide7.textile @@ -347,7 +347,7 @@ bc. #{crud.form} #{/crud.custom} #{/crud.form} -This is a little hacky and we could do better here, but we have now a simpler tags selector using a little bit of JavaScript: +This is a little hack and we could do better here, but we have now a simpler tags selector using a little bit of JavaScript: !images/guide7-6! diff --git a/documentation/manual/home.textile b/documentation/manual/home.textile index 93f8f43d5c..28dc329e3f 100644 --- a/documentation/manual/home.textile +++ b/documentation/manual/home.textile @@ -1,6 +1,6 @@ h1. Documentation -Welcome to the *Play framework 1.3.0* documentation. Check the "version 1.3.0. release notes":releasenotes-1.3.0. +Welcome to the *Play framework 1.5.x* documentation. Check the "version 1.5.x. release notes":releases/release1.5.x/releasenotes-1.5.x. h2. Getting started @@ -188,13 +188,23 @@ h2. Version notes New versions of Play include certain changes. Check older release notes for: -# "Play 1.3.0":releasenotes-1.3.0 -# "Play 1.2.7":releasenotes-1.2.7 -# "Play 1.2.6":releasenotes-1.2.6 -# "Play 1.2.5":releasenotes-1.2.5 -# "Play 1.2.4":releasenotes-1.2.4 -# "Play 1.2":releasenotes-1.2 -# "Play 1.1":releasenotes-1.1 -# "Play 1.0.3":releasenotes-1.0.3 -# "Play 1.0.2":releasenotes-1.0.2 -# "Play 1.0.1":releasenotes-1.0.1 +# "About Play releases":releases/releases +# "Play 1.4.4":releases/release1.4.x/releasenotes-1.4.4 +# "Play 1.4.3":releases/release1.4.x/releasenotes-1.4.3 +# "Play 1.4.2":releases/release1.4.x/releasenotes-1.4.2 +# "Play 1.4.1":releases/release1.4.x/releasenotes-1.4.1 +# "Play 1.4.0":releases/release1.4.x/releasenotes-1.4.0 +# "Play 1.3.4":releases/release1.3.x/releasenotes-1.3.4 +# "Play 1.3.3":releases/release1.3.x/releasenotes-1.3.3 +# "Play 1.3.2":releases/release1.3.x/releasenotes-1.3.2 +# "Play 1.3.1":releases/release1.3.x/releasenotes-1.3.1 +# "Play 1.3.0":releases/release1.3.x/releasenotes-1.3.0 +# "Play 1.2.7":releases/release1.2.x/releasenotes-1.2.7 +# "Play 1.2.6":releases/release1.2.x/releasenotes-1.2.6 +# "Play 1.2.5":releases/release1.2.x/releasenotes-1.2.5 +# "Play 1.2.4":releases/release1.2.x/releasenotes-1.2.4 +# "Play 1.2":releases/release1.2.x/releasenotes-1.2 +# "Play 1.1":releases/release1.1.x/releasenotes-1.1 +# "Play 1.0.3":releases/release1.0.x/releasenotes-1.0.3 +# "Play 1.0.2":releases/release1.0.x/releasenotes-1.0.2 +# "Play 1.0.1":releases/release1.0.x/releasenotes-1.0.1 diff --git a/documentation/manual/releasenotes-1.0.1.textile b/documentation/manual/releases/release1.0.x/releasenotes-1.0.1.textile similarity index 98% rename from documentation/manual/releasenotes-1.0.1.textile rename to documentation/manual/releases/release1.0.x/releasenotes-1.0.1.textile index 8f3587fe7d..7b6fbd1283 100644 --- a/documentation/manual/releasenotes-1.0.1.textile +++ b/documentation/manual/releases/release1.0.x/releasenotes-1.0.1.textile @@ -12,7 +12,7 @@ Now all dynamic expressions are escaped by the template engine to avoid XSS secu bc. ${title} --> <h1>Title</h1> -If you really want to display it in an unescaped way, you need to explicitely call the @raw()@ method: +If you really want to display it in an unescaped way, you need to explicitly call the @raw()@ method: bc. ${title.raw()} -->

Title

@@ -119,7 +119,7 @@ bc. public class User { h2. Test runner update -We’ve updated Selenium to the 1.0.1 final version and improved the UI. Selenium tests now run in fullscreen. And some new functionality like the "Run all tests" have been added. +We’ve updated Selenium to the 1.0.1 final version and improved the UI. Selenium tests now run in full screen. And some new functionality like the "Run all tests" have been added. !images/selenium-fullscreen! diff --git a/documentation/manual/releasenotes-1.0.2.textile b/documentation/manual/releases/release1.0.x/releasenotes-1.0.2.textile similarity index 100% rename from documentation/manual/releasenotes-1.0.2.textile rename to documentation/manual/releases/release1.0.x/releasenotes-1.0.2.textile diff --git a/documentation/manual/releasenotes-1.0.3.textile b/documentation/manual/releases/release1.0.x/releasenotes-1.0.3.textile similarity index 100% rename from documentation/manual/releasenotes-1.0.3.textile rename to documentation/manual/releases/release1.0.x/releasenotes-1.0.3.textile diff --git a/documentation/manual/releasenotes.textile b/documentation/manual/releases/release1.0.x/releasenotes.textile similarity index 97% rename from documentation/manual/releasenotes.textile rename to documentation/manual/releases/release1.0.x/releasenotes.textile index c0f1a43f1d..b192ba9c86 100644 --- a/documentation/manual/releasenotes.textile +++ b/documentation/manual/releases/release1.0.x/releasenotes.textile @@ -12,7 +12,7 @@ Now all dynamic expressions are escaped by the template engine to avoid XSS secu bc. ${title} --> <h1>Title</h1> -If you really want to display it in an unescaped way, you need to explicitely call the **raw()** method: +If you really want to display it in an unescaped way, you need to explicitly call the **raw()** method: bc. ${title.raw()} -->

Title

@@ -71,7 +71,7 @@ bc. public static void save(User user) { user.save(); // ok with 1.0.1 } -Of course as this feature can break existing applications it is not enabled by default. You can enable it by adding the followin line to your **application.conf** file: +Of course as this feature can break existing applications it is not enabled by default. You can enable it by adding the following line to your **application.conf** file: bc. future.bindJPAObjects=true @@ -119,7 +119,7 @@ bc. public class User { h2. Test runner update -We've updated selenium to the 1.0.1 final version and improved the UI. Selenium tests now run in fullscreen. And some new functionalities like the "Run all tests" have been added. +We've updated selenium to the 1.0.1 final version and improved the UI. Selenium tests now run in full screen. And some new functionalities like the "Run all tests" have been added. !images/selenium-fullscreen! diff --git a/documentation/manual/releasenotes-1.1.textile b/documentation/manual/releases/release1.1.x/releasenotes-1.1.textile similarity index 100% rename from documentation/manual/releasenotes-1.1.textile rename to documentation/manual/releases/release1.1.x/releasenotes-1.1.textile diff --git a/documentation/manual/releasenotes-1.2.4.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.4.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.4.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.4.textile diff --git a/documentation/manual/releasenotes-1.2.5.5.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.5.5.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.5.5.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.5.5.textile diff --git a/documentation/manual/releases/release1.2.x/releasenotes-1.2.5.6.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.5.6.textile new file mode 100644 index 0000000000..04d79085b9 --- /dev/null +++ b/documentation/manual/releases/release1.2.x/releasenotes-1.2.5.6.textile @@ -0,0 +1,7 @@ +h1. Play 1.2.5.6 -- Release notes + +Play 1.2.5.6 has been released of the 1.2.5.x-security maintenance branch. + +h2. What's fixed in Play 1.2.5.6 + +Fix vulnerabilty : Reset current request to avoid 3rd-party to acquire session information for another in-progress request diff --git a/documentation/manual/releasenotes-1.2.5.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.5.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.5.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.5.textile diff --git a/documentation/manual/releasenotes-1.2.6.1.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.6.1.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.6.1.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.6.1.textile diff --git a/documentation/manual/releases/release1.2.x/releasenotes-1.2.6.2.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.6.2.textile new file mode 100644 index 0000000000..64fc0a3962 --- /dev/null +++ b/documentation/manual/releases/release1.2.x/releasenotes-1.2.6.2.textile @@ -0,0 +1,8 @@ +h1. Play 1.2.6.2 -- Release notes + +Play 1.2.6.2 has been released of the 1.2.6.x-security maintenance branch. + +h2. What's fixed in Play 1.2.6.2 + +Fix vulnerabilty : Reset current request to avoid 3rd-party to acquire session information for another in-progress request + diff --git a/documentation/manual/releasenotes-1.2.6.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.6.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.6.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.6.textile diff --git a/documentation/manual/releasenotes-1.2.7.2.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.7.2.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.7.2.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.7.2.textile diff --git a/documentation/manual/releasenotes-1.2.7.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.7.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.7.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.7.textile diff --git a/documentation/manual/releasenotes-1.2.textile b/documentation/manual/releases/release1.2.x/releasenotes-1.2.textile similarity index 100% rename from documentation/manual/releasenotes-1.2.textile rename to documentation/manual/releases/release1.2.x/releasenotes-1.2.textile diff --git a/documentation/manual/releasenotes-1.3.0.textile b/documentation/manual/releases/release1.3.x/releasenotes-1.3.0.textile similarity index 100% rename from documentation/manual/releasenotes-1.3.0.textile rename to documentation/manual/releases/release1.3.x/releasenotes-1.3.0.textile diff --git a/documentation/manual/releasenotes-1.3.1.textile b/documentation/manual/releases/release1.3.x/releasenotes-1.3.1.textile similarity index 100% rename from documentation/manual/releasenotes-1.3.1.textile rename to documentation/manual/releases/release1.3.x/releasenotes-1.3.1.textile diff --git a/documentation/manual/releases/release1.3.x/releasenotes-1.3.2.textile b/documentation/manual/releases/release1.3.x/releasenotes-1.3.2.textile new file mode 100644 index 0000000000..c9a85631fe --- /dev/null +++ b/documentation/manual/releases/release1.3.x/releasenotes-1.3.2.textile @@ -0,0 +1,25 @@ +h1. Play 1.3.2 -- Release notes + +Play 1.3.2 has been released of the 1.3.x maintenance branch. + +The changes in this release are listed in the "Play 1.3.2 milestone":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/211835-132 on Lighthouse, including 31 resolved tickets. + +h2. What's new in Play 1.3.2 + +* Add ability to define the timeout in testRunner module +* Add ability to manually set the VirtualHost of WS +* Improve performance of Router.reverse() +* Allow upload a 0B file +* add '--server' arg to install command commands python to specify just ONE custom repository for module installation + + +h2. What's fixed in Play 1.3.2. + +* Fix redirect to wrong domain and port behind apache +* Customize JSON rendering by passing the Gson serializer object json render +* Getting Static Initialization Deadlock in class DataParser dataparser +* Allow zero-length blobs binder +* OpenID discovery fails in 1.3.x in some cases openid +* Problem to run specific tests selenium testrunner tests +* 500.html template redering issue +* play.libs.image.crop don't close the OutputStream image diff --git a/documentation/manual/releases/release1.3.x/releasenotes-1.3.3.textile b/documentation/manual/releases/release1.3.x/releasenotes-1.3.3.textile new file mode 100644 index 0000000000..99e3bb2004 --- /dev/null +++ b/documentation/manual/releases/release1.3.x/releasenotes-1.3.3.textile @@ -0,0 +1,18 @@ +h1. Play 1.3.3 -- Release notes + +Play 1.3.3 has been released of the 1.3.x maintenance branch. + +The changes in this release are listed in the "Play 1.3.3 milestone":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/216577-141 on Lighthouse, including 26 resolved tickets. +Play 1.3.3 is a maintenance release so it mostly contains bug fixes. The most important are: + +h2. What's new in Play 1.3.3 + +* Add ability to define enabled ssl protocols +* Make DB properties configurable + +h2. What's fixed in Play 1.3.3 + +* Fix vulnerabilty : Reset current request to avoid 3rd-party to acquire session information for another in-progress request +* Fix putting property to customer DB configuration +* Add method Plugin.onActionInvocationFinally() +* Fix javadoc tools errors diff --git a/documentation/manual/releases/release1.3.x/releasenotes-1.3.4.textile b/documentation/manual/releases/release1.3.x/releasenotes-1.3.4.textile new file mode 100644 index 0000000000..718d3e1b8c --- /dev/null +++ b/documentation/manual/releases/release1.3.x/releasenotes-1.3.4.textile @@ -0,0 +1,32 @@ +h1. Play 1.3.4 -- Release notes + +Play 1.3.4 has been released of the 1.3.x maintenance branch. + +The changes in this release are listed in the "Play 1.3.4 milestone":https://github.com/playframework/play1/wiki/Changelogs#play-134, including 22 resolved tickets. +Play 1.3.4 is a maintenance release so it mostly contains bug fixes. The most important are: + +h2. What's fixed in Play 1.3.4 + +* Fix vulnerabilty : Escape flash message in the secure module (login page) +* "#2008":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2008-error-while-binding-an-enumset Error while binding an EnumSet +* "#2011":https://play.lighthouseapp.com/projects/57987/tickets/2011-support-double-semicolon-escaping-in-evolution-scripts) Support "double semicolon" escaping in evolution scripts +* "#2007":https://play.lighthouseapp.com/projects/57987/tickets/2007 JPA classloader issues with precompiled app +* "#2014":https://play.lighthouseapp.com/projects/57987/tickets/2014 Message not appear correctly in secure module +* "#1934":https://play.lighthouseapp.com/projects/57987/tickets/193) feat(controller): Support for non-static controller methods - they are easier to mock/unit-test +* "#2009":https://play.lighthouseapp.com/projects/57987/tickets/2009 Field name detection in 'attachment' +* "#2017":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2017 Use StringBuilder instead of StringBuffer +* "#2016":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2016 default value of "XForwardedOverwriteDomainAndPort" setting should be false +* "#1979":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/1979 Improve performance of Play! +* "#2021":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2021 Optimize usage of StringBuilder +* "#2022":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2022 Add missing @Override annotations +* "#2020":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2020 Avoid multiple creation of new arrays/maps +* "#2027":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2027 Cache method Lang.getLocale() +* "#2015":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2015 i18n tag only restore the first % character +* "#2034":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2034 add hibernate-ehcache jar to be able to use ehcache as second level cache +* "#2026":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2026 Blob: missing methods implemented to support (2nd level) caching +* "#1947":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/1948 Job::withinFilter Only Calls First PlayPlugin Filter It Finds. (Invoker Calls All Filters) +* "#1948":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/1948 Invoker.Invocation::run executes action once per plugin +* "#2035":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2035 MakeWS.getString() returning same result on every call +* "#2019":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2019 Make method Cache.clear() null-safe +* "#2018":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2018 Class caches added +* "#2036":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2036 Force secure reverse routes config diff --git a/documentation/manual/releases/release1.4.x/releasenotes-1.4.0.textile b/documentation/manual/releases/release1.4.x/releasenotes-1.4.0.textile new file mode 100644 index 0000000000..a4b0e83381 --- /dev/null +++ b/documentation/manual/releases/release1.4.x/releasenotes-1.4.0.textile @@ -0,0 +1,28 @@ +h1. Play 1.4.0 -- Release notes + +Play 1.4.0 has been released of the 1.4.x maintenance branch. + +The changes in this release are listed in the "Play 1.4.0 milestone":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/215150-140 on Lighthouse, including 31 resolved tickets. + +h2. What's new in Play 1.4.0 + +* Compatible Java 7. No longer support for Java 6 +* Upgrade to async-http-client v1.9.31 +* Upgrade to netty 3.10.4 +* Update HtmlUnit to v2.16 +* Add ability to define the timeout in testRunner module +* Add ability to manually set the VirtualHost of WS +* Improve performance of Router.reverse() +* Allow upload a 0B file +* add '--server' arg to install command commands python to specify just ONE custom repository for module installation + +h2. What's fixed in Play 1.4.0 + +* Fix redirect to wrong domain and port behind apache +* Customize JSON rendering by passing the Gson serializer object json render +* Getting Static Initialization Deadlock in class DataParser dataparser +* Allow zero-length blobs binder +* OpenID discovery fails in 1.3.x in some cases openid +* Problem to run specific tests selenium testrunner tests +* 500.html template redering issue +* play.libs.image.crop don't close the OutputStream image diff --git a/documentation/manual/releases/release1.4.x/releasenotes-1.4.1.textile b/documentation/manual/releases/release1.4.x/releasenotes-1.4.1.textile new file mode 100644 index 0000000000..8ca3de6c1c --- /dev/null +++ b/documentation/manual/releases/release1.4.x/releasenotes-1.4.1.textile @@ -0,0 +1,19 @@ +h1. Play 1.4.1 -- Release notes + +Play 1.4.1 has been released of the 1.4.x maintenance branch. + +The changes in this release are listed in the "Play 1.4.1 milestone":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/216577-141 on Lighthouse, including 31 resolved tickets. + +h2. What's new in Play 1.4.1 + +* Add PATCH support +* Update to htmlUnit v 2.19 +* Add ability to define enabled ssl protocols +* Make DB properties configurable + +h2. What's fixed in Play 1.4.1 + +* Fix vulnerabilty : Reset current request to avoid 3rd-party to acquire session information for another in-progress request +* Fix putting property to customer DB configuration +* Add method Plugin.onActionInvocationFinally() +* Fix javadoc tools errors diff --git a/documentation/manual/releases/release1.4.x/releasenotes-1.4.2.textile b/documentation/manual/releases/release1.4.x/releasenotes-1.4.2.textile new file mode 100644 index 0000000000..4ba30bfd43 --- /dev/null +++ b/documentation/manual/releases/release1.4.x/releasenotes-1.4.2.textile @@ -0,0 +1,38 @@ +h1. Play 1.4.2 -- Release notes + +Play 1.4.2 has been released of the 1.4.x maintenance branch. + +The changes in this release are listed in the "Play 1.4.2 milestone":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/217739-142 on Lighthouse, including 27 resolved tickets. + +h2. What's new in Play 1.4.2 + +* "#2029":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2029) Upgrade to groovy 2.4.6 +* "#2027":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2027) Cache method Lang.getLocale() + +h2. What's fixed in Play 1.4.2 + +* Fix vulnerabilty : Escape flash message in the secure module (login page) +* "#2008":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2008-error-while-binding-an-enumset) Error while binding an EnumSet +* "#2011":https://play.lighthouseapp.com/projects/57987/tickets/2011-support-double-semicolon-escaping-in-evolution-scripts) Support "double semicolon" escaping in evolution scripts +* "#2007":https://play.lighthouseapp.com/projects/57987/tickets/2007) JPA classloader issues with precompiled app +* "#2014":https://play.lighthouseapp.com/projects/57987/tickets/2014) Message not appear corretly in secure module +* "#1939":https://play.lighthouseapp.com/projects/57987/tickets/1939-update-to-groovy-24x) chore(lib): Update to groovy from 2.3.9 to 2.4.5 +* "#1934":https://play.lighthouseapp.com/projects/57987/tickets/1934) feat(controller): Support for non-static controller methods - they are easier to mock/unit-test +* "#2009":https://play.lighthouseapp.com/projects/57987/tickets/2009) Field name detection in 'attachment' +* "#2017":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2017) Use StringBuilder instead of StringBuffer +* "#2016":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2016) default value of "XForwardedOverwriteDomainAndPort" setting should be false +* "#1979":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/1979) Improve performance of Play! +* "#2021":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2021) Optimize usage of StringBuilder +* "#2022":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2022) Add missing @Override annotations +* "#2020":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2020) Avoid multiple creation of new arrays/maps +* "#2015":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2015) i18n tag only restore the first % character +* "#2034":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2034) add hibernate-ehcache jar to be able to use ehcache as second level cache +* "#2026":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2026) Blob: missing methods implemented to support (2nd level) caching +* "#1947":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/1948) Job::withinFilter Only Calls First PlayPlugin Filter It Finds. (Invoker Calls All Filters) +* "#1948":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/1948) Invoker.Invocation::run executes action once per plugin +* "#2035":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2035) MakeWS.getString() returning same result on every call +* "#2019":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2019) Make method Cache.clear() null-safe +* "#2018":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2018) Class caches added +* "#2036":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2036) Force secure reverse routes config +* "#2039":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2039) Update some libraires + diff --git a/documentation/manual/releases/release1.4.x/releasenotes-1.4.3.textile b/documentation/manual/releases/release1.4.x/releasenotes-1.4.3.textile new file mode 100644 index 0000000000..ba0fc66dc1 --- /dev/null +++ b/documentation/manual/releases/release1.4.x/releasenotes-1.4.3.textile @@ -0,0 +1,34 @@ +h1. Play 1.4.3 -- Release notes + +Play 1.4.3 has been released of the 1.4.x maintenance branch. + +The changes in this release are listed in the "Play 1.4.3 milestone":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/219451-143 on Lighthouse and "1.4.3 on github":https://github.com/playframework/play1/milestone/2?closed=1, +including 27 resolved tickets. + +h2. What's new in Play 1.4.3 + +* "#2055":https://play.lighthouseapp.com/projects/57987/tickets/2055 Upgrade dependency joda-time (from 2.9.2 to 2.9.4) +* "#2056":https://play.lighthouseapp.com/projects/57987/tickets/2056 Upgrade Groovy to 2.4.7 +* "#986":https://github.com/playframework/play1/issues/986 Update selenium to 2.53 + +h2. What's fixed in Play 1.4.3 + +* "#2040":https://play.lighthouseapp.com/projects/57987/tickets/2040-concurrentmodificationexception-in-groovytemplatecompilerendtag ConcurrentModificationException in GroovyTemplateCompiler.endTag +* "#2042":https://play.lighthouseapp.com/projects/57987/tickets/2042 Upgrade dependencies +* "#2044":https://play.lighthouseapp.com/projects/57987/tickets/2044 support empty values in validation error arguments +* "#2049":https://play.lighthouseapp.com/projects/57987/tickets/2049 Some resultsets are not close in Evolutions causing some leak +* "#2050":https://play.lighthouseapp.com/projects/57987/tickets/2050 Update dep for netty and async-http +* "#2051, #1667":https://play.lighthouseapp.com/projects/57987/tickets/2051 Avoid to call @Util and @Catch controller methods directly using a http request +* "#2057":https://play.lighthouseapp.com/projects/57987/tickets/2057 Remove dependency on commons-collections +* "#2052":https://play.lighthouseapp.com/projects/57987/tickets/2052 session.put() works incorrectly with null objects +* "#2058":https://play.lighthouseapp.com/projects/57987/tickets/2058 KeyError: 'disable_random_jpda' in application.check_jpda +* "#984":https://github.com/playframework/play1/issues/984 Play framework should log every time when application restart happens (in dev mode) +* "#982":https://github.com/playframework/play1/issues/982 `play build-module` command should exit with error code if build failed +* fix UnexpectedException.getErrorDescription(): write error message in any case +* "#2059":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2059 Rework place of release note of documentation +* Upgrade Apache Ant 1.9.6 -> 1.9.7 +* "#987":https://github.com/playframework/play1/pull/987 Don't swallow exceptions in binder +* "#2048":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2048 upgrade to cglib 3.2.2 +* "#2053":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2053 flash method should handle value as a key message + + diff --git a/documentation/manual/releases/release1.4.x/releasenotes-1.4.4.textile b/documentation/manual/releases/release1.4.x/releasenotes-1.4.4.textile new file mode 100644 index 0000000000..b11a890eb0 --- /dev/null +++ b/documentation/manual/releases/release1.4.x/releasenotes-1.4.4.textile @@ -0,0 +1,51 @@ +h1. Play 1.4.4 -- Release notes + +Play 1.4.4 has been released of the 1.4.x maintenance branch. + +The changes in this release are listed in the "Play 1.4.4 milestone":https://play.lighthouseapp.com/projects/57987/milestones/222767-144 on Lighthouse and "1.4.4 on github":https://github.com/playframework/play1/milestone/3?closed=1 +including 72 resolved tickets. + +h2. What's new in Play 1.4.4 + +* "#1007":https://github.com/playframework/play1/pull/1007 Calculate template rendering time, so it can be logged/profiled +* "#983":https://github.com/playframework/play1/issues/983 feat(DependenciesManager): sync option should be ON by default. +* "#2072":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2072 add method Binder.unregister() +* "#2071":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2071 Precompile additional templates +* "#1007":https://github.com/playframework/play1/issues/1007 Calculate template rendering time, so it can be logged/profiled + +h2. What's fixed in Play 1.4.4 + +* "#1005":https://github.com/playframework/play1/issues/1005 feat(fixture): add id/type in log when loading a fixture file throw exception +* "#1009":https://github.com/playframework/play1/pull/1009 fix(templates): not log error and exception when compiling a template with a non GroovyInlineTag +* "#1014":https://github.com/playframework/play1/pull/1014 chore(travis): fix buffer overflow exception +* fix h2.jar binary +* "#995":https://github.com/playframework/play1/pull/995 use hamcrest-all 1.3 instead of hamcrest-core 1.3 +* "#1011":https://github.com/playframework/play1/pull/1011 upgrade to cglib 3.2.4 +* "#1008":https://github.com/playframework/play1/pull/1008 fix(ConfigurationChangeWatcherPlugin): initialized configLastModified on application start +* "#2066":https://play.lighthouseapp.com/projects/57987/tickets/2066 fix open file on 500 error pages for modules +* "#998":https://github.com/playframework/play1/pull/998 fix(router): allow to have space when defining static args in routes +* "#1025":https://github.com/playframework/play1/pull/1025 fix(DB): Detect changed for JNDI datasource which using jndi: prefix +* "#2068":https://play.lighthouseapp.com/projects/57987/tickets/2068 chore(lib): dependency upgrade: joda-time (2.9.4 -> 2.9.5) +* "#1024":https://github.com/playframework/play1/pull/1024 Default application.path to working directory +* "#1090":https://github.com/playframework/play1/issues/1090 Fix performance issue in classloader +* "#2074":https://github.com/playframework/play1/issues/2074 Dependency upgrade: joda-time (2.9.6 -> 2.9.7) +* "#1067":https://github.com/playframework/play1/issues/1067 test(tag): add demo and test for used of #{set} and #{get} +* "#1090":https://github.com/playframework/play1/pull/1072 add method RenderJson.getResponse() +* "#1070":https://github.com/playframework/play1/pull/1070 Avoid reflection and make the default list of enhancers overridable +* "#1040":https://github.com/playframework/play1/pull/1040 Avoid reflection and make the default list of enhancers overridable +* "#1064":https://github.com/playframework/play1/issues/1064 [#1064] fix(messages): add some test on parameters to avoid NullpointerException +* "#1064":https://github.com/playframework/play1/issues/1064 Initialize play static field early +* "#1061":https://github.com/playframework/play1/issues/1061 Methods marked with `@Catch(Throwable.class)` catch all `Results` +* "#1058 ":https://github.com/playframework/play1/issues/1058 Improve performance of RenderJson +* "#1056":https://github.com/playframework/play1/issues/1056 Add getters to all `Result` subclasses +* "#1051":https://github.com/playframework/play1/issues/1051 Play dependencies silently resolve not found dependencies +* "#1033":https://github.com/playframework/play1/pull/1033 add possibility to disable generating getters/setters +* "#1025":https://github.com/playframework/play1/pull/1025 Detect changed for JNDI datasource which using jndi: prefix and its documentation +* "#2066":https://github.com/playframework/play1/pull/2066 Avoid NPE in regex matcher +* "#1919":https://github.com/playframework/play1/issues/1919 fixes Building modules with a space in path under windows fails +* "#2066":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2066 fix(router): allow to have space when defining static args in routes +* "#998":https://github.com/playframework/play1/issues/998 fix open file on 500 error pages for modules +* "#2066":https://play.lighthouseapp.com/projects/57987-play-framework/tickets/2066 fix open file on 500 error pages for modules +* "#1005":https://github.com/playframework/play1/issues/1005 add id/type in log when loading a fixture file throw exception +* "#998":https://github.com/playframework/play1/issues/998 Routes 'Assign static args' only work for first argument +* "#985":https://github.com/playframework/play1/issues/985 Play should NOT clear cache after every code change \ No newline at end of file diff --git a/documentation/manual/releases/releases.textile b/documentation/manual/releases/releases.textile new file mode 100644 index 0000000000..1fa6d06806 --- /dev/null +++ b/documentation/manual/releases/releases.textile @@ -0,0 +1,38 @@ +h1. About Play releases + +You can download Play releases "here":https://www.playframework.com/download. Each release has a Migration Guide that explains how to upgrade from the previous release. + + +h2. Play 1.4.x + +# "Play 1.4.4":release1.4.x/releasenotes-1.4.4 +# "Play 1.4.3":release1.4.x/releasenotes-1.4.3 +# "Play 1.4.2":release1.4.x/releasenotes-1.4.2 +# "Play 1.4.1":release1.4.x/releasenotes-1.4.1 +# "Play 1.4.0":release1.4.x/releasenotes-1.4.0 + +h2. Play 1.3.x + +# "Play 1.3.4":release1.3.x/releasenotes-1.3.4 +# "Play 1.3.3":release1.3.x/releasenotes-1.3.3 +# "Play 1.3.2":release1.3.x/releasenotes-1.3.2 +# "Play 1.3.1":release1.3.x/releasenotes-1.3.1 +# "Play 1.3.0":release1.3.x/releasenotes-1.3.0 + +h2. Play 1.2.x + +# "Play 1.2.7":release1.2.x/releasenotes-1.2.7 +# "Play 1.2.6":release1.2.x/releasenotes-1.2.6 +# "Play 1.2.5":release1.2.x/releasenotes-1.2.5 +# "Play 1.2.4":release1.2.x/releasenotes-1.2.4 +# "Play 1.2":release1.2.x/releasenotes-1.2 + +h2. Play 1.1.x + +# "Play 1.1":release1.1.x/releasenotes-1.1 + +h2. Play 1.0.x + +# "Play 1.0.3":release1.0.x/releasenotes-1.0.3 +# "Play 1.0.2":release1.0.x/releasenotes-1.0.2 +# "Play 1.0.1":release1.0.x/releasenotes-1.0.1 \ No newline at end of file diff --git a/documentation/manual/templates.textile b/documentation/manual/templates.textile index fdc3929436..90e5270227 100644 --- a/documentation/manual/templates.textile +++ b/documentation/manual/templates.textile @@ -80,7 +80,7 @@ All dynamic expressions are escaped by the template engine to avoid XSS security bc. ${title} --> <h1>Title</h1> -If you really want to display it in an unescaped way, you need to explicitely call the @raw()@ method: +If you really want to display it in an unescaped way, you need to explicitly call the @raw()@ method: bc. ${title.raw()} -->

Title

diff --git a/documentation/manual_ja/asynchronous.textile b/documentation/manual_ja/asynchronous.textile index 0b3fc4cf5c..c946ae9308 100644 --- a/documentation/manual_ja/asynchronous.textile +++ b/documentation/manual_ja/asynchronous.textile @@ -114,7 +114,7 @@ h2. WebSockets の使用 WebSockets は、ブラウザとアプリケーション間の双方向コミュニケーションチャンネルを開く、ひとつの方法です。ブラウザ側で "ws://" という url を使ってソケットを開きます。 -bc. new Socket("ws://localhost:9000/helloSocket?name=Guillaume") +bc. new WebSocket("ws://localhost:9000/helloSocket?name=Guillaume") Play 側では WS ルートを定義します: diff --git a/documentation/manual_ja/cache.textile b/documentation/manual_ja/cache.textile index c93a90e6f1..7376471c15 100644 --- a/documentation/manual_ja/cache.textile +++ b/documentation/manual_ja/cache.textile @@ -1,6 +1,6 @@ h1. キャッシュの使用 -パフォーマンスの高いシステムを作成するため、データのキャッシュが必要になる場合があります。Play にはキャッシュライブラリがあり、分散環境下では "Memcahed":http://www.danga.com/memcached/ を使用します。 +パフォーマンスの高いシステムを作成するため、データのキャッシュが必要になる場合があります。Play にはキャッシュライブラリがあり、分散環境下では "Memcached":http://www.danga.com/memcached/ を使用します。 Memcached を設定しない場合、Play は JVM ヒープにデータを保存するスタンドアロンキャッシュを使用します。JVM へのデータのキャッシュは、Play の ‘何も共有しない’ という前提を覆します: 複数のサーバで実行したアプリケーションの振る舞いが一貫していると期待することはできません。それぞれのアプリケーションインスタンスは異なるデータのコピーを持ちます。 diff --git a/documentation/manual_ja/configuration.textile b/documentation/manual_ja/configuration.textile index 6670367310..bbceb26181 100644 --- a/documentation/manual_ja/configuration.textile +++ b/documentation/manual_ja/configuration.textile @@ -517,11 +517,11 @@ h3(#java.source). java.source Java ソースレベルです。これは @java.version@ システムプロパティをオーバーライドします。設定例: -bc. java.source=1.6 +bc. java.source=1.8 -Values: @1.5@, @1.6@, @1.7@ (experimental). +Values: @1.7@ (No longer supported since 1.5.0), @1.8@. -デフォルト: @1.5@ +デフォルト: @1.8@ h2(#jpa). JPA diff --git a/documentation/manual_ja/guide7.textile b/documentation/manual_ja/guide7.textile index bb54a92ccb..10298d3a1a 100755 --- a/documentation/manual_ja/guide7.textile +++ b/documentation/manual_ja/guide7.textile @@ -347,7 +347,7 @@ bc. #{crud.form} #{/crud.custom} #{/crud.form} -これはちょっと hacky であり、もっとうまくやることもできますが、ちょっとした JavaScript を使うことでタグセレクタはよりシンプルになりました: +これはちょっと hack であり、もっとうまくやることもできますが、ちょっとした JavaScript を使うことでタグセレクタはよりシンプルになりました: !images/guide7-6! diff --git a/framework/build.xml b/framework/build.xml index a6b89f7337..0a5750d240 100644 --- a/framework/build.xml +++ b/framework/build.xml @@ -2,7 +2,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -94,12 +94,13 @@ Play! ${version}]]> Guillaume Bort & zenexity - Distributed under Apache 2 licence, without any warrantly]]> - + + - + - + @@ -233,7 +234,7 @@ - + @@ -290,6 +291,10 @@ + + + + @@ -370,7 +375,7 @@ - + @@ -380,6 +385,9 @@ + + + diff --git a/framework/dependencies.yml b/framework/dependencies.yml index 71e2329fb4..39227487a5 100644 --- a/framework/dependencies.yml +++ b/framework/dependencies.yml @@ -8,61 +8,63 @@ transitiveDependencies: false # This core dependencies are required by Play framework require: &allDependencies - antlr 2.7.7 - - c3p0 0.9.5 - - cglib -> cglib-nodep 3.1 - - com.google.code.gson -> gson 2.3.1 + - com.mchange -> c3p0 0.9.5.2 + - org.ow2.asm -> asm-all 5.2 + - cglib -> cglib 3.2.4 + - com.google.code.gson -> gson 2.8.0 - com.jamonapi -> jamon 2.81 - - com.ning -> async-http-client 1.8.16 - - commons-beanutils 1.8.3 + - com.ning -> async-http-client 1.9.40 + - commons-beanutils 1.9.2 - commons-codec 1.10 - - commons-collections 3.2.1 - - commons-email 1.3.3 - - commons-fileupload 1.3.1 - - commons-io 2.4 - - commons-javaflow 1590792 + - org.apache.commons -> commons-email 1.4 + - commons-fileupload 1.3.2 + - commons-io 2.5 + - com.google.code.maven-play-plugin.org.apache.commons -> commons-javaflow 1590792 - commons-lang 2.6 - commons-logging 1.2 - dom4j 1.6.1 - - com.h2database -> h2 1.4.185 + - com.h2database -> h2 1.4.195 - javax.activation -> activation 1.1.1 - javax.mail -> mail 1.4.7 - javax.inject 1.0 - - javax.validation -> validation-api 1.0.0.GA + - javax.validation -> validation-api 1.1.0.Final - jaxen 1.1.6 - - joda-time 2.8.2 - - org.hamcrest -> hamcrest-core 1.3 + - joda-time 2.9.9 + - org.hamcrest -> hamcrest-all 1.3 - junit 4.12 - jregex 1.2_01 - log4j 1.2.17 - net.sf.ehcache -> ehcache-core 2.6.11 - net.sf.ezmorph -> ezmorph 1.0.6 - - net.sf.jsr107cache -> jsr107cache 1.0 - - net.sf.oval -> oval 1.84 - - mysql -> mysql-connector-java 5.1.35 + - net.sf.jsr107cache -> jsr107cache 1.1 + - net.sf.oval -> oval 1.86 + - mysql -> mysql-connector-java 5.1.41 - oauth.signpost -> signpost-core 1.2.1.2 - org.apache.geronimo.specs -> geronimo-servlet_2.5_spec 1.2 - org.apache.ivy -> ivy 2.4.0 - - org.bouncycastle -> bcprov-jdk15 1.45 - - org.codehaus.groovy -> groovy-all 2.3.9 - - org.eclipse.jdt.core 3.10.0.v20140604-1726 - - org.hibernate -> hibernate-core 4.2.19.Final - - org.hibernate -> hibernate-commons-annotations 4.0.2.Final - - org.hibernate -> hibernate-entitymanager 4.2.19.Final - - org.hibernate -> hibernate-validator 4.1.0.Final - - org.hibernate -> jboss-logging 3.1.0.GA - - org.hibernate -> jboss-transaction-api_1.1_spec 1.0.1.Final - - org.hibernate.javax.persistence -> hibernate-jpa-2.0-api 1.0.1.Final - - org.hibernate -> hibernate-c3p0 4.2.19.Final - - com.mchange -> mchange-commons-java 0.2.9 - - org.javassist -> javassist 3.19.0-GA - - org.jboss.netty -> netty 3.9.8.Final + - org.bouncycastle -> bcprov-jdk15 1.46 + - org.codehaus.groovy -> groovy-all 2.4.11 + - org.eclipse.jdt.core 3.12.3 + - org.hibernate -> hibernate-core 5.2.10.patched + - org.hibernate.common -> hibernate-commons-annotations 5.0.1.Final + - org.hibernate -> hibernate-entitymanager 5.2.10.Final + - org.hibernate -> hibernate-validator 5.4.1.Final + - org.jboss.logging -> jboss-logging 3.3.0.Final + - org.jboss.spec.javax.transaction -> jboss-transaction-api_1.2_spec 1.0.1.Final + - org.hibernate.javax.persistence -> hibernate-jpa-2.1-api 1.0.0.Final + - com.fasterxml -> classmate 1.3.3 + - org.hibernate -> hibernate-c3p0 5.2.10.Final + - org.hibernate -> hibernate-ehcache 5.2.10.Final + - com.mchange -> mchange-commons-java 0.2.12 + - org.javassist -> javassist 3.21.0-GA + - io.netty -> netty 3.10.6.Final - org.postgresql -> postgresql 9.0 - - org.slf4j -> slf4j-api 1.7.10 - - org.slf4j -> slf4j-log4j12 1.7.10 - - org.yaml -> snakeyaml 1.15 - - spy -> spymemcached 2.11.7 - - com.thoughtworks.xstream -> xstream 1.4.7 - - xmlpull 1.1.3.1 + - org.slf4j -> slf4j-api 1.7.22 + - org.slf4j -> slf4j-log4j12 1.7.22 + - org.yaml -> snakeyaml 1.17 + - net.spy -> spymemcached 2.12.1 + - com.thoughtworks.xstream -> xstream 1.4.9 + - xmlpull 1.1.3.4d_b4_min # Default repositories, used for all projects repositories: diff --git a/framework/lib-test/ant.jar b/framework/lib-test/ant.jar index 24641e74aa..1c348e3eb5 100644 Binary files a/framework/lib-test/ant.jar and b/framework/lib-test/ant.jar differ diff --git a/framework/lib/asm-all-5.0.3.jar b/framework/lib/asm-all-5.0.3.jar deleted file mode 100644 index da5b23e156..0000000000 Binary files a/framework/lib/asm-all-5.0.3.jar and /dev/null differ diff --git a/framework/lib/asm-all-5.2.jar b/framework/lib/asm-all-5.2.jar new file mode 100644 index 0000000000..0b629c2289 Binary files /dev/null and b/framework/lib/asm-all-5.2.jar differ diff --git a/framework/lib/async-http-client-1.8.16.jar b/framework/lib/async-http-client-1.8.16.jar deleted file mode 100644 index 108122c982..0000000000 Binary files a/framework/lib/async-http-client-1.8.16.jar and /dev/null differ diff --git a/framework/lib/async-http-client-1.9.40.jar b/framework/lib/async-http-client-1.9.40.jar new file mode 100644 index 0000000000..467d41702a Binary files /dev/null and b/framework/lib/async-http-client-1.9.40.jar differ diff --git a/framework/lib/bcprov-jdk15-1.45.jar b/framework/lib/bcprov-jdk15-1.45.jar deleted file mode 100644 index 409070b037..0000000000 Binary files a/framework/lib/bcprov-jdk15-1.45.jar and /dev/null differ diff --git a/framework/lib/bcprov-jdk15-1.46.jar b/framework/lib/bcprov-jdk15-1.46.jar new file mode 100644 index 0000000000..daa0b54cc0 Binary files /dev/null and b/framework/lib/bcprov-jdk15-1.46.jar differ diff --git a/framework/lib/c3p0-0.9.5.2.jar b/framework/lib/c3p0-0.9.5.2.jar new file mode 100644 index 0000000000..579cedd980 Binary files /dev/null and b/framework/lib/c3p0-0.9.5.2.jar differ diff --git a/framework/lib/c3p0-0.9.5.jar b/framework/lib/c3p0-0.9.5.jar deleted file mode 100644 index 46fdddf7e7..0000000000 Binary files a/framework/lib/c3p0-0.9.5.jar and /dev/null differ diff --git a/framework/lib/cglib-3.2.4.jar b/framework/lib/cglib-3.2.4.jar new file mode 100644 index 0000000000..8ef8ae60ce Binary files /dev/null and b/framework/lib/cglib-3.2.4.jar differ diff --git a/framework/lib/cglib-nodep-3.1.jar b/framework/lib/cglib-nodep-3.1.jar deleted file mode 100644 index c0ac121455..0000000000 Binary files a/framework/lib/cglib-nodep-3.1.jar and /dev/null differ diff --git a/framework/lib/classmate-1.3.3.jar b/framework/lib/classmate-1.3.3.jar new file mode 100644 index 0000000000..d44873ea54 Binary files /dev/null and b/framework/lib/classmate-1.3.3.jar differ diff --git a/framework/lib/commons-beanutils-1.8.3.jar b/framework/lib/commons-beanutils-1.8.3.jar deleted file mode 100644 index 218510bc5d..0000000000 Binary files a/framework/lib/commons-beanutils-1.8.3.jar and /dev/null differ diff --git a/framework/lib/commons-beanutils-1.9.2.jar b/framework/lib/commons-beanutils-1.9.2.jar new file mode 100644 index 0000000000..7d075edf42 Binary files /dev/null and b/framework/lib/commons-beanutils-1.9.2.jar differ diff --git a/framework/lib/commons-collections-3.2.1.jar b/framework/lib/commons-collections-3.2.1.jar deleted file mode 100644 index c35fa1fee1..0000000000 Binary files a/framework/lib/commons-collections-3.2.1.jar and /dev/null differ diff --git a/framework/lib/commons-collections-3.2.2.jar b/framework/lib/commons-collections-3.2.2.jar new file mode 100644 index 0000000000..fa5df82a63 Binary files /dev/null and b/framework/lib/commons-collections-3.2.2.jar differ diff --git a/framework/lib/commons-email-1.3.3.jar b/framework/lib/commons-email-1.3.3.jar deleted file mode 100644 index 0061ed86d3..0000000000 Binary files a/framework/lib/commons-email-1.3.3.jar and /dev/null differ diff --git a/framework/lib/commons-email-1.4.jar b/framework/lib/commons-email-1.4.jar new file mode 100644 index 0000000000..99fd402837 Binary files /dev/null and b/framework/lib/commons-email-1.4.jar differ diff --git a/framework/lib/commons-fileupload-1.3.1.jar b/framework/lib/commons-fileupload-1.3.1.jar deleted file mode 100644 index af0cda226f..0000000000 Binary files a/framework/lib/commons-fileupload-1.3.1.jar and /dev/null differ diff --git a/framework/lib/commons-fileupload-1.3.2.jar b/framework/lib/commons-fileupload-1.3.2.jar new file mode 100644 index 0000000000..4975590e34 Binary files /dev/null and b/framework/lib/commons-fileupload-1.3.2.jar differ diff --git a/framework/lib/commons-io-2.4.jar b/framework/lib/commons-io-2.4.jar deleted file mode 100644 index 90035a4fe0..0000000000 Binary files a/framework/lib/commons-io-2.4.jar and /dev/null differ diff --git a/framework/lib/commons-io-2.5.jar b/framework/lib/commons-io-2.5.jar new file mode 100644 index 0000000000..107b061f5f Binary files /dev/null and b/framework/lib/commons-io-2.5.jar differ diff --git a/framework/lib/groovy-all-2.3.9.jar b/framework/lib/groovy-all-2.4.11.jar similarity index 51% rename from framework/lib/groovy-all-2.3.9.jar rename to framework/lib/groovy-all-2.4.11.jar index b6b186ade1..ff2e4245f3 100644 Binary files a/framework/lib/groovy-all-2.3.9.jar and b/framework/lib/groovy-all-2.4.11.jar differ diff --git a/framework/lib/gson-2.3.1.jar b/framework/lib/gson-2.3.1.jar deleted file mode 100644 index 250132c197..0000000000 Binary files a/framework/lib/gson-2.3.1.jar and /dev/null differ diff --git a/framework/lib/gson-2.8.0.jar b/framework/lib/gson-2.8.0.jar new file mode 100644 index 0000000000..1235f63816 Binary files /dev/null and b/framework/lib/gson-2.8.0.jar differ diff --git a/framework/lib/h2-1.4.185.jar b/framework/lib/h2-1.4.185.jar deleted file mode 100644 index a7e4dddec9..0000000000 Binary files a/framework/lib/h2-1.4.185.jar and /dev/null differ diff --git a/framework/lib/h2-1.4.195.jar b/framework/lib/h2-1.4.195.jar new file mode 100644 index 0000000000..c568b4f22d Binary files /dev/null and b/framework/lib/h2-1.4.195.jar differ diff --git a/framework/lib/hamcrest-all-1.3.jar b/framework/lib/hamcrest-all-1.3.jar new file mode 100644 index 0000000000..6f62ba00c7 Binary files /dev/null and b/framework/lib/hamcrest-all-1.3.jar differ diff --git a/framework/lib/hamcrest-core-1.3.jar b/framework/lib/hamcrest-core-1.3.jar deleted file mode 100644 index 9d5fe16e3d..0000000000 Binary files a/framework/lib/hamcrest-core-1.3.jar and /dev/null differ diff --git a/framework/lib/hibernate-c3p0-4.2.19.Final.jar b/framework/lib/hibernate-c3p0-4.2.19.Final.jar deleted file mode 100644 index 984d327a6e..0000000000 Binary files a/framework/lib/hibernate-c3p0-4.2.19.Final.jar and /dev/null differ diff --git a/framework/lib/hibernate-c3p0-5.2.10.Final.jar b/framework/lib/hibernate-c3p0-5.2.10.Final.jar new file mode 100644 index 0000000000..9416da0e1a Binary files /dev/null and b/framework/lib/hibernate-c3p0-5.2.10.Final.jar differ diff --git a/framework/lib/hibernate-commons-annotations-4.0.2.Final.jar b/framework/lib/hibernate-commons-annotations-4.0.2.Final.jar deleted file mode 100644 index c26aba4ed5..0000000000 Binary files a/framework/lib/hibernate-commons-annotations-4.0.2.Final.jar and /dev/null differ diff --git a/framework/lib/hibernate-commons-annotations-5.0.1.Final.jar b/framework/lib/hibernate-commons-annotations-5.0.1.Final.jar new file mode 100644 index 0000000000..82e425dc2e Binary files /dev/null and b/framework/lib/hibernate-commons-annotations-5.0.1.Final.jar differ diff --git a/framework/lib/hibernate-core-4.2.19.Final.jar b/framework/lib/hibernate-core-4.2.19.Final.jar deleted file mode 100644 index 54b3fea2d7..0000000000 Binary files a/framework/lib/hibernate-core-4.2.19.Final.jar and /dev/null differ diff --git a/framework/lib/hibernate-core-5.2.10.patched.jar b/framework/lib/hibernate-core-5.2.10.patched.jar new file mode 100644 index 0000000000..65249ba583 Binary files /dev/null and b/framework/lib/hibernate-core-5.2.10.patched.jar differ diff --git a/framework/lib/hibernate-ehcache-5.2.10.Final.jar b/framework/lib/hibernate-ehcache-5.2.10.Final.jar new file mode 100644 index 0000000000..4be83a7700 Binary files /dev/null and b/framework/lib/hibernate-ehcache-5.2.10.Final.jar differ diff --git a/framework/lib/hibernate-entitymanager-4.2.19.Final.jar b/framework/lib/hibernate-entitymanager-4.2.19.Final.jar deleted file mode 100644 index 5700709aed..0000000000 Binary files a/framework/lib/hibernate-entitymanager-4.2.19.Final.jar and /dev/null differ diff --git a/framework/lib/hibernate-entitymanager-5.2.10.Final.jar b/framework/lib/hibernate-entitymanager-5.2.10.Final.jar new file mode 100644 index 0000000000..0e328fd5f5 Binary files /dev/null and b/framework/lib/hibernate-entitymanager-5.2.10.Final.jar differ diff --git a/framework/lib/hibernate-jpa-2.0-api-1.0.1.Final.jar b/framework/lib/hibernate-jpa-2.0-api-1.0.1.Final.jar deleted file mode 100644 index 1e9f71b8c1..0000000000 Binary files a/framework/lib/hibernate-jpa-2.0-api-1.0.1.Final.jar and /dev/null differ diff --git a/framework/lib/hibernate-jpa-2.1-api-1.0.0.Final.jar b/framework/lib/hibernate-jpa-2.1-api-1.0.0.Final.jar new file mode 100644 index 0000000000..e2f2c59287 Binary files /dev/null and b/framework/lib/hibernate-jpa-2.1-api-1.0.0.Final.jar differ diff --git a/framework/lib/hibernate-validator-4.1.0.Final.jar b/framework/lib/hibernate-validator-4.1.0.Final.jar deleted file mode 100644 index abf87e4b41..0000000000 Binary files a/framework/lib/hibernate-validator-4.1.0.Final.jar and /dev/null differ diff --git a/framework/lib/hibernate-validator-5.4.1.Final.jar b/framework/lib/hibernate-validator-5.4.1.Final.jar new file mode 100644 index 0000000000..1975846ff4 Binary files /dev/null and b/framework/lib/hibernate-validator-5.4.1.Final.jar differ diff --git a/framework/lib/javassist-3.19.0-GA.jar b/framework/lib/javassist-3.19.0-GA.jar deleted file mode 100644 index 2a640c4a48..0000000000 Binary files a/framework/lib/javassist-3.19.0-GA.jar and /dev/null differ diff --git a/framework/lib/javassist-3.21.0-GA.jar b/framework/lib/javassist-3.21.0-GA.jar new file mode 100644 index 0000000000..64549c4ada Binary files /dev/null and b/framework/lib/javassist-3.21.0-GA.jar differ diff --git a/framework/lib/jboss-logging-3.1.0.GA.jar b/framework/lib/jboss-logging-3.1.0.GA.jar deleted file mode 100644 index 72113b0f84..0000000000 Binary files a/framework/lib/jboss-logging-3.1.0.GA.jar and /dev/null differ diff --git a/framework/lib/jboss-logging-3.3.0.Final.jar b/framework/lib/jboss-logging-3.3.0.Final.jar new file mode 100644 index 0000000000..ea45d4d341 Binary files /dev/null and b/framework/lib/jboss-logging-3.3.0.Final.jar differ diff --git a/framework/lib/jboss-transaction-api_1.1_spec-1.0.1.Final.jar b/framework/lib/jboss-transaction-api_1.1_spec-1.0.1.Final.jar deleted file mode 100644 index 981f8f9910..0000000000 Binary files a/framework/lib/jboss-transaction-api_1.1_spec-1.0.1.Final.jar and /dev/null differ diff --git a/framework/lib/jboss-transaction-api_1.2_spec-1.0.1.Final.jar b/framework/lib/jboss-transaction-api_1.2_spec-1.0.1.Final.jar new file mode 100644 index 0000000000..2113cc0279 Binary files /dev/null and b/framework/lib/jboss-transaction-api_1.2_spec-1.0.1.Final.jar differ diff --git a/framework/lib/joda-time-2.8.2.jar b/framework/lib/joda-time-2.8.2.jar deleted file mode 100644 index 05960ff121..0000000000 Binary files a/framework/lib/joda-time-2.8.2.jar and /dev/null differ diff --git a/framework/lib/joda-time-2.9.9.jar b/framework/lib/joda-time-2.9.9.jar new file mode 100644 index 0000000000..b3080c4353 Binary files /dev/null and b/framework/lib/joda-time-2.9.9.jar differ diff --git a/framework/lib/jsr107cache-1.0.jar b/framework/lib/jsr107cache-1.0.jar deleted file mode 100644 index d522b2915e..0000000000 Binary files a/framework/lib/jsr107cache-1.0.jar and /dev/null differ diff --git a/framework/lib/jsr107cache-1.1.jar b/framework/lib/jsr107cache-1.1.jar new file mode 100644 index 0000000000..a94aa3184e Binary files /dev/null and b/framework/lib/jsr107cache-1.1.jar differ diff --git a/framework/lib/mchange-commons-java-0.2.12.jar b/framework/lib/mchange-commons-java-0.2.12.jar new file mode 100644 index 0000000000..af11054860 Binary files /dev/null and b/framework/lib/mchange-commons-java-0.2.12.jar differ diff --git a/framework/lib/mchange-commons-java-0.2.9.jar b/framework/lib/mchange-commons-java-0.2.9.jar deleted file mode 100644 index 07e4b66eb3..0000000000 Binary files a/framework/lib/mchange-commons-java-0.2.9.jar and /dev/null differ diff --git a/framework/lib/mysql-connector-java-5.1.35.jar b/framework/lib/mysql-connector-java-5.1.35.jar deleted file mode 100644 index 27fbbce77b..0000000000 Binary files a/framework/lib/mysql-connector-java-5.1.35.jar and /dev/null differ diff --git a/framework/lib/mysql-connector-java-5.1.41.jar b/framework/lib/mysql-connector-java-5.1.41.jar new file mode 100644 index 0000000000..cdf74884d8 Binary files /dev/null and b/framework/lib/mysql-connector-java-5.1.41.jar differ diff --git a/framework/lib/netty-3.9.8.Final.jar b/framework/lib/netty-3.10.6.Final.jar similarity index 71% rename from framework/lib/netty-3.9.8.Final.jar rename to framework/lib/netty-3.10.6.Final.jar index 2bd13d123c..b0a1bdad61 100644 Binary files a/framework/lib/netty-3.9.8.Final.jar and b/framework/lib/netty-3.10.6.Final.jar differ diff --git a/framework/lib/org.eclipse.jdt.core-3.10.0.v20140604-1726.jar b/framework/lib/org.eclipse.jdt.core-3.10.0.v20140604-1726.jar deleted file mode 100644 index 107e915fa7..0000000000 Binary files a/framework/lib/org.eclipse.jdt.core-3.10.0.v20140604-1726.jar and /dev/null differ diff --git a/framework/lib/org.eclipse.jdt.core-3.12.3.jar b/framework/lib/org.eclipse.jdt.core-3.12.3.jar new file mode 100644 index 0000000000..501a10289e Binary files /dev/null and b/framework/lib/org.eclipse.jdt.core-3.12.3.jar differ diff --git a/framework/lib/oval-1.84.jar b/framework/lib/oval-1.84.jar deleted file mode 100644 index ab39854fa4..0000000000 Binary files a/framework/lib/oval-1.84.jar and /dev/null differ diff --git a/framework/lib/oval-1.86.jar b/framework/lib/oval-1.86.jar new file mode 100644 index 0000000000..e28918e9d8 Binary files /dev/null and b/framework/lib/oval-1.86.jar differ diff --git a/framework/lib/slf4j-api-1.7.10.jar b/framework/lib/slf4j-api-1.7.10.jar deleted file mode 100644 index 744e9ec5b0..0000000000 Binary files a/framework/lib/slf4j-api-1.7.10.jar and /dev/null differ diff --git a/framework/lib/slf4j-api-1.7.22.jar b/framework/lib/slf4j-api-1.7.22.jar new file mode 100644 index 0000000000..3c3d953130 Binary files /dev/null and b/framework/lib/slf4j-api-1.7.22.jar differ diff --git a/framework/lib/slf4j-log4j12-1.7.10.jar b/framework/lib/slf4j-log4j12-1.7.10.jar deleted file mode 100644 index 957b2b158a..0000000000 Binary files a/framework/lib/slf4j-log4j12-1.7.10.jar and /dev/null differ diff --git a/framework/lib/slf4j-log4j12-1.7.22.jar b/framework/lib/slf4j-log4j12-1.7.22.jar new file mode 100644 index 0000000000..0a90fe6a83 Binary files /dev/null and b/framework/lib/slf4j-log4j12-1.7.22.jar differ diff --git a/framework/lib/snakeyaml-1.15.jar b/framework/lib/snakeyaml-1.15.jar deleted file mode 100644 index 34084e3325..0000000000 Binary files a/framework/lib/snakeyaml-1.15.jar and /dev/null differ diff --git a/framework/lib/snakeyaml-1.17.jar b/framework/lib/snakeyaml-1.17.jar new file mode 100644 index 0000000000..b0372a3cdd Binary files /dev/null and b/framework/lib/snakeyaml-1.17.jar differ diff --git a/framework/lib/spymemcached-2.11.7.jar b/framework/lib/spymemcached-2.12.1.jar similarity index 55% rename from framework/lib/spymemcached-2.11.7.jar rename to framework/lib/spymemcached-2.12.1.jar index 90ece2af74..904faf365b 100644 Binary files a/framework/lib/spymemcached-2.11.7.jar and b/framework/lib/spymemcached-2.12.1.jar differ diff --git a/framework/lib/validation-api-1.0.0.GA.jar b/framework/lib/validation-api-1.0.0.GA.jar deleted file mode 100644 index 1ff2dd718e..0000000000 Binary files a/framework/lib/validation-api-1.0.0.GA.jar and /dev/null differ diff --git a/framework/lib/validation-api-1.1.0.Final.jar b/framework/lib/validation-api-1.1.0.Final.jar new file mode 100644 index 0000000000..de85403868 Binary files /dev/null and b/framework/lib/validation-api-1.1.0.Final.jar differ diff --git a/framework/lib/xmlpull-1.1.3.1.jar b/framework/lib/xmlpull-1.1.3.1.jar deleted file mode 100644 index cbc149d0d5..0000000000 Binary files a/framework/lib/xmlpull-1.1.3.1.jar and /dev/null differ diff --git a/framework/lib/xmlpull-1.1.3.4d_b4_min.jar b/framework/lib/xmlpull-1.1.3.4d_b4_min.jar new file mode 100644 index 0000000000..43486c28c5 Binary files /dev/null and b/framework/lib/xmlpull-1.1.3.4d_b4_min.jar differ diff --git a/framework/lib/xstream-1.4.7.jar b/framework/lib/xstream-1.4.7.jar deleted file mode 100644 index ea4b6a2816..0000000000 Binary files a/framework/lib/xstream-1.4.7.jar and /dev/null differ diff --git a/framework/lib/xstream-1.4.9.jar b/framework/lib/xstream-1.4.9.jar new file mode 100644 index 0000000000..c754e0ab7f Binary files /dev/null and b/framework/lib/xstream-1.4.9.jar differ diff --git a/framework/patches/hibernate-4.2.19-patch-play.README b/framework/patches/hibernate-4.2.19-patch-play.README deleted file mode 100644 index c78e35d3ac..0000000000 --- a/framework/patches/hibernate-4.2.19-patch-play.README +++ /dev/null @@ -1,6 +0,0 @@ ----- -Download Hibernate 4.2.19.Final source code, apply the patch, and build with gradle (tip use export GRADLE_OPTS=-Xmx1G -XX:MaxPermSize=512m) ----- - -DRY RUN -> patch --dry-run -p1 -i hibernate-4.2.19-patch-play.patch -APPLY -> patch -p1 -i hibernate-4.2.19-patch-play.patch diff --git a/framework/patches/hibernate-5.2.10-patch-play.README b/framework/patches/hibernate-5.2.10-patch-play.README new file mode 100644 index 0000000000..5898a0f527 --- /dev/null +++ b/framework/patches/hibernate-5.2.10-patch-play.README @@ -0,0 +1,6 @@ +---- +Download Hibernate 5.2.10.Final source code, apply the patch, and build with gradle (tip use export GRADLE_OPTS=-Xmx1G -XX:MaxPermSize=512m) +---- + +DRY RUN -> patch --dry-run -p1 -i hibernate-5.2.10-patch-play.patch +APPLY -> patch -p1 -i hibernate-5.2.10-patch-play.patch diff --git a/framework/patches/hibernate-4.2.19-patch-play.patch.patch b/framework/patches/hibernate-5.2.10-patch-play.patch similarity index 100% rename from framework/patches/hibernate-4.2.19-patch-play.patch.patch rename to framework/patches/hibernate-5.2.10-patch-play.patch diff --git a/framework/pym/play/application.py b/framework/pym/play/application.py index db9933b578..1b70ef905a 100644 --- a/framework/pym/play/application.py +++ b/framework/pym/play/application.py @@ -116,7 +116,7 @@ def override(self, f, t): sys.exit(-1) toFile = os.path.join(self.path, t) if os.path.exists(toFile): - response = raw_input("~ Warning! %s already exists and will be overriden (y/n)? " % toFile) + response = raw_input("~ Warning! %s already exists and will be overridden (y/n)? " % toFile) if not response == 'y': return if not os.path.exists(os.path.dirname(toFile)): @@ -224,7 +224,7 @@ def check_jpda(self): s.bind(('', int(self.jpda_port))) s.close() except socket.error, e: - if self.play_env["disable_random_jpda"]: + if "disable_random_jpda" in self.play_env and self.play_env["disable_random_jpda"]: print 'JPDA port %s is already used, and command line option "-f" was specified. Cannot start server\n' % self.jpda_port sys.exit(-1) else: @@ -281,11 +281,11 @@ def java_cmd(self, java_args, cp_args=None, className='play.server.Server', args else: javaVersion = getJavaVersion() print "~ using java version \"%s\"" % javaVersion - if javaVersion.startswith("1.7"): - # JDK 7 compat - java_args.append('-XX:-UseSplitVerifier') - elif javaVersion.startswith("1.8"): - java_args.append('-noverify') + + if javaVersion.startswith("1.5") or javaVersion.startswith("1.6") or javaVersion.startswith("1.7"): + print "~ ERROR: java version prior to 1.8 are no longer supported: current version \"%s\" : please update" % javaVersion + + java_args.append('-noverify') java_policy = self.readConf('java.policy') if java_policy != '': diff --git a/framework/pym/play/commands/autotest.py b/framework/pym/play/commands/autotest.py index 0b1ed88d98..b1b0ef17ee 100644 --- a/framework/pym/play/commands/autotest.py +++ b/framework/pym/play/commands/autotest.py @@ -71,6 +71,19 @@ def autotest(app, args): if args.count('--selenium'): args.remove('--selenium') add_options.append('-DrunSeleniumTests') + + # Handle timeout parameter + weblcient_timeout = -1 + if app.readConf('webclient.timeout'): + weblcient_timeout = app.readConf('webclient.timeout') + + for arg in args: + if arg.startswith('--timeout='): + args.remove(arg) + weblcient_timeout = arg[10:] + + if weblcient_timeout >= 0: + add_options.append('-DwebclientTimeout=' + weblcient_timeout) # Run app test_result = os.path.join(app.path, 'test-result') diff --git a/framework/pym/play/commands/base.py b/framework/pym/play/commands/base.py index fae08a4981..c1cff8c1ef 100644 --- a/framework/pym/play/commands/base.py +++ b/framework/pym/play/commands/base.py @@ -97,7 +97,6 @@ def new(app, args, env, cmdloader=None): print "~" # Configure modules - runDepsAfter = False for m in md: # Check dependencies.yml of the module depsYaml = os.path.join(env["basedir"], 'modules/%s/conf/dependencies.yml' % m) @@ -106,12 +105,10 @@ def new(app, args, env, cmdloader=None): try: moduleDefinition = re.search(r'self:\s*(.*)\s*', deps).group(1) replaceAll(os.path.join(app.path, 'conf/dependencies.yml'), r'- play\n', '- play\n - %s\n' % moduleDefinition ) - runDepsAfter = True except Exception: pass - if runDepsAfter: - cmdloader.commands['dependencies'].execute(command='dependencies', app=app, args=['--sync'], env=env, cmdloader=cmdloader) + cmdloader.commands['dependencies'].execute(command='dependencies', app=app, args=['--sync'], env=env, cmdloader=cmdloader) print "~ OK, the application is created." print "~ Start it with : play run %s" % sys.argv[2] diff --git a/framework/pym/play/commands/daemon.py b/framework/pym/play/commands/daemon.py index 370364144e..f1cbb1f8ee 100644 --- a/framework/pym/play/commands/daemon.py +++ b/framework/pym/play/commands/daemon.py @@ -1,9 +1,12 @@ -import os, os.path +import os +import os.path import subprocess -from play.utils import * import time + +from play.utils import * + if os.name == 'nt': - import win32pdh, string, win32api + import win32pdh, win32pdhutil COMMANDS = ['start', 'stop', 'restart', 'pid', 'out'] @@ -15,6 +18,7 @@ 'out': 'Follow logs/system.out file' } + def execute(**kargs): command = kargs.get("command") app = kargs.get("app") @@ -32,12 +36,14 @@ def execute(**kargs): if command == 'out': out(app) + def start(app, args): app.check() if os.path.exists(app.pid_path()): pid = open(app.pid_path()).readline().strip() if process_running(pid): - print "~ Oops. %s is already started (pid:%s)! (or delete %s)" % (os.path.normpath(app.path), pid, os.path.normpath(app.pid_path())) + print "~ Oops. %s is already started (pid:%s)! (or delete %s)" % ( + os.path.normpath(app.path), pid, os.path.normpath(app.pid_path())) print "~" sys.exit(1) else: @@ -45,7 +51,7 @@ def start(app, args): os.remove(app.pid_path()) sysout = app.readConf('application.log.system.out') - sysout = sysout!='false' and sysout!='off' + sysout = sysout != 'false' and sysout != 'off' if not sysout: sout = None else: @@ -57,12 +63,13 @@ def start(app, args): sys.exit(-1) print "~ OK, %s is started" % os.path.normpath(app.path) if sysout: - print "~ output is redirected to %s" % os.path.normpath(os.path.join(app.log_path(), 'system.out')) + print "~ output is redirected to %s" % os.path.normpath(os.path.join(app.log_path(), 'system.out')) pid_file = open(app.pid_path(), 'w') pid_file.write(str(pid)) print "~ pid is %s" % pid print "~" + def stop(app): app.check() if not os.path.exists(app.pid_path()): @@ -87,12 +94,12 @@ def restart(app, args): kill(pid) sysout = app.readConf('application.log.system.out') - sysout = sysout!='false' and sysout!='off' + sysout = sysout != 'false' and sysout != 'off' java_cmd = app.java_cmd(args) if not sysout: - sout = None + sout = None else: - sout = open(os.path.join(app.log_path(), 'system.out'), 'w') + sout = open(os.path.join(app.log_path(), 'system.out'), 'w') try: pid = subprocess.Popen(java_cmd, stdout=sout, env=os.environ).pid except OSError: @@ -100,7 +107,7 @@ def restart(app, args): sys.exit(-1) print "~ OK, %s is restarted" % os.path.normpath(app.path) if sysout: - print "~ output is redirected to %s" % os.path.normpath(os.path.join(app.log_path(), 'system.out')) + print "~ output is redirected to %s" % os.path.normpath(os.path.join(app.log_path(), 'system.out')) pid_file = open(app.pid_path(), 'w') pid_file.write(str(pid)) print "~ New pid is %s" % pid @@ -118,6 +125,7 @@ def pid(app): print "~ PID of the running applications is %s" % pid print "~ " + def out(app): app.check() if not os.path.exists(os.path.join(app.log_path(), 'system.out')): @@ -138,14 +146,18 @@ def out(app): else: print line + def kill(pid): if os.name == 'nt': import ctypes handle = ctypes.windll.kernel32.OpenProcess(1, False, int(pid)) - if not ctypes.windll.kernel32.TerminateProcess(handle, 0): + process = ctypes.windll.kernel32.TerminateProcess(handle, 0) + ctypes.windll.kernel32.CloseHandle(handle) + if not process: print "~ Cannot kill the process with pid %s (ERROR %s)" % (pid, ctypes.windll.kernel32.GetLastError()) print "~ " sys.exit(-1) + print "~ Process with PID %s terminated" % pid else: try: os.kill(int(pid), 15) @@ -154,42 +166,46 @@ def kill(pid): print "~" sys.exit(-1) + def process_running(pid): - if os.name == 'nt': - return process_running_nt(pid) - else: - try: - os.kill(int(pid), 0) - return True - except OSError: - return False + if os.name == 'nt': + return process_running_nt(pid) + else: + try: + os.kill(int(pid), 0) + return True + except OSError: + return False + # loosely based on http://code.activestate.com/recipes/303339/ def process_list_nt(): - #each instance is a process, you can have multiple processes w/same name - junk, instances = win32pdh.EnumObjectItems(None,None,'process', win32pdh.PERF_DETAIL_WIZARD) - proc_ids={} - proc_dict={} + # each instance is a process, you can have multiple processes w/same name + processLocalizedName = win32pdhutil.find_pdh_counter_localized_name("Process") + junk, instances = win32pdh.EnumObjectItems(None, None, processLocalizedName, win32pdh.PERF_DETAIL_WIZARD) + proc_ids = {} + proc_dict = {} for instance in instances: if instance in proc_dict: proc_dict[instance] = proc_dict[instance] + 1 else: - proc_dict[instance]=0 + proc_dict[instance] = 0 + idProcessLocalizedName = win32pdhutil.find_pdh_counter_localized_name("ID Process") for instance, max_instances in proc_dict.items(): - for inum in xrange(max_instances+1): - hq = win32pdh.OpenQuery() # initializes the query handle - path = win32pdh.MakeCounterPath( (None,'process',instance, None, inum,'ID Process') ) - counter_handle=win32pdh.AddCounter(hq, path) - win32pdh.CollectQueryData(hq) #collects data for the counter + for inum in xrange(max_instances + 1): + hq = win32pdh.OpenQuery() # initializes the query handle + path = win32pdh.MakeCounterPath((None, processLocalizedName, instance, None, inum, idProcessLocalizedName)) + counter_handle = win32pdh.AddCounter(hq, path) + win32pdh.CollectQueryData(hq) # collects data for the counter type, val = win32pdh.GetFormattedCounterValue(counter_handle, win32pdh.PDH_FMT_LONG) - proc_ids[str(val)]=instance; - win32pdh.CloseQuery(hq) + proc_ids[str(val)] = instance; + win32pdh.CloseQuery(hq) return proc_ids def process_running_nt(pid): - if process_list_nt().get(pid,"") != "": + if process_list_nt().get(pid, "") != "": return True else: return False diff --git a/framework/pym/play/commands/deps.py b/framework/pym/play/commands/deps.py index cc3405eb84..d82da2b62f 100644 --- a/framework/pym/play/commands/deps.py +++ b/framework/pym/play/commands/deps.py @@ -20,6 +20,8 @@ def execute(**kargs): force = "false" trim = "false" + shortModuleNames = "false" + if args.count('--forceCopy') == 1: args.remove('--forceCopy') force = "true" @@ -29,17 +31,24 @@ def execute(**kargs): force = "true" trim = "true" + if args.count('--shortModuleNames') == 1: + args.remove('--shortModuleNames') + shortModuleNames = "true" + classpath = app.getClasspath() args_memory = app.java_args_memory(args) app.jpda_port = app.readConf('jpda.port') - add_options = ['-Dapplication.path=%s' % (app.path), '-Dframework.path=%s' % (play_env['basedir']), '-Dplay.id=%s' % play_env['id'], '-Dplay.version=%s' % play_env['version'], '-Dplay.forcedeps=%s' % (force), '-Dplay.trimdeps=%s' % (trim)] + add_options = ['-Dapplication.path=%s' % (app.path), '-Dframework.path=%s' % (play_env['basedir']), '-Dplay.id=%s' % play_env['id'], '-Dplay.version=%s' % play_env['version'], '-Dplay.forcedeps=%s' % (force), '-Dplay.trimdeps=%s' % (trim), '-Dplay.shortModuleNames=%s' % (shortModuleNames)] if args.count('--verbose'): args.remove('--verbose') add_options.append('-Dverbose') if args.count('--sync'): args.remove('--sync') add_options.append('-Dsync') + if args.count('--nosync'): + args.remove('--nosync') + add_options.append('-Dnosync') if args.count('--debug'): args.remove('--debug') add_options.append('-Ddebug') diff --git a/framework/pym/play/commands/eclipse.py b/framework/pym/play/commands/eclipse.py index 8740bbadb0..93211c16f2 100644 --- a/framework/pym/play/commands/eclipse.py +++ b/framework/pym/play/commands/eclipse.py @@ -29,13 +29,12 @@ def execute(**kargs): vm_arguments = app.readConf('jvm.memory') javaVersion = getJavaVersion() + print "~ using java version \"%s\"" % javaVersion - if javaVersion.startswith("1.7"): - # JDK 7 compat - vm_arguments = vm_arguments +' -XX:-UseSplitVerifier' - elif javaVersion.startswith("1.8"): - # JDK 8 compatible - vm_arguments = vm_arguments +' -noverify' + if javaVersion.startswith("1.5") or javaVersion.startswith("1.6") or javaVersion.startswith("1.7"): + print "~ ERROR: java version prior to 1.8 are no longer supported: current version \"%s\" : please update" % javaVersion + + vm_arguments = vm_arguments +' -noverify' if application_name: application_name = application_name.replace("/", " ") diff --git a/framework/pym/play/commands/modulesrepo.py b/framework/pym/play/commands/modulesrepo.py index fa7f23ce41..d8de6670fe 100644 --- a/framework/pym/play/commands/modulesrepo.py +++ b/framework/pym/play/commands/modulesrepo.py @@ -1,4 +1,5 @@ import os +import subprocess import sys import re import zipfile @@ -29,6 +30,8 @@ DEFAULT_REPO = 'https://www.playframework.com' +DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7' + def load_module(name): base = os.path.normpath(os.path.dirname(os.path.realpath(sys.argv[0]))) mod_desc = imp.find_module(name, [os.path.join(base, 'framework/pym')]) @@ -84,8 +87,14 @@ def __init__(self, width=55): def retrieve(self, url, destination, callback=None): self.size = 0 - time.clock() - try: urllib.urlretrieve(url, destination, self.progress) + time.clock() + try: + headers={'User-Agent':DEFAULT_USER_AGENT, + 'Accept': 'application/json' + } + req = urllib2.Request(url, headers=headers) + result = urllib2.urlopen(req) + self.chunk_read(result, destination, report_hook=self.chunk_report) except KeyboardInterrupt: print '\n~ Download cancelled' print '~' @@ -101,14 +110,43 @@ def retrieve(self, url, destination, callback=None): print '' return self.size - def progress(self, blocks, blocksize, filesize): + def chunk_read(self, response, destination, chunk_size=8192, report_hook=None): + total_size = response.info().getheader('Content-Length').strip() + total_size = int(total_size) + bytes_so_far = 0 + file = open(destination,"wb") + + while 1: + chunk = response.read(chunk_size) + file.write(chunk) + bytes_so_far += len(chunk) + + if not chunk: + break + + if report_hook: + #report_hook(bytes_so_far, chunk_size, total_size) + self.progress(bytes_so_far, chunk_size, total_size) + + return bytes_so_far + + + def chunk_report(self, bytes_so_far, chunk_size, total_size): + percent = float(bytes_so_far) / total_size + percent = round(percent*100, 2) + sys.stdout.write("Downloaded %d of %d bytes (%0.2f%%)\r" % (bytes_so_far, total_size, percent)) + if bytes_so_far >= total_size: + sys.stdout.write('\n') + + + def progress(self, bytes_so_far, blocksize, filesize): self.cycles += 1 - bits = min(blocks*blocksize, filesize) + bits = min(bytes_so_far, filesize) if bits != filesize: done = self.proc(bits, filesize) else: done = 100 - bar = self.bar(done) + bar = self.bar(bytes_so_far, filesize, done) if not self.cycles % 3 and bits != filesize: now = time.clock() elapsed = now-self.before @@ -121,10 +159,10 @@ def progress(self, blocks, blocksize, filesize): self.size = self.kibi(bits) print '\r~ [%s] %s KiB/s ' % (bar, str(average)), - def bar(self, done): + def bar(self, bytes_so_far, filesize, done): span = self.width * done * 0.01 offset = len(str(int(done))) - .99 - result = ('%d%%' % (done,)).center(self.width) + result = ('%s of %s KiB (%d%%)' % (self.kibi(bytes_so_far), self.kibi(filesize), done,)).center(self.width) return result.replace(' ', '-', int(span - offset)) class Unzip: @@ -150,9 +188,11 @@ def extract(self, file, dir): complete = int (i / perc) * percent if not name.endswith('/'): outfile = open(os.path.join(dir, name), 'wb') - outfile.write(zf.read(name)) - outfile.flush() - outfile.close() + try: + outfile.write(zf.read(name)) + outfile.flush() + finally: + outfile.close() def _createstructure(self, file, dir): self._makedirs(self._listdirs(file), dir) @@ -176,6 +216,7 @@ def _listdirs(self, file): dirs.sort() return dirs + def new(app, args, play_env): if os.path.exists(app.path): print "~ Oops. %s already exists" % app.path @@ -212,6 +253,7 @@ def new(app, args, play_env): print "~ Have fun!" print "~" + def list(app, args): print "~ You can also browse this list online at:" for repo in repositories: @@ -271,21 +313,23 @@ def build(app, args, env): deps_file = os.path.join(app.path, 'conf', 'dependencies.yml') if os.path.exists(deps_file): f = open(deps_file) - deps = yaml.load(f.read()) - if 'self' in deps: - splitted = deps["self"].split(" -> ") - if len(splitted) == 2: - nameAndVersion = splitted.pop().strip() - splitted = nameAndVersion.split(" ") - if len(splitted) == 2: - version = splitted.pop() - name = splitted.pop() - for dep in deps["require"]: - if isinstance(dep, basestring): - splitted = dep.split(" ") - if len(splitted) == 2 and splitted[0] == "play": - fwkMatch = splitted[1] - f.close + try: + deps = yaml.load(f.read()) + if 'self' in deps: + splitted = deps["self"].split(" -> ") + if len(splitted) == 2: + nameAndVersion = splitted.pop().strip() + splitted = nameAndVersion.split(" ") + if len(splitted) == 2: + version = splitted.pop() + name = splitted.pop() + for dep in deps["require"]: + if isinstance(dep, basestring): + splitted = dep.split(" ") + if len(splitted) == 2 and splitted[0] == "play": + fwkMatch = splitted[1] + finally: + f.close() if name is None: name = os.path.basename(app.path) @@ -297,7 +341,7 @@ def build(app, args, env): if os.path.exists(deps_file): f = open(deps_file) deps = yaml.load(f.read()) - if 'self' in deps: + if 'self' in deps: splitted = deps["self"].split(" -> ") f.close() if len(splitted) == 2: @@ -311,15 +355,16 @@ def build(app, args, env): replaceAll(deps_file, origModuleDefinition, modifiedModuleDefinition) except: pass - build_file = os.path.join(app.path, 'build.xml') if os.path.exists(build_file): print "~" print "~ Building..." print "~" - os.system('ant -f %s -Dplay.path=%s' % (build_file, ftb) ) + status = subprocess.call('ant -f %s -Dplay.path=%s' % (build_file, ftb), shell=True) print "~" + if status: + sys.exit(status) mv = '%s-%s' % (name, version) print("~ Packaging %s ... " % mv) @@ -331,20 +376,24 @@ def build(app, args, env): manifest = os.path.join(app.path, 'manifest') manifestF = open(manifest, 'w') - manifestF.write('version=%s\nframeworkVersions=%s\n' % (version, fwkMatch)) - manifestF.close() + try: + manifestF.write('version=%s\nframeworkVersions=%s\n' % (version, fwkMatch)) + finally: + manifestF.close() zip = zipfile.ZipFile(os.path.join(dist_dir, '%s.zip' % mv), 'w', zipfile.ZIP_STORED) - for (dirpath, dirnames, filenames) in os.walk(app.path): - if dirpath == dist_dir: - continue - if dirpath.find(os.sep + '.') > -1 or dirpath.find('/tmp/') > -1 or dirpath.find('/test-result/') > -1 or dirpath.find('/logs/') > -1 or dirpath.find('/eclipse/') > -1 or dirpath.endswith('/test-result') or dirpath.endswith('/logs') or dirpath.endswith('/eclipse') or dirpath.endswith('/nbproject'): - continue - for file in filenames: - if file.find('~') > -1 or file.endswith('.iml') or file.startswith('.'): + try: + for (dirpath, dirnames, filenames) in os.walk(app.path): + if dirpath == dist_dir: continue - zip.write(os.path.join(dirpath, file), os.path.join(dirpath[len(app.path):], file)) - zip.close() + if dirpath.find(os.sep + '.') > -1 or dirpath.find('/tmp/') > -1 or dirpath.find('/test-result/') > -1 or dirpath.find('/logs/') > -1 or dirpath.find('/eclipse/') > -1 or dirpath.endswith('/test-result') or dirpath.endswith('/logs') or dirpath.endswith('/eclipse') or dirpath.endswith('/nbproject'): + continue + for file in filenames: + if file.find('~') > -1 or file.endswith('.iml') or file.startswith('.'): + continue + zip.write(os.path.join(dirpath, file), os.path.join(dirpath[len(app.path):], file)) + finally: + zip.close() os.remove(manifest) @@ -360,6 +409,7 @@ def build(app, args, env): print "~ Package is available at %s" % os.path.join(dist_dir, '%s.zip' % mv) print "~" + def install(app, args, env): if len(sys.argv) < 3: help_file = os.path.join(env["basedir"], 'documentation/commands/cmd-install.txt') @@ -416,6 +466,7 @@ def install(app, args, env): print '~' print '~ Fetching %s' % fetch + Downloader().retrieve(fetch, archive) if not os.path.exists(archive): @@ -440,6 +491,7 @@ def install(app, args, env): print '~' sys.exit(0) + def add(app, args, env): app.check() @@ -478,7 +530,8 @@ def add(app, args, env): print "~ Module %s add to application %s." % (mn, app.name()) print "~ " -def load_module_list(custom_server): + +def load_module_list(custom_server=None): def addServer(module, server): module['server'] = server @@ -506,16 +559,20 @@ def any(arr, func): modules.append(addServer(module, repo)) return modules + def load_modules_from(modules_server): try: url = '%s/modules' % modules_server - req = urllib2.Request(url) - req.add_header('Accept', 'application/json') + headers={'User-Agent':DEFAULT_USER_AGENT, + 'Accept': 'application/json' + } + req = urllib2.Request(url, headers=headers) result = urllib2.urlopen(req) return json.loads(result.read()) except urllib2.HTTPError, e: print "~ Oops," print "~ Cannot fetch the modules list from %s (%s)..." % (url, e.code) + print e.reason print "~" sys.exit(-1) except urllib2.URLError, e: diff --git a/framework/pym/yaml/composer.py b/framework/pym/yaml/composer.py index 06e5ac782f..df85ef653b 100644 --- a/framework/pym/yaml/composer.py +++ b/framework/pym/yaml/composer.py @@ -72,9 +72,9 @@ def compose_node(self, parent, index): anchor = event.anchor if anchor is not None: if anchor in self.anchors: - raise ComposerError("found duplicate anchor %r; first occurence" + raise ComposerError("found duplicate anchor %r; first occurrence" % anchor.encode('utf-8'), self.anchors[anchor].start_mark, - "second occurence", event.start_mark) + "second occurrence", event.start_mark) self.descend_resolver(parent, index) if self.check_event(ScalarEvent): node = self.compose_scalar_node(anchor) diff --git a/framework/pym/yaml/emitter.py b/framework/pym/yaml/emitter.py index 4cb2c8aa55..5186bccbd0 100644 --- a/framework/pym/yaml/emitter.py +++ b/framework/pym/yaml/emitter.py @@ -41,7 +41,7 @@ def __init__(self, stream, canonical=None, indent=None, width=None, # The stream should have the methods `write` and possibly `flush`. self.stream = stream - # Encoding can be overriden by STREAM-START. + # Encoding can be overridden by STREAM-START. self.encoding = None # Emitter is a state machine with a stack of states to handle nested @@ -650,7 +650,7 @@ def analyze_scalar(self, scalar): flow_indicators = True # First character or preceded by a whitespace. - preceeded_by_whitespace = True + preceded_by_whitespace = True # Last character or followed by a whitespace. followed_by_whitespace = (len(scalar) == 1 or @@ -687,7 +687,7 @@ def analyze_scalar(self, scalar): flow_indicators = True if followed_by_whitespace: block_indicators = True - if ch == u'#' and preceeded_by_whitespace: + if ch == u'#' and preceded_by_whitespace: flow_indicators = True block_indicators = True @@ -728,7 +728,7 @@ def analyze_scalar(self, scalar): # Prepare for the next character. index += 1 - preceeded_by_whitespace = (ch in u'\0 \t\r\n\x85\u2028\u2029') + preceded_by_whitespace = (ch in u'\0 \t\r\n\x85\u2028\u2029') followed_by_whitespace = (index+1 >= len(scalar) or scalar[index+1] in u'\0 \t\r\n\x85\u2028\u2029') diff --git a/framework/pym/yaml/scanner.py b/framework/pym/yaml/scanner.py index 5228fad65c..475dfec7f7 100644 --- a/framework/pym/yaml/scanner.py +++ b/framework/pym/yaml/scanner.py @@ -568,7 +568,7 @@ def fetch_value(self): else: # Block context needs additional checks. - # (Do we really need them? They will be catched by the parser + # (Do we really need them? They will be caught by the parser # anyway.) if not self.flow_level: diff --git a/framework/src/play.plugins b/framework/src/play.plugins index 5c2dbb813f..f6e8553e8c 100644 --- a/framework/src/play.plugins +++ b/framework/src/play.plugins @@ -1,4 +1,5 @@ 0:play.CorePlugin +1:play.ConfigurationChangeWatcherPlugin 100:play.data.parsing.TempFilePlugin 200:play.data.validation.ValidationPlugin 300:play.db.DBPlugin diff --git a/framework/src/play/ConfigurationChangeWatcherPlugin.java b/framework/src/play/ConfigurationChangeWatcherPlugin.java new file mode 100644 index 0000000000..369831bb81 --- /dev/null +++ b/framework/src/play/ConfigurationChangeWatcherPlugin.java @@ -0,0 +1,36 @@ +package play; + +import play.vfs.VirtualFile; + +/** + * Plugin used for tracking for application.conf changes + */ +public class ConfigurationChangeWatcherPlugin extends PlayPlugin { + protected static long configLastModified = System.currentTimeMillis(); + + @Override + public void onApplicationStart() { + configLastModified = System.currentTimeMillis(); + } + + @Override + public void onConfigurationRead() { + if (Play.mode.isProd()) { + Play.pluginCollection.disablePlugin(this); + } + } + + @Override + public void detectChange() { + for (VirtualFile conf : Play.confs) { + if (conf.lastModified() > configLastModified) { + configLastModified = conf.lastModified(); + onConfigurationFileChanged(conf); + } + } + } + + protected void onConfigurationFileChanged(VirtualFile conf) { + throw new RuntimeException("Need to restart Play because " + conf.getRealFile().getAbsolutePath() + " has been changed"); + } +} diff --git a/framework/src/play/CorePlugin.java b/framework/src/play/CorePlugin.java index 97a82f1e70..ac797bfb2e 100644 --- a/framework/src/play/CorePlugin.java +++ b/framework/src/play/CorePlugin.java @@ -1,17 +1,25 @@ package play; +import static java.util.Arrays.asList; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.jamonapi.Monitor; import com.jamonapi.MonitorFactory; import com.jamonapi.utils.Misc; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.text.SimpleDateFormat; -import java.util.*; -import org.apache.commons.lang.StringUtils; import play.Play.Mode; import play.classloading.ApplicationClasses.ApplicationClass; import play.classloading.enhancers.ContinuationEnhancer; @@ -27,8 +35,6 @@ import play.mvc.Http.Request; import play.mvc.Http.Response; -import static java.util.Arrays.asList; - /** * Plugin used for core tasks */ @@ -36,6 +42,10 @@ public class CorePlugin extends PlayPlugin { /** * Get the application status + * + * @param json + * true if the status should be return in JSON + * @return application status */ public static String computeApplicationStatus(boolean json) { if (json) { @@ -70,11 +80,11 @@ public static String computeApplicationStatus(boolean json) { } /** - * Intercept /@status and check that the Authorization header is valid. - * Then ask each plugin for a status dump and send it over the HTTP response. + * Intercept /@status and check that the Authorization header is valid. Then ask each plugin for a status dump and + * send it over the HTTP response. * - * You can ask the /@status using the authorization header and putting your status secret key in it. - * Prior to that you would be required to start play with a -DstatusKey=yourkey + * You can ask the /@status using the authorization header and putting your status secret key in it. Prior to that + * you would be required to start play with a -DstatusKey=yourkey */ @Override public boolean rawInvocation(Request request, Response response) throws Exception { @@ -87,14 +97,15 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio } } if (request.path.equals("/@status") || request.path.equals("/@status.json")) { - if(!Play.started) { + if (!Play.started) { response.print("Application is not started"); response.status = 503; return true; } response.contentType = request.path.contains(".json") ? "application/json" : "text/plain"; Header authorization = request.headers.get("authorization"); - if (authorization != null && (Crypto.sign("@status").equals(authorization.value()) || System.getProperty("statusKey", Play.secretKey).equals(authorization.value()))) { + if (authorization != null && (Crypto.sign("@status").equals(authorization.value()) + || System.getProperty("statusKey", Play.secretKey).equals(authorization.value()))) { response.print(computeApplicationStatus(request.path.contains(".json"))); response.status = 200; return true; @@ -138,7 +149,8 @@ public String getStatus() { out.println("~~~~~~~~~~~~"); out.println("Path: " + Play.applicationPath); out.println("Name: " + Play.configuration.getProperty("application.name", "(not set)")); - out.println("Started at: " + (Play.started ? new SimpleDateFormat("MM/dd/yyyy HH:mm").format(new Date(Play.startedAt)) : "Not yet started")); + out.println("Started at: " + + (Play.started ? new SimpleDateFormat("MM/dd/yyyy HH:mm").format(new Date(Play.startedAt)) : "Not yet started")); out.println(); out.println("Loaded modules:"); out.println("~~~~~~~~~~~~~~"); @@ -149,7 +161,8 @@ public String getStatus() { out.println("Loaded plugins:"); out.println("~~~~~~~~~~~~~~"); for (PlayPlugin plugin : Play.pluginCollection.getAllPlugins()) { - out.println(plugin.index + ":" + plugin.getClass().getName() + " [" + (Play.pluginCollection.isEnabled(plugin) ? "enabled" : "disabled") + "]"); + out.println(plugin.index + ":" + plugin.getClass().getName() + " [" + + (Play.pluginCollection.isEnabled(plugin) ? "enabled" : "disabled") + "]"); } out.println(); out.println("Threads:"); @@ -170,9 +183,10 @@ public String getStatus() { try { out.println("Monitors:"); out.println("~~~~~~~~"); - List monitors = new ArrayList(asList(MonitorFactory.getRootMonitor().getMonitors())); + List monitors = new ArrayList<>(asList(MonitorFactory.getRootMonitor().getMonitors())); Collections.sort(monitors, new Comparator() { - @Override public int compare(Monitor m1, Monitor m2) { + @Override + public int compare(Monitor m1, Monitor m2) { return Double.compare(m2.getTotal(), m1.getTotal()); } }); @@ -184,8 +198,8 @@ public String getStatus() { } for (Monitor monitor : monitors) { if (monitor.getHits() > 0) { - out.println(String.format("%-" + lm + "s -> %8.0f hits; %8.1f avg; %8.1f min; %8.1f max;", - monitor.getLabel(), monitor.getHits(), monitor.getAvg(), monitor.getMin(), monitor.getMax())); + out.println(String.format("%-" + lm + "s -> %8.0f hits; %8.1f avg; %8.1f min; %8.1f max;", monitor.getLabel(), + monitor.getHits(), monitor.getAvg(), monitor.getMin(), monitor.getMax())); } } } catch (Exception e) { @@ -290,22 +304,20 @@ static ThreadGroup getRootThread() { return root; } + protected Enhancer[] defaultEnhancers() { + return new Enhancer[] { new PropertiesEnhancer(), new ContinuationEnhancer(), new SigEnhancer(), new ControllersEnhancer(), + new MailerEnhancer(), new LocalvariablesNamesEnhancer() }; + } + @Override public void enhance(ApplicationClass applicationClass) throws Exception { - Class[] enhancers = new Class[]{ - PropertiesEnhancer.class, - ContinuationEnhancer.class, - SigEnhancer.class, - ControllersEnhancer.class, - MailerEnhancer.class, - LocalvariablesNamesEnhancer.class - }; - for (Class enhancer : enhancers) { + for (Enhancer enhancer : defaultEnhancers()) { try { long start = System.currentTimeMillis(); - ((Enhancer) enhancer.newInstance()).enhanceThisClass(applicationClass); + enhancer.enhanceThisClass(applicationClass); if (Logger.isTraceEnabled()) { - Logger.trace("%sms to apply %s to %s", System.currentTimeMillis() - start, enhancer.getSimpleName(), applicationClass.name); + Logger.trace("%sms to apply %s to %s", System.currentTimeMillis() - start, enhancer.getClass().getSimpleName(), + applicationClass.name); } } catch (Exception e) { throw new UnexpectedException("While applying " + enhancer + " on " + applicationClass.name, e); diff --git a/framework/src/play/Invoker.java b/framework/src/play/Invoker.java index e962c54050..1531eae74b 100644 --- a/framework/src/play/Invoker.java +++ b/framework/src/play/Invoker.java @@ -13,7 +13,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import com.jamonapi.Monitor; +import com.jamonapi.MonitorFactory; + import play.Play.Mode; +import play.classloading.ApplicationClassloader; import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer; import play.exceptions.PlayException; import play.exceptions.UnexpectedException; @@ -22,9 +26,6 @@ import play.libs.F.Promise; import play.utils.PThreadFactory; -import com.jamonapi.Monitor; -import com.jamonapi.MonitorFactory; - /** * Run some code in a Play! context */ @@ -37,10 +38,12 @@ public class Invoker { /** * Run the code in a new thread took from a thread pool. - * @param invocation The code to run + * + * @param invocation + * The code to run * @return The future object, to know when the task is completed */ - public static Future invoke(final Invocation invocation) { + public static Future invoke(Invocation invocation) { Monitor monitor = MonitorFactory.getMonitor("Invoker queue size", "elmts."); monitor.add(executor.getQueue().size()); invocation.waitInQueue = MonitorFactory.start("Waiting for execution"); @@ -49,11 +52,14 @@ public static Future invoke(final Invocation invocation) { /** * Run the code in a new thread after a delay - * @param invocation The code to run - * @param millis The time to wait before, in milliseconds + * + * @param invocation + * The code to run + * @param millis + * The time to wait before, in milliseconds * @return The future object, to know when the task is completed */ - public static Future invoke(final Invocation invocation, long millis) { + public static Future invoke(Invocation invocation, long millis) { Monitor monitor = MonitorFactory.getMonitor("Invocation queue", "elmts."); monitor.add(executor.getQueue().size()); return executor.schedule(invocation, millis, TimeUnit.MILLISECONDS); @@ -61,7 +67,9 @@ public static Future invoke(final Invocation invocation, long millis) { /** * Run the code in the same thread than caller. - * @param invocation The code to run + * + * @param invocation + * The code to run */ public static void invokeInThread(DirectInvocation invocation) { boolean retry = true; @@ -84,12 +92,21 @@ public static void invokeInThread(DirectInvocation invocation) { } } + static void resetClassloaders() { + Thread[] executorThreads = new Thread[executor.getPoolSize()]; + Thread.enumerate(executorThreads); + for (Thread thread : executorThreads) { + if (thread != null && thread.getContextClassLoader() instanceof ApplicationClassloader) + thread.setContextClassLoader(ClassLoader.getSystemClassLoader()); + } + } + /** * The class/method that will be invoked by the current operation */ public static class InvocationContext { - public static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); private final List annotations; private final String invocationType; @@ -99,7 +116,7 @@ public static InvocationContext current() { public InvocationContext(String invocationType) { this.invocationType = invocationType; - this.annotations = new ArrayList(); + this.annotations = new ArrayList<>(); } public InvocationContext(String invocationType, List annotations) { @@ -114,7 +131,7 @@ public InvocationContext(String invocationType, Annotation[] annotations) { public InvocationContext(String invocationType, Annotation[]... annotations) { this.invocationType = invocationType; - this.annotations = new ArrayList(); + this.annotations = new ArrayList<>(); for (Annotation[] some : annotations) { this.annotations.addAll(Arrays.asList(some)); } @@ -144,8 +161,10 @@ public boolean isAnnotationPresent(Class clazz) { } /** - * Returns the InvocationType for this invocation - Ie: A plugin can use this to - * find out if it runs in the context of a background Job + * Returns the InvocationType for this invocation - Ie: A plugin can use this to find out if it runs in the + * context of a background Job + * + * @return the InvocationType for this invocation */ public String getInvocationType() { return invocationType; @@ -167,7 +186,7 @@ public String toString() { /** * An Invocation in something to run in a Play! context */ - public static abstract class Invocation implements Runnable { + public abstract static class Invocation implements Runnable { /** * If set, monitor the time the invocation waited in the queue @@ -176,15 +195,15 @@ public static abstract class Invocation implements Runnable { /** * Override this method + * * @throws java.lang.Exception + * Thrown if Invocation encounters any problems */ public abstract void execute() throws Exception; - /** - * Needs this method to do stuff *before* init() is executed. - * The different Invocation-implementations does a lot of stuff in init() - * and they might do it before calling super.init() + * Needs this method to do stuff *before* init() is executed. The different Invocation-implementations does a + * lot of stuff in init() and they might do it before calling super.init() */ protected void preInit() { // clear language for this request - we're resolving it later when it is needed @@ -192,7 +211,9 @@ protected void preInit() { } /** - * Init the call (especially usefull in DEV mode to detect changes) + * Init the call (especially useful in DEV mode to detect changes) + * + * @return true if successful */ public boolean init() { Thread.currentThread().setContextClassLoader(Play.classloader); @@ -207,7 +228,6 @@ public boolean init() { return true; } - public abstract InvocationContext getInvocationContext(); /** @@ -219,8 +239,7 @@ public void before() { } /** - * Things to do after an Invocation. - * (if the Invocation code has not thrown any exception) + * Things to do after an Invocation. (if the Invocation code has not thrown any exception) */ public void after() { Play.pluginCollection.afterInvocation(); @@ -229,6 +248,9 @@ public void after() { /** * Things to do when the whole invocation has succeeded (before + execute + after) + * + * @throws java.lang.Exception + * Thrown if Invoker encounters any problems */ public void onSuccess() throws Exception { Play.pluginCollection.onInvocationSuccess(); @@ -236,6 +258,9 @@ public void onSuccess() throws Exception { /** * Things to do if the Invocation code thrown an exception + * + * @param e + * The exception */ public void onException(Throwable e) { Play.pluginCollection.onInvocationException(e); @@ -247,7 +272,9 @@ public void onException(Throwable e) { /** * The request is suspended + * * @param suspendRequest + * the suspended request */ public void suspend(Suspend suspendRequest) { if (suspendRequest.task != null) { @@ -266,15 +293,16 @@ public void _finally() { } private void withinFilter(play.libs.F.Function0 fct) throws Throwable { - for( PlayPlugin plugin : Play.pluginCollection.getEnabledPlugins() ) { - if (plugin.getFilter() != null) - plugin.getFilter().withinFilter(fct); - } + F.Option> filters = Play.pluginCollection.composeFilters(); + if (filters.isDefined()) { + filters.get().withinFilter(fct); + } } /** * It's time to execute. */ + @Override public void run() { if (waitInQueue != null) { waitInQueue.stop(); @@ -285,6 +313,7 @@ public void run() { before(); final AtomicBoolean executed = new AtomicBoolean(false); this.withinFilter(new play.libs.F.Function0() { + @Override public Void apply() throws Throwable { executed.set(true); execute(); @@ -312,7 +341,7 @@ public Void apply() throws Throwable { /** * A direct invocation (in the same thread than caller) */ - public static abstract class DirectInvocation extends Invocation { + public abstract static class DirectInvocation extends Invocation { public static final String invocationType = "DirectInvocation"; @@ -339,7 +368,8 @@ public InvocationContext getInvocationContext() { * Init executor at load time. */ static { - int core = Integer.parseInt(Play.configuration.getProperty("play.pool", Play.mode == Mode.DEV ? "1" : ((Runtime.getRuntime().availableProcessors() + 1) + ""))); + int core = Integer.parseInt(Play.configuration.getProperty("play.pool", + Play.mode == Mode.DEV ? "1" : ((Runtime.getRuntime().availableProcessors() + 1) + ""))); executor = new ScheduledThreadPoolExecutor(core, new PThreadFactory("play"), new ThreadPoolExecutor.AbortPolicy()); } @@ -352,7 +382,7 @@ public static class Suspend extends PlayException { * Suspend for a timeout (in milliseconds). */ long timeout; - + /** * Wait for task execution. */ @@ -389,7 +419,7 @@ static class WaitForTasksCompletion extends Thread { Map, Invocation> queue; public WaitForTasksCompletion() { - queue = new ConcurrentHashMap, Invocation>(); + queue = new ConcurrentHashMap<>(); setName("WaitForTasksCompletion"); setDaemon(true); } @@ -398,6 +428,7 @@ public static void waitFor(Future task, final Invocation invocation) { if (task instanceof Promise) { Promise smartFuture = (Promise) task; smartFuture.onRedeem(new F.Action>() { + @Override public void invoke(Promise result) { executor.submit(invocation); } @@ -419,7 +450,7 @@ public void run() { while (true) { try { if (!queue.isEmpty()) { - for (Future task : new HashSet>(queue.keySet())) { + for (Future task : new HashSet<>(queue.keySet())) { if (task.isDone()) { executor.submit(queue.get(task)); queue.remove(task); diff --git a/framework/src/play/Logger.java b/framework/src/play/Logger.java index 85ec10432e..5d957896d0 100644 --- a/framework/src/play/Logger.java +++ b/framework/src/play/Logger.java @@ -121,7 +121,7 @@ public static void setUp(String level) { } /** - * Utility method that translayte log4j levels to java.util.logging levels. + * Utility method that translate log4j levels to java.util.logging levels. */ static java.util.logging.Level toJuliLevel(String level) { java.util.logging.Level juliLevel = java.util.logging.Level.INFO; @@ -532,7 +532,7 @@ static boolean niceThrowable(org.apache.log4j.Level level, Throwable e, String m Throwable toClean = e; for (int i = 0; i < 5; i++) { // Clean stack trace - List cleanTrace = new ArrayList(); + List cleanTrace = new ArrayList<>(); for (StackTraceElement se : toClean.getStackTrace()) { if (se.getClassName().startsWith("play.server.PlayHandler$NettyInvocation")) { cleanTrace.add(new StackTraceElement("Invocation", "HTTP Request", "Play!", -1)); @@ -647,15 +647,15 @@ public CallInfo(String className, String methodName) { * @return the className of the class actually logging the message */ static String getCallerClassName() { - final int level = 5; + int level = 5; return getCallerClassName(level); } /** * @return the className of the class actually logging the message */ - static String getCallerClassName(final int level) { - CallInfo ci = getCallerInformations(level); + static String getCallerClassName(int level) { + CallInfo ci = getCallerInformation(level); return ci.className; } @@ -664,7 +664,7 @@ static String getCallerClassName(final int level) { * @param level method stack depth * @return who called the logger */ - static CallInfo getCallerInformations(int level) { + static CallInfo getCallerInformation(int level) { StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); StackTraceElement caller = callStack[level]; return new CallInfo(caller.getClassName(), caller.getMethodName()); diff --git a/framework/src/play/Play.java b/framework/src/play/Play.java index 85f52053f2..be156ca24b 100644 --- a/framework/src/play/Play.java +++ b/framework/src/play/Play.java @@ -1,13 +1,23 @@ package play; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; import java.io.InputStreamReader; -import java.io.BufferedReader; import java.io.LineNumberReader; -import java.io.IOException; import java.net.URI; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,6 +27,7 @@ import play.classloading.ApplicationClassloader; import play.deps.DependenciesManager; import play.exceptions.PlayException; +import play.exceptions.RestartNeededException; import play.exceptions.UnexpectedException; import play.libs.IO; import play.mvc.Http; @@ -53,6 +64,7 @@ public boolean isProd() { return this == PROD; } } + /** * Is the application initialized */ @@ -69,15 +81,15 @@ public boolean isProd() { /** * The framework ID */ - public static String id; + public static String id = System.getProperty("play.id", ""); /** * The application mode */ - public static Mode mode; + public static Mode mode = Mode.DEV; /** * The application root */ - public static File applicationPath = null; + public static File applicationPath = new File(System.getProperty("application.path", ".")); /** * tmp dir */ @@ -101,15 +113,15 @@ public boolean isProd() { /** * All paths to search for files */ - public static List roots = new ArrayList(16); + public static List roots = new ArrayList<>(16); /** * All paths to search for Java files */ - public static List javaPath; + public static List javaPath = new CopyOnWriteArrayList<>(); /** * All paths to search for templates files */ - public static List templatesPath; + public static List templatesPath = new ArrayList<>(2); /** * Main routes file */ @@ -117,15 +129,15 @@ public boolean isProd() { /** * Plugin routes files */ - public static Map modulesRoutes; + public static Map modulesRoutes = new HashMap<>(16); /** * The loaded configuration files */ - public static Set confs = new HashSet(1); + public static Set confs = new HashSet<>(1); /** * The app configuration (already resolved from the framework id) */ - public static Properties configuration; + public static Properties configuration = new Properties(); /** * The last time than the application has started */ @@ -133,7 +145,7 @@ public boolean isProd() { /** * The list of supported locales */ - public static List langs = new ArrayList(16); + public static List langs = new ArrayList<>(16); /** * The very secret key */ @@ -143,17 +155,15 @@ public boolean isProd() { */ public static PluginCollection pluginCollection = new PluginCollection(); /** - * Readonly list containing currently enabled plugins. - * This list is updated from pluginCollection when pluginCollection is modified - * Play plugins - * Use pluginCollection instead. + * Readonly list containing currently enabled plugins. This list is updated from pluginCollection when + * pluginCollection is modified Play plugins Use pluginCollection instead. */ @Deprecated public static List plugins = pluginCollection.getEnabledPlugins(); /** * Modules */ - public static Map modules = new HashMap(16); + public static Map modules = new HashMap<>(16); /** * Framework version */ @@ -176,16 +186,17 @@ public boolean isProd() { public static String defaultWebEncoding = "utf-8"; /** - * This flag indicates if the app is running in a standalone Play server or - * as a WAR in an applicationServer + * This flag indicates if the app is running in a standalone Play server or as a WAR in an applicationServer */ public static boolean standalonePlayServer = true; /** * Init the framework * - * @param root The application path - * @param id The framework id to use + * @param root + * The application path + * @param id + * The framework id to use */ public static void init(File root, String id) { // Simple things @@ -207,8 +218,8 @@ public static void init(File root, String id) { Logger.init(); String logLevel = configuration.getProperty("application.log", "INFO"); - //only override log-level if Logger was not configured manually - if( !Logger.configuredManually) { + // only override log-level if Logger was not configured manually + if (!Logger.configuredManually) { Logger.setUp(logLevel); } Logger.recordCaller = Boolean.parseBoolean(configuration.getProperty("application.log.recordCaller", "false")); @@ -236,7 +247,7 @@ public static void init(File root, String id) { tmpDir.mkdirs(); } catch (Throwable e) { tmpDir = null; - Logger.warn("No tmp folder will be used (cannot create the tmp dir)"); + Logger.warn("No tmp folder will be used (cannot create the tmp dir), caused by: %s", e); } } } @@ -248,10 +259,10 @@ public static void init(File root, String id) { Logger.error("Illegal mode '%s', use either prod or dev", configuration.getProperty("application.mode")); fatalServerErrorOccurred(); } - + // Force the Production mode if forceProd or precompile is activate // Set to the Prod mode must be done before loadModules call - // as some modules (e.g. DocViewver) is only available in DEV + // as some modules (e.g. DocViewer) is only available in DEV if (usePrecompiled || forceProd || System.getProperty("precompile") != null) { mode = Mode.PROD; } @@ -261,24 +272,24 @@ public static void init(File root, String id) { // Build basic java source path VirtualFile appRoot = VirtualFile.open(applicationPath); + roots.clear(); roots.add(appRoot); - javaPath = new CopyOnWriteArrayList(); + + javaPath.clear(); javaPath.add(appRoot.child("app")); javaPath.add(appRoot.child("conf")); // Build basic templates path + templatesPath.clear(); if (appRoot.child("app/views").exists() || (usePrecompiled && appRoot.child("precompiled/templates/app/views").exists())) { - templatesPath = new ArrayList(2); templatesPath.add(appRoot.child("app/views")); - } else { - templatesPath = new ArrayList(1); } // Main route file routes = appRoot.child("conf/routes"); // Plugin route files - modulesRoutes = new HashMap(16); + modulesRoutes.clear(); // Load modules loadModules(appRoot); @@ -296,7 +307,7 @@ public static void init(File root, String id) { // Default cookie domain Http.Cookie.defaultDomain = configuration.getProperty("application.defaultCookieDomain", null); - if (Http.Cookie.defaultDomain!=null) { + if (Http.Cookie.defaultDomain != null) { Logger.info("Using default cookie domain: " + Http.Cookie.defaultDomain); } @@ -336,7 +347,8 @@ public static void guessFrameworkPath() { } else if (uri.getScheme().equals("file")) { frameworkPath = new File(uri).getParentFile().getParentFile().getParentFile().getParentFile(); } else { - throw new UnexpectedException("Cannot find the Play! framework - trying with uri: " + uri + " scheme " + uri.getScheme()); + throw new UnexpectedException( + "Cannot find the Play! framework - trying with uri: " + uri + " scheme " + uri.getScheme()); } } } catch (Exception e) { @@ -345,45 +357,44 @@ public static void guessFrameworkPath() { } /** - * Read application.conf and resolve overriden key using the play id mechanism. + * Read application.conf and resolve overridden key using the play id mechanism. */ public static void readConfiguration() { - confs = new HashSet(); + confs = new HashSet<>(); configuration = readOneConfigurationFile("application.conf"); extractHttpPort(); // Plugins pluginCollection.onConfigurationRead(); - } + } private static void extractHttpPort() { - final String javaCommand = System.getProperty("sun.java.command", ""); + String javaCommand = System.getProperty("sun.java.command", ""); jregex.Matcher m = new jregex.Pattern(".* --http.port=({port}\\d+)").matcher(javaCommand); if (m.matches()) { configuration.setProperty("http.port", m.group("port")); } } - private static Properties readOneConfigurationFile(String filename) { - Properties propsFromFile=null; + Properties propsFromFile = null; VirtualFile appRoot = VirtualFile.open(applicationPath); - + VirtualFile conf = appRoot.child("conf/" + filename); if (confs.contains(conf)) { throw new RuntimeException("Detected recursive @include usage. Have seen the file " + filename + " before"); } - + try { propsFromFile = IO.readUtf8Properties(conf.inputstream()); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { - Logger.fatal("Cannot read "+filename); + Logger.fatal("Cannot read " + filename); fatalServerErrorOccurred(); } } confs.add(conf); - + // OK, check for instance specifics configuration Properties newConfiguration = new OrderSafeProperties(); Pattern pattern = Pattern.compile("^%([a-zA-Z0-9_\\-]+)\\.(.*)$"); @@ -432,14 +443,14 @@ private static Properties readOneConfigurationFile(String filename) { propsFromFile.setProperty(key.toString(), newValue.toString()); } // Include - Map toInclude = new HashMap(16); + Map toInclude = new HashMap<>(16); for (Object key : propsFromFile.keySet()) { if (key.toString().startsWith("@include.")) { try { String filenameToInclude = propsFromFile.getProperty(key.toString()); - toInclude.putAll( readOneConfigurationFile(filenameToInclude) ); + toInclude.putAll(readOneConfigurationFile(filenameToInclude)); } catch (Exception ex) { - Logger.warn("Missing include: %s", key); + Logger.warn(ex, "Missing include: %s", key); } } } @@ -449,8 +460,7 @@ private static Properties readOneConfigurationFile(String filename) { } /** - * Start the application. - * Recall to restart ! + * Start the application. Recall to restart ! */ public static synchronized void start() { try { @@ -459,17 +469,20 @@ public static synchronized void start() { stop(); } - if( standalonePlayServer) { + if (standalonePlayServer) { // Can only register shutdown-hook if running as standalone server if (!shutdownHookEnabled) { - //registers shutdown hook - Now there's a good chance that we can notify - //our plugins that we're going down when some calls ctrl+c or just kills our process.. + // registers shutdown hook - Now there's a good chance that we can notify + // our plugins that we're going down when some calls ctrl+c or just kills our process.. shutdownHookEnabled = true; - Runtime.getRuntime().addShutdownHook(new Thread() { + Thread hook = new Thread() { + @Override public void run() { Play.stop(); } - }); + }; + hook.setContextClassLoader(ClassLoader.getSystemClassLoader()); + Runtime.getRuntime().addShutdownHook(hook); } } @@ -488,16 +501,16 @@ public void run() { // Configure logs String logLevel = configuration.getProperty("application.log", "INFO"); - //only override log-level if Logger was not configured manually - if( !Logger.configuredManually) { + // only override log-level if Logger was not configured manually + if (!Logger.configuredManually) { Logger.setUp(logLevel); } Logger.recordCaller = Boolean.parseBoolean(configuration.getProperty("application.log.recordCaller", "false")); // Locales - langs = new ArrayList(Arrays.asList(configuration.getProperty("application.langs", "").split(","))); + langs = new ArrayList<>(Arrays.asList(configuration.getProperty("application.langs", "").split(","))); if (langs.size() == 1 && langs.get(0).trim().length() == 0) { - langs = new ArrayList(16); + langs = new ArrayList<>(16); } // Clean templates @@ -511,18 +524,17 @@ public void run() { // Default web encoding String _defaultWebEncoding = configuration.getProperty("application.web_encoding"); - if( _defaultWebEncoding != null ) { + if (_defaultWebEncoding != null) { Logger.info("Using custom default web encoding: " + _defaultWebEncoding); defaultWebEncoding = _defaultWebEncoding; // Must update current response also, since the request/response triggering // this configuration-loading in dev-mode have already been // set up with the previous encoding - if( Http.Response.current() != null ) { + if (Http.Response.current() != null) { Http.Response.current().encoding = _defaultWebEncoding; } } - // Try to load all classes Play.classloader.getAllClasses(); @@ -559,11 +571,17 @@ public void run() { } catch (PlayException e) { started = false; - try { Cache.stop(); } catch (Exception ignored) {} + try { + Cache.stop(); + } catch (Exception ignored) { + } throw e; } catch (Exception e) { started = false; - try { Cache.stop(); } catch (Exception ignored) {} + try { + Cache.stop(); + } catch (Exception ignored) { + } throw new UnexpectedException(e); } } @@ -578,6 +596,7 @@ public static synchronized void stop() { started = false; Cache.stop(); Router.lastLoading = 0L; + Invoker.resetClassloaders(); } } @@ -632,35 +651,36 @@ public static synchronized void detectChanges() { } try { pluginCollection.beforeDetectingChanges(); - if(!pluginCollection.detectClassesChange()) { + if (!pluginCollection.detectClassesChange()) { classloader.detectChanges(); } Router.detectChanges(ctxPath); - for(VirtualFile conf : confs) { - if (conf.lastModified() > startedAt) { - start(); - return; - } - } pluginCollection.detectChange(); if (!Play.started) { - throw new RuntimeException("Not started"); + throw new RestartNeededException("Not started"); } } catch (PlayException e) { throw e; + } catch (RestartNeededException e) { + if (started) { + if (e.getCause() != null && e.getCause() != e) { + Logger.info("Restart: " + e.getMessage() + ", caused by: " + e.getCause()); + } else { + Logger.info("Restart: " + e.getMessage()); + } + } + start(); } catch (Exception e) { - // We have to do a clean refresh + Logger.error(e, "Restart: " + e.getMessage()); start(); } } @SuppressWarnings("unchecked") - public static T plugin(Class clazz) { - return (T)pluginCollection.getPluginInstance((Class)clazz); + public static T plugin(Class clazz) { + return pluginCollection.getPluginInstance(clazz); } - - /** * Allow some code to run very early in Play - Use with caution ! */ @@ -680,7 +700,7 @@ public static void initStaticStuff() { try { Class.forName(line); } catch (Exception e) { - Logger.warn("! Cannot init static: " + line); + Logger.warn(e, "! Cannot init static: " + line); } } } catch (Exception ex) { @@ -690,31 +710,32 @@ public static void initStaticStuff() { } /** - * Load all modules. You can even specify the list using the MODULES - * environment variable. + * Load all modules. You can even specify the list using the MODULES environment variable. */ public static void loadModules() { loadModules(VirtualFile.open(applicationPath)); } /** - * Load all modules. - * You can even specify the list using the MODULES environment variable. - * @param appRoot : the application path virtual file + * Load all modules. You can even specify the list using the MODULES environment variable. + * + * @param appRoot + * the application path virtual file */ public static void loadModules(VirtualFile appRoot) { if (System.getenv("MODULES") != null) { // Modules path is prepended with a env property if (System.getenv("MODULES") != null && System.getenv("MODULES").trim().length() > 0) { - for (String m : System.getenv("MODULES").split(System.getProperty("os.name").startsWith("Windows") ? ";" : ":")) { + + for (String m : System.getenv("MODULES").split(File.pathSeparator)) { File modulePath = new File(m); if (!modulePath.exists() || !modulePath.isDirectory()) { - Logger.error("Module %s will not be loaded because %s does not exist", modulePath.getName(), modulePath.getAbsolutePath()); + Logger.error("Module %s will not be loaded because %s does not exist", modulePath.getName(), + modulePath.getAbsolutePath()); } else { - final String modulePathName = modulePath.getName(); - final String moduleName = modulePathName.contains("-") ? - modulePathName.substring(0, modulePathName.lastIndexOf("-")) : - modulePathName; + String modulePathName = modulePath.getName(); + String moduleName = modulePathName.contains("-") ? modulePathName.substring(0, modulePathName.lastIndexOf("-")) + : modulePathName; addModule(appRoot, moduleName, modulePath); } } @@ -722,78 +743,75 @@ public static void loadModules(VirtualFile appRoot) { } // Load modules from modules/ directory, but get the order from the dependencies.yml file - // .listFiles() returns items in an OS dependant sequence, which is bad - // See #781 - // the yaml parser wants play.version as an environment variable - System.setProperty("play.version", Play.version); - System.setProperty("application.path", applicationPath.getAbsolutePath()); - - File localModules = Play.getFile("modules"); - Set modules = new LinkedHashSet(); - if (localModules != null && localModules.exists() && localModules.isDirectory()) { - try { - File userHome = new File(System.getProperty("user.home")); - DependenciesManager dm = new DependenciesManager(applicationPath, frameworkPath, userHome); - modules = dm.retrieveModules(); - } catch (Exception e) { - Logger.error("There was a problem parsing dependencies.yml (module will not be loaded in order of the dependencies.yml)", e); - // Load module without considering the dependencies.yml order - modules.addAll(Arrays.asList(localModules.list())); - } - - for (Iterator iter = modules.iterator(); iter.hasNext();) { - String moduleName = (String) iter.next(); - - File module = new File(localModules, moduleName); - - if (moduleName.contains("-")) { - moduleName = moduleName.substring(0, moduleName.indexOf("-")); - } - - if(module == null || !module.exists()){ - Logger.error("Module %s will not be loaded because %s does not exist", moduleName, module.getAbsolutePath()); - } else if (module.isDirectory()) { - addModule(appRoot, moduleName, module); - } else { - File modulePath = new File(IO.readContentAsString(module).trim()); - if (!modulePath.exists() || !modulePath.isDirectory()) { - Logger.error("Module %s will not be loaded because %s does not exist", moduleName, modulePath.getAbsolutePath()); - } else { - addModule(appRoot, moduleName, modulePath); - } - } - } - } + // .listFiles() returns items in an OS dependant sequence, which is bad + // See #781 + // the yaml parser wants play.version as an environment variable + System.setProperty("play.version", Play.version); + System.setProperty("application.path", applicationPath.getAbsolutePath()); + + File localModules = Play.getFile("modules"); + Set modules = new LinkedHashSet<>(); + if (localModules != null && localModules.exists() && localModules.isDirectory()) { + try { + File userHome = new File(System.getProperty("user.home")); + DependenciesManager dm = new DependenciesManager(applicationPath, frameworkPath, userHome); + modules = dm.retrieveModules(); + } catch (Exception e) { + Logger.error("There was a problem parsing dependencies.yml (module will not be loaded in order of the dependencies.yml)", + e); + // Load module without considering the dependencies.yml order + modules.addAll(Arrays.asList(localModules.list())); + } + + for (Iterator iter = modules.iterator(); iter.hasNext();) { + String moduleName = iter.next(); + + File module = new File(localModules, moduleName); + + if (moduleName.contains("-")) { + moduleName = moduleName.substring(0, moduleName.indexOf("-")); + } + + if (module == null || !module.exists()) { + Logger.error("Module %s will not be loaded because %s does not exist", moduleName, module.getAbsolutePath()); + } else if (module.isDirectory()) { + addModule(appRoot, moduleName, module); + } else { + File modulePath = new File(IO.readContentAsString(module).trim()); + if (!modulePath.exists() || !modulePath.isDirectory()) { + Logger.error("Module %s will not be loaded because %s does not exist", moduleName, modulePath.getAbsolutePath()); + } else { + addModule(appRoot, moduleName, modulePath); + } + } + } + } // Auto add special modules if (Play.runingInTestMode()) { addModule(appRoot, "_testrunner", new File(Play.frameworkPath, "modules/testrunner")); } - - if (Play.mode == Mode.DEV) { - addModule(appRoot, "_docviewer", new File(Play.frameworkPath, "modules/docviewer")); - } } /** * Add a play application (as plugin) * * @param name - * : the module name + * the module name * @param path * The application path */ public static void addModule(String name, File path) { addModule(VirtualFile.open(applicationPath), name, path); } - + /** * Add a play application (as plugin) * * @param appRoot - * : the application path virtual file + * the application path virtual file * @param name - * : the module name + * the module name * @param path * The application path */ @@ -803,10 +821,12 @@ public static void addModule(VirtualFile appRoot, String name, File path) { if (root.child("app").exists()) { javaPath.add(root.child("app")); } - if (root.child("app/views").exists() || (usePrecompiled && appRoot.child("precompiled/templates/from_module_" + name + "/app/views").exists())) { + if (root.child("app/views").exists() + || (usePrecompiled && appRoot.child("precompiled/templates/from_module_" + name + "/app/views").exists())) { templatesPath.add(root.child("app/views")); } - if (root.child("conf/routes").exists() || (usePrecompiled && appRoot.child("precompiled/templates/from_module_" + name + "/conf/routes").exists())) { + if (root.child("conf/routes").exists() + || (usePrecompiled && appRoot.child("precompiled/templates/from_module_" + name + "/conf/routes").exists())) { modulesRoutes.put(name, root.child("conf/routes")); } roots.add(root); @@ -818,7 +838,8 @@ public static void addModule(VirtualFile appRoot, String name, File path) { /** * Search a VirtualFile in all loaded applications and plugins * - * @param path Relative path from the applications root + * @param path + * Relative path from the applications root * @return The virtualFile or null */ public static VirtualFile getVirtualFile(String path) { @@ -828,7 +849,8 @@ public static VirtualFile getVirtualFile(String path) { /** * Search a File in the current application * - * @param path Relative path from the application root + * @param path + * Relative path from the application root * @return The file even if it doesn't exist */ public static File getFile(String path) { @@ -836,17 +858,15 @@ public static File getFile(String path) { } /** - * Returns true if application is runing in test-mode. - * Test-mode is resolved from the framework id. + * Returns true if application is runing in test-mode. Test-mode is resolved from the framework id. * - * Your app is running in test-mode if the framwork id (Play.id) - * is 'test' or 'test-?.*' + * Your app is running in test-mode if the framwork id (Play.id) is 'test' or 'test-?.*' + * * @return true if testmode */ - public static boolean runingInTestMode(){ + public static boolean runingInTestMode() { return id.matches("test|test-?.*"); } - /** * Call this method when there has been a fatal error that Play cannot recover from diff --git a/framework/src/play/PlayPlugin.java b/framework/src/play/PlayPlugin.java index 41af348850..9781e4f5ec 100644 --- a/framework/src/play/PlayPlugin.java +++ b/framework/src/play/PlayPlugin.java @@ -1,18 +1,21 @@ package play; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; + import java.lang.annotation.Annotation; -import com.google.gson.JsonObject; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.HashMap; + +import com.google.gson.JsonObject; + import play.classloading.ApplicationClasses.ApplicationClass; import play.data.binding.RootParamNode; import play.db.Model; +import play.libs.F; import play.mvc.Http.Request; import play.mvc.Http.Response; import play.mvc.Router.Route; @@ -45,6 +48,10 @@ public boolean compileSources() { /** * Run a test class + * + * @param clazz + * the class to run + * @return : tests results */ public TestResults runTest(Class clazz) { return null; @@ -52,7 +59,20 @@ public TestResults runTest(Class clazz) { /** * Use method using RootParamNode instead - * @return + * + * @param name + * the name of the object + * @param clazz + * the class of the object to bind + * @param type + * type + * @param annotations + * annotation on the object + * @param params + * parameters to bind + * @return binding object + * + * @deprecated use {@link #bind(RootParamNode, String, Class, Type, Annotation[])} */ @Deprecated public Object bind(String name, Class clazz, Type type, Annotation[] annotations, Map params) { @@ -62,16 +82,35 @@ public Object bind(String name, Class clazz, Type type, Annotation[] annotations /** * Called when play need to bind a Java object from HTTP params. * - * When overriding this method, do not call super impl.. super impl is calling old bind method - * to be backward compatible. - */ - public Object bind( RootParamNode rootParamNode, String name, Class clazz, Type type, Annotation[] annotations) { + * When overriding this method, do not call super impl.. super impl is calling old bind method to be backward + * compatible. + * + * @param rootParamNode + * parameters to bind + * @param name + * the name of the object + * @param clazz + * the class of the object to bind + * @param type + * type + * @param annotations + * annotation on the object + * @return binding object + */ + public Object bind(RootParamNode rootParamNode, String name, Class clazz, Type type, Annotation[] annotations) { // call old method to be backward compatible return bind(name, clazz, type, annotations, rootParamNode.originalParams); } /** - * Use bindBean instead + * @deprecated Use bindBean instead + * @param name + * the name of the object + * @param o + * object to bind + * @param params + * parameters to bind + * @return binding object */ @Deprecated public Object bind(String name, Object o, Map params) { @@ -79,23 +118,46 @@ public Object bind(String name, Object o, Map params) { } /** - * Called when play need to bind an existing Java object from HTTP params. - * When overriding this method, DO NOT call the super method, since its default impl is to - * call the old bind method to be backward compatible. + * Called when play need to bind an existing Java object from HTTP params. When overriding this method, DO NOT call + * the super method, since its default impl is to call the old bind method to be backward compatible. + * + * @param rootParamNode + * parameters to bind + * @param name + * the name of the object + * @param bean + * object to bind + * @return binding object */ public Object bindBean(RootParamNode rootParamNode, String name, Object bean) { // call old method to be backward compatible. return bind(name, bean, rootParamNode.originalParams); } + /** + * Unbind an object + * + * @param src + * object to unbind + * @param name + * the name of the object + * @return List of parameters + */ public Map unBind(Object src, String name) { return null; } - + /** - * Translate the given key for the given locale and arguments. - * If null is returned, Play's normal message translation mechanism will be - * used. + * Translate the given key for the given locale and arguments. If null is returned, Play's normal message + * translation mechanism will be used. + * + * @param locale + * the locale we want + * @param key + * the message key + * @param args + * arguments of the messages + * @return the formatted string */ public String getMessage(String locale, Object key, Object... args) { return null; @@ -103,6 +165,8 @@ public String getMessage(String locale, Object key, Object... args) { /** * Return the plugin status + * + * @return the plugin status */ public String getStatus() { return null; @@ -110,6 +174,8 @@ public String getStatus() { /** * Return the plugin status in JSON format + * + * @return the plugin status in JSON format */ public JsonObject getJsonStatus() { return null; @@ -117,15 +183,21 @@ public JsonObject getJsonStatus() { /** * Enhance this class + * * @param applicationClass + * the class to enhance * @throws java.lang.Exception + * if cannot enhance the class */ public void enhance(ApplicationClass applicationClass) throws Exception { } /** * This hook is not plugged, don't implement it + * * @param template + * the template to compile + * @deprecated */ @Deprecated public void onTemplateCompilation(Template template) { @@ -133,9 +205,14 @@ public void onTemplateCompilation(Template template) { /** * Give a chance to this plugin to fully manage this request - * @param request The Play request - * @param response The Play response + * + * @param request + * The Play request + * @param response + * The Play response * @return true if this plugin has managed this request + * @throws java.lang.Exception + * if cannot enhance the class */ public boolean rawInvocation(Request request, Response response) throws Exception { return false; @@ -143,8 +220,13 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio /** * Let a chance to this plugin to manage a static resource - * @param request The Play request - * @param response The Play response + * + * @param file + * The requested file + * @param request + * The Play request + * @param response + * The Play response * @return true if this plugin has managed this request */ public boolean serveStatic(VirtualFile file, Request request, Response response) { @@ -154,28 +236,32 @@ public boolean serveStatic(VirtualFile file, Request request, Response response) public void beforeDetectingChanges() { } + /** + * @param file + * the file of the template to load + * @return the template object + */ public Template loadTemplate(VirtualFile file) { return null; } /** - * It's time for the plugin to detect changes. - * Throw an exception is the application must be reloaded. + * It's time for the plugin to detect changes. Throw an exception is the application must be reloaded. */ public void detectChange() { } /** - * It's time for the plugin to detect changes. - * Throw an exception is the application must be reloaded. + * It's time for the plugin to detect changes. Throw an exception is the application must be reloaded. + * + * @return false si no change detected */ public boolean detectClassesChange() { return false; } /** - * Called at application start (and at each reloading) - * Time to start stateful things. + * Called at application start (and at each reloading) Time to start stateful things. */ public void onApplicationStart() { } @@ -187,52 +273,52 @@ public void afterApplicationStart() { } /** - * Called at application stop (and before each reloading) - * Time to shutdown stateful things. + * Called at application stop (and before each reloading) Time to shutdown stateful things. */ public void onApplicationStop() { } /** - * Called before a Play! invocation. - * Time to prepare request specific things. + * Called before a Play! invocation. Time to prepare request specific things. */ public void beforeInvocation() { } /** - * Called after an invocation. - * (unless an excetion has been thrown). - * Time to close request specific things. + * Called after an invocation. (unless an exception has been thrown). Time to close request specific things. */ public void afterInvocation() { } /** - * Called if an exception occured during the invocation. - * @param e The catched exception. + * Called if an exception occurred during the invocation. + * + * @param e + * The caught exception. */ public void onInvocationException(Throwable e) { } /** - * Called at the end of the invocation. - * (even if an exception occured). - * Time to close request specific things. + * Called at the end of the invocation. (even if an exception occurred). Time to close request specific things. */ public void invocationFinally() { } /** - * Called before an 'action' invocation, - * ie an HTTP request processing. + * Called before an 'action' invocation, ie an HTTP request processing. + * + * @param actionMethod + * name of the method */ public void beforeActionInvocation(Method actionMethod) { } /** * Called when the action method has thrown a result. - * @param result The result object for the request. + * + * @param result + * The result object for the request. */ public void onActionInvocationResult(Result result) { } @@ -242,7 +328,9 @@ public void onInvocationSuccess() { /** * Called when the request has been routed. - * @param route The route selected. + * + * @param route + * The route selected. */ public void onRequestRouting(Route route) { } @@ -253,6 +341,12 @@ public void onRequestRouting(Route route) { public void afterActionInvocation() { } + /** + * Called at the end of the action invocation (either in case of success or any failure). + */ + public void onActionInvocationFinally() { + } + /** * Called when the application.conf has been read. */ @@ -265,34 +359,49 @@ public void onConfigurationRead() { public void onRoutesLoaded() { } - /** + /** * Event may be sent by plugins or other components - * @param message convention: pluginClassShortName.message - * @param context depends on the plugin + * + * @param message + * convention: pluginClassShortName.message + * @param context + * depends on the plugin */ public void onEvent(String message, Object context) { } + /** + * @param modified + * list of modified class + * @return List of class + */ public List onClassesChange(List modified) { - return new ArrayList(); + return emptyList(); } + /** + * @return List of the template extension + */ public List addTemplateExtensions() { - return new ArrayList(); + return emptyList(); } /** - * Override to provide additional mime types from your plugin. These mimetypes get priority over - * the default framework mimetypes but not over the application's configuration. + * Override to provide additional mime types from your plugin. These mimetypes get priority over the default + * framework mimetypes but not over the application's configuration. + * * @return a Map from extensions (without dot) to mimetypes */ public Map addMimeTypes() { - return new HashMap(); + return emptyMap(); } /** - * Let a chance to the plugin to compile it owns classes. - * Must be added to the mutable list. + * Let a chance to the plugin to compile it owns classes. Must be added to the mutable list. + * + * @param classes + * list of class to compile + * @deprecated */ @Deprecated public void compileAll(List classes) { @@ -300,11 +409,18 @@ public void compileAll(List classes) { /** * Let some plugins route themself + * * @param request + * the current request */ public void routeRequest(Request request) { } + /** + * @param modelClass + * class of the model + * @return the Model factory + */ public Model.Factory modelFactory(Class modelClass) { return null; } @@ -314,6 +430,11 @@ public void afterFixtureLoad() { /** * Inter-plugin communication. + * + * @param message + * the message to post + * @param context + * an object */ public static void postEvent(String message, Object context) { Play.pluginCollection.onEvent(message, context); @@ -322,7 +443,7 @@ public static void postEvent(String message, Object context) { public void onApplicationReady() { } - // ~~~~~ + @Override public int compareTo(PlayPlugin o) { int res = index < o.index ? -1 : (index == o.index ? 0 : 1); if (res != 0) { @@ -330,7 +451,7 @@ public int compareTo(PlayPlugin o) { } // index is equal in both plugins. - // Sort on classtype to get consistent order + // Sort on class type to get consistent order res = this.getClass().getName().compareTo(o.getClass().getName()); if (res != 0) { // classnames where different @@ -356,62 +477,127 @@ public Object willBeValidated(Object value) { } /** - * Implement to add some classes that should be considered unit tests but do not extend - * {@link org.junit.Assert} to tests that can be executed by test runner (will be visible in test UI). - *

+ * Implement to add some classes that should be considered unit tests but do not extend {@link org.junit.Assert} to + * tests that can be executed by test runner (will be visible in test UI). + *

* Note:You probably will also need to override {@link PlayPlugin#runTest(java.lang.Class)} method * to handle unsupported tests execution properly. - *

- * Keep in mind that this method can only add tests to currently loaded ones. - * You cannot disable tests this way. You should also make sure you do not duplicate already loaded tests. + *

+ * Keep in mind that this method can only add tests to currently loaded ones. You cannot disable tests this way. You + * should also make sure you do not duplicate already loaded tests. * * @return list of plugin supported unit test classes (empty list in default implementation) */ public Collection getUnitTests() { - return Collections.emptyList(); + return emptyList(); } /** * Implement to add some classes that should be considered functional tests but do not extend * {@link play.test.FunctionalTest} to tests that can be executed by test runner (will be visible in test UI). - *

+ *

* Note:You probably will also need to override {@link PlayPlugin#runTest(java.lang.Class)} method * to handle unsupported tests execution properly. - *

- * Keep in mind that this method can only add tests to currently loaded ones. - * You cannot disable tests this way. You should also make sure you do not duplicate already loaded tests. + *

+ * Keep in mind that this method can only add tests to currently loaded ones. You cannot disable tests this way. You + * should also make sure you do not duplicate already loaded tests. * * @return list of plugin supported functional test classes (empty list in default implementation) */ public Collection getFunctionalTests() { - return Collections.emptyList(); + return emptyList(); } - /** - * Class that define a filter. A filter is a class that wrap a certain behavior around an action. - * You can access your Request and Response object within the filter. See the JPA plugin for an example. - * The JPA plugin wraps a transaction around an action. The filter applies a transaction to the current Action. + /** + * Class that define a filter. A filter is a class that wrap a certain behavior around an action. You can access + * your Request and Response object within the filter. See the JPA plugin for an example. The JPA plugin wraps a + * transaction around an action. The filter applies a transaction to the current Action. */ - public abstract class Filter { + public abstract static class Filter { String name; public Filter(String name) { this.name = name; } - + public abstract T withinFilter(play.libs.F.Function0 fct) throws Throwable; + /** + * Surround innerFilter with this. (innerFilter after this) + * + * @param innerFilter + * filter to be wrapped. + * @return a new Filter object. newFilter.withinFilter(x) is + * outerFilter.withinFilter(innerFilter.withinFilter(x)) + */ + public Filter decorate(final Filter innerFilter) { + final Filter outerFilter = this; + return new Filter(this.name) { + @Override + public T withinFilter(F.Function0 fct) throws Throwable { + return compose(outerFilter.asFunction(), innerFilter.asFunction()).apply(fct); + } + }; + } + + /** + * Compose two second order functions whose input is a zero param function that returns type T... + * + * @param outer + * Function that will wrap inner -- ("outer after inner") + * @param inner + * Function to be wrapped by outer function -- ("outer after inner") + * @return A function that computes outer(inner(x)) on application. + */ + private static Function1, T> compose(final Function1, T> outer, + final Function1, T> inner) { + + return new Function1, T>() { + @Override + public T apply(final F.Function0 arg) throws Throwable { + return outer.apply(new F.Function0() { + @Override + public T apply() throws Throwable { + return inner.apply(arg); + } + }); + } + }; + } + + private final Function1, T> _asFunction = new Function1, T>() { + @Override + public T apply(F.Function0 arg) throws Throwable { + return withinFilter(arg); + } + }; + + public Function1, T> asFunction() { + return _asFunction; + } + public String getName() { return name; } + + // I don't want to add any additional dependencies to the project or use JDK 8 features + // so I'm just rolling my own 1 arg function interface... there must be a better way to do this... + public static interface Function1 { + public O apply(I arg) throws Throwable; + } + } + + public final boolean hasFilter() { + return this.getFilter() != null; } - + /** - * Return the filter implementation for this plugin. - */ + * Return the filter implementation for this plugin. + * + * @return filter object of this plugin + */ public Filter getFilter() { return null; } - } diff --git a/framework/src/play/ant/PlayConfigurationLoadTask.java b/framework/src/play/ant/PlayConfigurationLoadTask.java index db670030a7..be9168da1c 100644 --- a/framework/src/play/ant/PlayConfigurationLoadTask.java +++ b/framework/src/play/ant/PlayConfigurationLoadTask.java @@ -104,8 +104,8 @@ private Map properties() { } BufferedReader reader = null; try { - properties = new HashMap(); - Map idSpecific = new HashMap(); + properties = new HashMap<>(); + Map idSpecific = new HashMap<>(); reader = new BufferedReader(new FileReader(srcFile)); String line; while ((line = reader.readLine()) != null) { @@ -148,7 +148,7 @@ private Map properties() { * and new style, with dependencies starting at 1.2 (load everything from the modules/ dir) */ private Set modules() { - Set modules = new HashSet(); + Set modules = new HashSet<>(); // Old-skool for (Map.Entry entry: properties().entrySet()) { diff --git a/framework/src/play/cache/Cache.java b/framework/src/play/cache/Cache.java index 9273007b90..bbb8a6a4e4 100644 --- a/framework/src/play/cache/Cache.java +++ b/framework/src/play/cache/Cache.java @@ -177,7 +177,7 @@ public static Object get(String key) { /** * Bulk retrieve. * @param key List of keys - * @return Map of keys & values + * @return Map of keys & values */ public static Map get(String... key) { return cacheImpl.get(key); @@ -205,7 +205,9 @@ public static boolean safeDelete(String key) { * Clear all data from cache. */ public static void clear() { - cacheImpl.clear(); + if (cacheImpl != null) { + cacheImpl.clear(); + } } /** @@ -216,7 +218,7 @@ public static void clear() { * @return The element value or null */ @SuppressWarnings("unchecked") - public static T get(String key, Class clazz) { + public static T get(String key, Class clazz) { return (T) cacheImpl.get(key); } diff --git a/framework/src/play/cache/EhCacheImpl.java b/framework/src/play/cache/EhCacheImpl.java index 4fda070e0b..d84a6ab991 100644 --- a/framework/src/play/cache/EhCacheImpl.java +++ b/framework/src/play/cache/EhCacheImpl.java @@ -43,6 +43,7 @@ public static EhCacheImpl newInstance() { return uniqueInstance; } + @Override public void add(String key, Object value, int expiration) { if (cache.get(key) != null) { return; @@ -52,10 +53,12 @@ public void add(String key, Object value, int expiration) { cache.put(element); } + @Override public void clear() { cache.removeAll(); } + @Override public synchronized long decr(String key, int by) { Element e = cache.get(key); if (e == null) { @@ -68,23 +71,27 @@ public synchronized long decr(String key, int by) { return newValue; } + @Override public void delete(String key) { cache.remove(key); } + @Override public Object get(String key) { Element e = cache.get(key); return (e == null) ? null : e.getValue(); } + @Override public Map get(String[] keys) { - Map result = new HashMap(keys.length); + Map result = new HashMap<>(keys.length); for (String key : keys) { result.put(key, get(key)); } return result; } + @Override public synchronized long incr(String key, int by) { Element e = cache.get(key); if (e == null) { @@ -98,6 +105,7 @@ public synchronized long incr(String key, int by) { } + @Override public void replace(String key, Object value, int expiration) { if (cache.get(key) == null) { return; @@ -107,6 +115,7 @@ public void replace(String key, Object value, int expiration) { cache.put(element); } + @Override public boolean safeAdd(String key, Object value, int expiration) { try { add(key, value, expiration); @@ -116,6 +125,7 @@ public boolean safeAdd(String key, Object value, int expiration) { } } + @Override public boolean safeDelete(String key) { try { delete(key); @@ -126,6 +136,7 @@ public boolean safeDelete(String key) { } } + @Override public boolean safeReplace(String key, Object value, int expiration) { try { replace(key, value, expiration); @@ -136,6 +147,7 @@ public boolean safeReplace(String key, Object value, int expiration) { } } + @Override public boolean safeSet(String key, Object value, int expiration) { try { set(key, value, expiration); @@ -146,12 +158,14 @@ public boolean safeSet(String key, Object value, int expiration) { } } + @Override public void set(String key, Object value, int expiration) { Element element = new Element(key, value); element.setTimeToLive(expiration); cache.put(element); } + @Override public void stop() { cacheManager.shutdown(); } diff --git a/framework/src/play/cache/MemcachedImpl.java b/framework/src/play/cache/MemcachedImpl.java index 0a186802ba..59454ad14a 100644 --- a/framework/src/play/cache/MemcachedImpl.java +++ b/framework/src/play/cache/MemcachedImpl.java @@ -128,10 +128,12 @@ public void initClient() throws IOException { } } + @Override public void add(String key, Object value, int expiration) { client.add(key, expiration, value, tc); } + @Override public Object get(String key) { Future future = client.asyncGet(key, tc); try { @@ -142,14 +144,17 @@ public Object get(String key) { return null; } + @Override public void clear() { client.flush(); } + @Override public void delete(String key) { client.delete(key); } + @Override public Map get(String[] keys) { Future> future = client.asyncGetBulk(tc, keys); try { @@ -160,18 +165,22 @@ public Map get(String[] keys) { return Collections.emptyMap(); } + @Override public long incr(String key, int by) { return client.incr(key, by, 0); } + @Override public long decr(String key, int by) { return client.decr(key, by, 0); } + @Override public void replace(String key, Object value, int expiration) { client.replace(key, expiration, value, tc); } + @Override public boolean safeAdd(String key, Object value, int expiration) { Future future = client.add(key, expiration, value, tc); try { @@ -182,6 +191,7 @@ public boolean safeAdd(String key, Object value, int expiration) { return false; } + @Override public boolean safeDelete(String key) { Future future = client.delete(key); try { @@ -192,6 +202,7 @@ public boolean safeDelete(String key) { return false; } + @Override public boolean safeReplace(String key, Object value, int expiration) { Future future = client.replace(key, expiration, value, tc); try { @@ -202,6 +213,7 @@ public boolean safeReplace(String key, Object value, int expiration) { return false; } + @Override public boolean safeSet(String key, Object value, int expiration) { Future future = client.set(key, expiration, value, tc); try { @@ -212,10 +224,12 @@ public boolean safeSet(String key, Object value, int expiration) { return false; } + @Override public void set(String key, Object value, int expiration) { client.set(key, expiration, value, tc); } + @Override public void stop() { client.shutdown(); } diff --git a/framework/src/play/classloading/ApplicationClasses.java b/framework/src/play/classloading/ApplicationClasses.java index 8b61f8e98c..966961243a 100644 --- a/framework/src/play/classloading/ApplicationClasses.java +++ b/framework/src/play/classloading/ApplicationClasses.java @@ -1,14 +1,5 @@ package play.classloading; -import javassist.ClassPool; -import javassist.CtClass; -import play.Logger; -import play.Play; -import play.PlayPlugin; -import play.classloading.enhancers.Enhancer; -import play.exceptions.UnexpectedException; -import play.vfs.VirtualFile; - import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; @@ -18,6 +9,15 @@ import java.util.List; import java.util.Map; +import javassist.ClassPool; +import javassist.CtClass; +import play.Logger; +import play.Play; +import play.PlayPlugin; +import play.classloading.enhancers.Enhancer; +import play.exceptions.UnexpectedException; +import play.vfs.VirtualFile; + /** * Application classes container. */ @@ -30,40 +30,43 @@ public class ApplicationClasses { /** * Cache of all compiled classes */ - Map classes = new HashMap(); + Map classes = new HashMap<>(); /** * Clear the classes cache */ public void clear() { - classes = new HashMap(); + classes = new HashMap<>(); } /** * Get a class by name - * @param name The fully qualified class name + * + * @param name + * The fully qualified class name * @return The ApplicationClass or null */ public ApplicationClass getApplicationClass(String name) { - VirtualFile javaFile = getJava(name); - if(javaFile != null){ - if (!classes.containsKey(name)) { - classes.put(name, new ApplicationClass(name)); + if (!classes.containsKey(name)) { + VirtualFile javaFile = getJava(name); + if (javaFile != null) { + classes.put(name, new ApplicationClass(name, javaFile)); } - return classes.get(name); } - return null; + return classes.get(name); } /** * Retrieve all application classes assignable to this class. - * @param clazz The superclass, or the interface. + * + * @param clazz + * The superclass, or the interface. * @return A list of application classes. */ public List getAssignableClasses(Class clazz) { - List results = new ArrayList(); + List results = new ArrayList<>(); if (clazz != null) { - for (ApplicationClass applicationClass : new ArrayList(classes.values())) { + for (ApplicationClass applicationClass : new ArrayList<>(classes.values())) { if (!applicationClass.isClass()) { continue; } @@ -73,7 +76,8 @@ public List getAssignableClasses(Class clazz) { throw new UnexpectedException(ex); } try { - if (clazz.isAssignableFrom(applicationClass.javaClass) && !applicationClass.javaClass.getName().equals(clazz.getName())) { + if (clazz.isAssignableFrom(applicationClass.javaClass) + && !applicationClass.javaClass.getName().equals(clazz.getName())) { results.add(applicationClass); } } catch (Exception e) { @@ -85,11 +89,13 @@ public List getAssignableClasses(Class clazz) { /** * Retrieve all application classes with a specific annotation. - * @param clazz The annotation class. + * + * @param clazz + * The annotation class. * @return A list of application classes. */ public List getAnnotatedClasses(Class clazz) { - List results = new ArrayList(); + List results = new ArrayList<>(); for (ApplicationClass applicationClass : classes.values()) { if (!applicationClass.isClass()) { continue; @@ -108,14 +114,18 @@ public List getAnnotatedClasses(Class cl /** * All loaded classes. + * * @return All loaded classes */ public List all() { - return new ArrayList(classes.values()); + return new ArrayList<>(classes.values()); } /** * Put a new class to the cache. + * + * @param applicationClass + * The class to add */ public void add(ApplicationClass applicationClass) { classes.put(applicationClass.name, applicationClass); @@ -123,18 +133,30 @@ public void add(ApplicationClass applicationClass) { /** * Remove a class from cache + * + * @param applicationClass + * The class to remove */ public void remove(ApplicationClass applicationClass) { classes.remove(applicationClass.name); } + /** + * Remove a class from cache + * + * @param applicationClass + * The class name to remove + */ public void remove(String applicationClass) { classes.remove(applicationClass); } /** * Does this class is already loaded ? - * @param name The fully qualified class name + * + * @param name + * The fully qualified class name + * @return true if the class is loaded */ public boolean hasClass(String name) { return classes.containsKey(name); @@ -190,15 +212,19 @@ public ApplicationClass() { } public ApplicationClass(String name) { + this(name, getJava(name)); + } + + public ApplicationClass(String name, VirtualFile javaFile) { this.name = name; - this.javaFile = getJava(name); + this.javaFile = javaFile; this.refresh(); } /** * Need to refresh this class ! */ - public void refresh() { + public final void refresh() { if (this.javaFile != null) { this.javaSource = this.javaFile.contentAsString(); } @@ -213,6 +239,7 @@ public void refresh() { /** * Enhance this class + * * @return the enhanced byteCode */ public byte[] enhance() { @@ -223,15 +250,16 @@ public byte[] enhance() { // PlayPlugins can be included as regular java files in a Play-application. // If a PlayPlugin is present in the application, it is loaded when other plugins are loaded. // All plugins must be loaded before we can start enhancing. - // This is a problem when loading PlayPlugins bundled as regular app-class since it uses the same classloader - // as the other (soon to be) enhanched play-app-classes. + // This is a problem when loading PlayPlugins bundled as regular app-class since it uses the same + // classloader + // as the other (soon to be) enhanced play-app-classes. boolean shouldEnhance = true; try { CtClass ctClass = enhanceChecker_classPool.makeClass(new ByteArrayInputStream(this.enhancedByteCode)); if (ctClass.subclassOf(ctPlayPluginClass)) { shouldEnhance = false; } - } catch( Exception e) { + } catch (Exception e) { // nop } @@ -242,17 +270,13 @@ public byte[] enhance() { if (System.getProperty("precompile") != null) { try { // emit bytecode to standard class layout as well - File f = Play.getFile("precompiled/java/" + (name.replace(".", "/")) + ".class"); + File f = Play.getFile("precompiled/java/" + name.replace(".", "/") + ".class"); f.getParentFile().mkdirs(); - FileOutputStream fos = new FileOutputStream(f); - try { + try (FileOutputStream fos = new FileOutputStream(f)) { fos.write(this.enhancedByteCode); } - finally { - fos.close(); - } } catch (Exception e) { - e.printStackTrace(); + Logger.error(e, "Failed to write precompiled class %s to disk", name); } } return this.enhancedByteCode; @@ -261,6 +285,7 @@ public byte[] enhance() { /** * Is this class already compiled but not defined ? + * * @return if the class is compiled but not defined */ public boolean isDefinable() { @@ -271,9 +296,9 @@ public boolean isClass() { return isClass(this.name); } - public static boolean isClass(String name) { + public static boolean isClass(String name) { return !name.endsWith("package-info"); - } + } public String getPackage() { int dot = name.lastIndexOf('.'); @@ -282,11 +307,12 @@ public String getPackage() { /** * Compile the class from Java source + * * @return the bytes that comprise the class file */ public byte[] compile() { long start = System.currentTimeMillis(); - Play.classes.compiler.compile(new String[]{this.name}); + Play.classes.compiler.compile(new String[] { this.name }); if (Logger.isTraceEnabled()) { Logger.trace("%sms to compile class %s", System.currentTimeMillis() - start, name); @@ -304,7 +330,9 @@ public void uncompile() { /** * Call back when a class is compiled. - * @param code The bytecode. + * + * @param code + * The bytecode. */ public void compiled(byte[] code) { javaByteCode = code; @@ -321,9 +349,10 @@ public String toString() { // ~~ Utils /** - * Retrieve the corresponding source file for a given class name. - * It handles innerClass too ! - * @param name The fully qualified class name + * Retrieve the corresponding source file for a given class name. It handles innerClass too ! + * + * @param name + * The fully qualified class name * @return The virtualFile if found */ public static VirtualFile getJava(String name) { @@ -337,7 +366,7 @@ public static VirtualFile getJava(String name) { for (VirtualFile path : Play.javaPath) { // 1. check if there is a folder (without extension) VirtualFile javaFile = path.child(fileOrDir); - + if (javaFile.exists() && javaFile.isDirectory() && javaFile.matchName(fileOrDir)) { // we found a directory (package) return null; diff --git a/framework/src/play/classloading/ApplicationClassloader.java b/framework/src/play/classloading/ApplicationClassloader.java index 40250e1668..baf572ab46 100644 --- a/framework/src/play/classloading/ApplicationClassloader.java +++ b/framework/src/play/classloading/ApplicationClassloader.java @@ -1,16 +1,9 @@ package play.classloading; -import org.apache.commons.io.IOUtils; -import play.Logger; -import play.Play; -import play.cache.Cache; -import play.classloading.ApplicationClasses.ApplicationClass; -import play.classloading.hash.ClassStateHashCreator; -import play.exceptions.UnexpectedException; -import play.libs.IO; -import play.vfs.VirtualFile; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; +import static org.apache.commons.io.IOUtils.closeQuietly; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -23,22 +16,40 @@ import java.security.Permissions; import java.security.ProtectionDomain; import java.security.cert.Certificate; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; -import static org.apache.commons.io.IOUtils.closeQuietly; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; + +import play.Logger; +import play.Play; +import play.cache.Cache; +import play.classloading.ApplicationClasses.ApplicationClass; +import play.classloading.hash.ClassStateHashCreator; +import play.exceptions.RestartNeededException; +import play.exceptions.UnexpectedException; +import play.libs.IO; +import play.vfs.VirtualFile; /** - * The application classLoader. - * Load the classes from the application Java sources files. + * The application classLoader. Load the classes from the application Java sources files. */ public class ApplicationClassloader extends ClassLoader { - private final ClassStateHashCreator classStateHashCreator = new ClassStateHashCreator(); /** - * A representation of the current state of the ApplicationClassloader. - * It gets a new value each time the state of the classloader changes. + * A representation of the current state of the ApplicationClassloader. It gets a new value each time the state of + * the classloader changes. */ public ApplicationClassloaderState currentState = new ApplicationClassloaderState(); @@ -47,7 +58,7 @@ public class ApplicationClassloader extends ClassLoader { */ public ProtectionDomain protectionDomain; - private final Object lock = new Object(); + private final Object lock = new Object(); public ApplicationClassloader() { super(ApplicationClassloader.class.getClassLoader()); @@ -66,19 +77,16 @@ public ApplicationClassloader() { } } - /** - * You know ... - */ @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - // Loook up our cache + // Look up our cache Class c = findLoadedClass(name); if (c != null) { return c; } - synchronized( lock ) { - // First check if it's an application Class + synchronized (lock) { + // First check if it's an application Class Class applicationClass = loadApplicationClass(name); if (applicationClass != null) { if (resolve) { @@ -87,16 +95,15 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE return applicationClass; } } - // Delegate tothe classic classloader + // Delegate to the classic classloader return super.loadClass(name, resolve); } - // ~~~~~~~~~~~~~~~~~~~~~~~ public Class loadApplicationClass(String name) { if (ApplicationClass.isClass(name)) { Class maybeAlreadyLoaded = findLoadedClass(name); - if(maybeAlreadyLoaded != null) { + if (maybeAlreadyLoaded != null) { return maybeAlreadyLoaded; } } @@ -126,7 +133,7 @@ public Class loadApplicationClass(String name) { } return clazz; } catch (Exception e) { - throw new RuntimeException("Cannot find precompiled class file for " + name); + throw new RuntimeException("Cannot find precompiled class file for " + name, e); } } @@ -149,7 +156,8 @@ public Class loadApplicationClass(String name) { } if (bc != null) { applicationClass.enhancedByteCode = bc; - applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain); + applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, + applicationClass.enhancedByteCode.length, protectionDomain); resolveClass(applicationClass.javaClass); if (!applicationClass.isClass()) { applicationClass.javaPackage = applicationClass.javaClass.getPackage(); @@ -163,7 +171,8 @@ public Class loadApplicationClass(String name) { } if (applicationClass.javaByteCode != null || applicationClass.compile() != null) { applicationClass.enhance(); - applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain); + applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, + applicationClass.enhancedByteCode.length, protectionDomain); BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, name, applicationClass.javaSource); resolveClass(applicationClass.javaClass); if (!applicationClass.isClass()) { @@ -206,16 +215,14 @@ private void loadPackage(String className) { /** * Search for the byte code of the given class. */ - protected byte[] getClassDefinition(String name) { + byte[] getClassDefinition(String name) { name = name.replace(".", "/") + ".class"; InputStream is = this.getResourceAsStream(name); if (is == null) { return null; } try { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - IOUtils.copyLarge(is, os); - return os.toByteArray(); + return IOUtils.toByteArray(is); } catch (Exception e) { throw new UnexpectedException(e); } finally { @@ -223,9 +230,6 @@ protected byte[] getClassDefinition(String name) { } } - /** - * You know ... - */ @Override public InputStream getResourceAsStream(String name) { for (VirtualFile vf : Play.javaPath) { @@ -249,30 +253,36 @@ public InputStream getResourceAsStream(String name) { return super.getResourceAsStream(name); } - /** - * You know ... - */ @Override public URL getResource(String name) { - for (VirtualFile vf : Play.javaPath) { - VirtualFile res = vf.child(name); - if (res != null && res.exists()) { - try { + try { + for (VirtualFile vf : Play.javaPath) { + VirtualFile res = vf.child(name); + if (res != null && res.exists()) { return res.getRealFile().toURI().toURL(); - } catch (MalformedURLException ex) { - throw new UnexpectedException(ex); } } + if (Play.usePrecompiled) { + File file = Play.getFile("precompiled/java/" + name); + if (file.exists()) { + return file.toURI().toURL(); + } + } + else if ("true".equals(Play.configuration.getProperty("play.bytecodeCache", "true"))) { + File f = new File(Play.tmpDir, "classes/" + name); + if (f.exists()) { + return f.toURI().toURL(); + } + } + } catch (MalformedURLException ex) { + throw new UnexpectedException(ex); } return super.getResource(name); } - /** - * You know ... - */ @Override public Enumeration getResources(String name) throws IOException { - List urls = new ArrayList(); + List urls = new ArrayList<>(); for (VirtualFile vf : Play.javaPath) { VirtualFile res = vf.child(name); if (res != null && res.exists()) { @@ -293,10 +303,12 @@ public Enumeration getResources(String name) throws IOException { final Iterator it = urls.iterator(); return new Enumeration() { + @Override public boolean hasMoreElements() { return it.hasNext(); } + @Override public URL nextElement() { return it.next(); } @@ -305,27 +317,30 @@ public URL nextElement() { /** * Detect Java changes + * + * @throws play.exceptions.RestartNeededException + * Thrown if the application need to be restarted */ - public void detectChanges() { + public void detectChanges() throws RestartNeededException { // Now check for file modification - List modifieds = new ArrayList(); + List modifieds = new ArrayList<>(); for (ApplicationClass applicationClass : Play.classes.all()) { if (applicationClass.timestamp < applicationClass.javaFile.lastModified()) { applicationClass.refresh(); modifieds.add(applicationClass); } } - Set modifiedWithDependencies = new HashSet(); + Set modifiedWithDependencies = new HashSet<>(); modifiedWithDependencies.addAll(modifieds); - if (modifieds.size() > 0) { + if (!modifieds.isEmpty()) { modifiedWithDependencies.addAll(Play.pluginCollection.onClassesChange(modifieds)); } - List newDefinitions = new ArrayList(); + List newDefinitions = new ArrayList<>(); boolean dirtySig = false; for (ApplicationClass applicationClass : modifiedWithDependencies) { if (applicationClass.compile() == null) { Play.classes.classes.remove(applicationClass.name); - currentState = new ApplicationClassloaderState();//show others that we have changed.. + currentState = new ApplicationClassloaderState();// show others that we have changed.. } else { int sigChecksum = applicationClass.sigChecksum; applicationClass.enhance(); @@ -334,24 +349,25 @@ public void detectChanges() { } BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, applicationClass.name, applicationClass.javaSource); newDefinitions.add(new ClassDefinition(applicationClass.javaClass, applicationClass.enhancedByteCode)); - currentState = new ApplicationClassloaderState();//show others that we have changed.. + currentState = new ApplicationClassloaderState();// show others that we have changed.. } } - if (newDefinitions.size() > 0) { + + if (!newDefinitions.isEmpty()) { Cache.clear(); if (HotswapAgent.enabled) { try { HotswapAgent.reload(newDefinitions.toArray(new ClassDefinition[newDefinitions.size()])); } catch (Throwable e) { - throw new RuntimeException("Need reload"); + throw new RestartNeededException(newDefinitions.size() + " classes changed", e); } } else { - throw new RuntimeException("Need reload"); + throw new RestartNeededException(newDefinitions.size() + " classes changed (and HotSwap is not enabled)"); } } // Check signature (variable name & annotations aware !) if (dirtySig) { - throw new RuntimeException("Signature change !"); + throw new RestartNeededException("Signature change !"); } // Now check if there is new classes or removed classes @@ -361,11 +377,11 @@ public void detectChanges() { for (ApplicationClass applicationClass : Play.classes.all()) { if (!applicationClass.javaFile.exists()) { Play.classes.classes.remove(applicationClass.name); - currentState = new ApplicationClassloaderState();//show others that we have changed.. + currentState = new ApplicationClassloaderState();// show others that we have changed.. } if (applicationClass.name.contains("$")) { Play.classes.classes.remove(applicationClass.name); - currentState = new ApplicationClassloaderState();//show others that we have changed.. + currentState = new ApplicationClassloaderState();// show others that we have changed.. // Ok we have to remove all classes from the same file ... VirtualFile vf = applicationClass.javaFile; for (ApplicationClass ac : Play.classes.all()) { @@ -375,29 +391,31 @@ public void detectChanges() { } } } - throw new RuntimeException("Path has changed"); + throw new RestartNeededException("Path has changed"); } } + /** * Used to track change of the application sources path */ - int pathHash = 0; + private int pathHash = 0; - int computePathHash() { + private int computePathHash() { return classStateHashCreator.computePathHash(Play.javaPath); } /** * Try to load all .java files found. + * * @return The list of well defined Class */ public List getAllClasses() { if (allClasses == null) { - allClasses = new ArrayList(); + List result = new ArrayList<>(); if (Play.usePrecompiled) { - List applicationClasses = new ArrayList(); + List applicationClasses = new ArrayList<>(); scanPrecompiled(applicationClasses, "", Play.getVirtualFile("precompiled/java")); Play.classes.clear(); for (ApplicationClass applicationClass : applicationClasses) { @@ -405,23 +423,22 @@ public List getAllClasses() { Class clazz = loadApplicationClass(applicationClass.name); applicationClass.javaClass = clazz; applicationClass.compiled = true; - allClasses.add(clazz); + result.add(clazz); } } else { if (!Play.pluginCollection.compileSources()) { - List all = new ArrayList(); + List all = new ArrayList<>(); for (VirtualFile virtualFile : Play.javaPath) { all.addAll(getAllClasses(virtualFile)); } - List classNames = new ArrayList(); - for (int i = 0; i < all.size(); i++) { - ApplicationClass applicationClass = all.get(i); + List classNames = new ArrayList<>(); + for (ApplicationClass applicationClass : all) { if (applicationClass != null && !applicationClass.compiled && applicationClass.isClass()) { - classNames.add(all.get(i).name); + classNames.add(applicationClass.name); } } @@ -432,62 +449,95 @@ public List getAllClasses() { for (ApplicationClass applicationClass : Play.classes.all()) { Class clazz = loadApplicationClass(applicationClass.name); if (clazz != null) { - allClasses.add(clazz); + result.add(clazz); } } - Collections.sort(allClasses, new Comparator() { + Collections.sort(result, new Comparator() { + @Override public int compare(Class o1, Class o2) { return o1.getName().compareTo(o2.getName()); } }); } + + Map byNormalizedName = new HashMap<>(result.size()); + for (ApplicationClass clazz : Play.classes.all()) { + byNormalizedName.put(clazz.name.toLowerCase(), clazz); + if (clazz.name.contains("$")) { + byNormalizedName.put(StringUtils.replace(clazz.name.toLowerCase(), "$", "."), clazz); + } + } + + allClassesByNormalizedName = unmodifiableMap(byNormalizedName); + allClasses = unmodifiableList(result); } return allClasses; } - List allClasses = null; + + private List allClasses; + private Map allClassesByNormalizedName; /** * Retrieve all application classes assignable to this class. - * @param clazz The superclass, or the interface. + * + * @param clazz + * The superclass, or the interface. * @return A list of class */ public List getAssignableClasses(Class clazz) { + if (clazz == null) { + return Collections.emptyList(); + } getAllClasses(); - List results = new ArrayList(); - for (ApplicationClass c : Play.classes.getAssignableClasses(clazz)) { - results.add(c.javaClass); + List results = assignableClassesByName.get(clazz.getName()); + if (results != null) { + return results; + } else { + results = new ArrayList<>(); + for (ApplicationClass c : Play.classes.getAssignableClasses(clazz)) { + results.add(c.javaClass); + } + // cache assignable classes + assignableClassesByName.put(clazz.getName(), unmodifiableList(results)); } return results; } + // assignable classes cache + private final Map> assignableClassesByName = new HashMap<>(100); + /** * Find a class in a case insensitive way - * @param name The class name. + * + * @param name + * The class name. * @return a class */ public Class getClassIgnoreCase(String name) { getAllClasses(); - for (ApplicationClass c : Play.classes.all()) { - if (c.name.equalsIgnoreCase(name) || c.name.replace("$", ".").equalsIgnoreCase(name)) { - if (Play.usePrecompiled) { - return c.javaClass; - } - return loadApplicationClass(c.name); + String nameLowerCased = name.toLowerCase(); + ApplicationClass c = allClassesByNormalizedName.get(nameLowerCased); + if (c != null) { + if (Play.usePrecompiled) { + return c.javaClass; } + return loadApplicationClass(c.name); } return null; } /** * Retrieve all application classes with a specific annotation. - * @param clazz The annotation class. + * + * @param clazz + * The annotation class. * @return A list of class */ public List getAnnotatedClasses(Class clazz) { getAllClasses(); - List results = new ArrayList(); + List results = new ArrayList<>(); for (ApplicationClass c : Play.classes.getAnnotatedClasses(clazz)) { results.add(c.javaClass); } @@ -495,38 +545,29 @@ public List getAnnotatedClasses(Class clazz) { } public List getAnnotatedClasses(Class[] clazz) { - List results = new ArrayList(); + List results = new ArrayList<>(); for (Class cl : clazz) { results.addAll(getAnnotatedClasses(cl)); } return results; } - // ~~~ Intern - List getAllClasses(String basePackage) { - List res = new ArrayList(); - for (VirtualFile virtualFile : Play.javaPath) { - res.addAll(getAllClasses(virtualFile, basePackage)); - } - return res; - } - - List getAllClasses(VirtualFile path) { + private List getAllClasses(VirtualFile path) { return getAllClasses(path, ""); } - List getAllClasses(VirtualFile path, String basePackage) { + private List getAllClasses(VirtualFile path, String basePackage) { if (basePackage.length() > 0 && !basePackage.endsWith(".")) { basePackage += "."; } - List res = new ArrayList(); + List res = new ArrayList<>(); for (VirtualFile virtualFile : path.list()) { scan(res, basePackage, virtualFile); } return res; } - void scan(List classes, String packageName, VirtualFile current) { + private void scan(List classes, String packageName, VirtualFile current) { if (!current.isDirectory()) { if (current.getName().endsWith(".java") && !current.getName().startsWith(".")) { String classname = packageName + current.getName().substring(0, current.getName().length() - 5); @@ -539,7 +580,7 @@ void scan(List classes, String packageName, VirtualFile curren } } - void scanPrecompiled(List classes, String packageName, VirtualFile current) { + private void scanPrecompiled(List classes, String packageName, VirtualFile current) { if (!current.isDirectory()) { if (current.getName().endsWith(".class") && !current.getName().startsWith(".")) { String classname = packageName.substring(5) + current.getName().substring(0, current.getName().length() - 6); @@ -551,10 +592,4 @@ void scanPrecompiled(List classes, String packageName, Virtual } } } - - @Override - public String toString() { - return "(play) " + (allClasses == null ? "" : allClasses.toString()); - } - } diff --git a/framework/src/play/classloading/ApplicationCompiler.java b/framework/src/play/classloading/ApplicationCompiler.java index 6b45d119cf..5b0e608f30 100644 --- a/framework/src/play/classloading/ApplicationCompiler.java +++ b/framework/src/play/classloading/ApplicationCompiler.java @@ -6,13 +6,14 @@ import java.util.StringTokenizer; import org.eclipse.jdt.core.compiler.IProblem; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ClassFile; import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.Compiler; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; import org.eclipse.jdt.internal.compiler.ICompilerRequestor; import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; @@ -20,7 +21,6 @@ import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; -import org.eclipse.jdt.internal.compiler.Compiler; import play.Logger; import play.Play; @@ -33,16 +33,19 @@ */ public class ApplicationCompiler { - Map packagesCache = new HashMap(); + Map packagesCache = new HashMap<>(); ApplicationClasses applicationClasses; Map settings; /** * Try to guess the magic configuration options + * + * @param applicationClasses + * The application classes container */ public ApplicationCompiler(ApplicationClasses applicationClasses) { this.applicationClasses = applicationClasses; - this.settings = new HashMap(); + this.settings = new HashMap<>(); this.settings.put(CompilerOptions.OPTION_ReportMissingSerialVersion, CompilerOptions.IGNORE); this.settings.put(CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.GENERATE); this.settings.put(CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.GENERATE); @@ -50,27 +53,22 @@ public ApplicationCompiler(ApplicationClasses applicationClasses) { this.settings.put(CompilerOptions.OPTION_ReportUnusedImport, CompilerOptions.IGNORE); this.settings.put(CompilerOptions.OPTION_Encoding, "UTF-8"); this.settings.put(CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.GENERATE); - String javaVersion = CompilerOptions.VERSION_1_6; - if(System.getProperty("java.version").startsWith("1.6")) { - javaVersion = CompilerOptions.VERSION_1_6; - } else if (System.getProperty("java.version").startsWith("1.7")) { - javaVersion = CompilerOptions.VERSION_1_7; - } else if (System.getProperty("java.version").startsWith("1.8")) { - javaVersion = CompilerOptions.VERSION_1_8; + String javaVersion = CompilerOptions.VERSION_1_8; + if (System.getProperty("java.version").startsWith("1.5") || System.getProperty("java.version").startsWith("1.6") + || System.getProperty("java.version").startsWith("1.7")) { + throw new CompilationException("Java version prior to 1.8 are not supported"); } - if("1.5".equals(Play.configuration.get("java.source"))) { - javaVersion = CompilerOptions.VERSION_1_5; - } else if("1.6".equals(Play.configuration.get("java.source"))) { - javaVersion = CompilerOptions.VERSION_1_6; - } else if("1.7".equals(Play.configuration.get("java.source"))) { - javaVersion = CompilerOptions.VERSION_1_7; - }else if("1.8".equals(Play.configuration.get("java.source"))) { - javaVersion = CompilerOptions.VERSION_1_8; + + if ("1.5".equals(Play.configuration.get("java.source")) || "1.6".equals(Play.configuration.get("java.source")) + || "1.7".equals(Play.configuration.get("java.source"))) { + throw new CompilationException("Java version prior to 1.8 are not supported"); } + this.settings.put(CompilerOptions.OPTION_Source, javaVersion); this.settings.put(CompilerOptions.OPTION_TargetPlatform, javaVersion); this.settings.put(CompilerOptions.OPTION_PreserveUnusedLocal, CompilerOptions.PRESERVE); this.settings.put(CompilerOptions.OPTION_Compliance, javaVersion); + this.settings.put(CompilerOptions.OPTION_MethodParametersAttribute, CompilerOptions.GENERATE); } /** @@ -78,10 +76,10 @@ public ApplicationCompiler(ApplicationClasses applicationClasses) { */ final class CompilationUnit implements ICompilationUnit { - final private String clazzName; - final private String fileName; - final private char[] typeName; - final private char[][] packageName; + private final String clazzName; + private final String fileName; + private final char[] typeName; + private final char[][] packageName; CompilationUnit(String pClazzName) { clazzName = pClazzName; @@ -102,35 +100,40 @@ final class CompilationUnit implements ICompilationUnit { } } + @Override public char[] getFileName() { return fileName.toCharArray(); } + @Override public char[] getContents() { return applicationClasses.getApplicationClass(clazzName).javaSource.toCharArray(); } + @Override public char[] getMainTypeName() { return typeName; } + @Override public char[][] getPackageName() { return packageName; } @Override public boolean ignoreOptionalProblems() { - // TODO Auto-generated method stub return false; } } /** * Please compile this className + * + * @param classNames + * Arrays of the class name to compile */ @SuppressWarnings("deprecation") public void compile(String[] classNames) { - ICompilationUnit[] compilationUnits = new CompilationUnit[classNames.length]; for (int i = 0; i < classNames.length; i++) { compilationUnits[i] = new CompilationUnit(classNames[i]); @@ -143,8 +146,9 @@ public void compile(String[] classNames) { */ INameEnvironment nameEnvironment = new INameEnvironment() { - public NameEnvironmentAnswer findType(final char[][] compoundTypeName) { - final StringBuffer result = new StringBuffer(); + @Override + public NameEnvironmentAnswer findType(char[][] compoundTypeName) { + StringBuilder result = new StringBuilder(compoundTypeName.length * 7); for (int i = 0; i < compoundTypeName.length; i++) { if (i != 0) { result.append('.'); @@ -154,8 +158,9 @@ public NameEnvironmentAnswer findType(final char[][] compoundTypeName) { return findType(result.toString()); } - public NameEnvironmentAnswer findType(final char[] typeName, final char[][] packageName) { - final StringBuffer result = new StringBuffer(); + @Override + public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) { + StringBuilder result = new StringBuilder(packageName.length * 7 + 1 + typeName.length); for (int i = 0; i < packageName.length; i++) { result.append(packageName[i]); result.append('.'); @@ -164,7 +169,7 @@ public NameEnvironmentAnswer findType(final char[] typeName, final char[][] pack return findType(result.toString()); } - private NameEnvironmentAnswer findType(final String name) { + private NameEnvironmentAnswer findType(String name) { try { if (name.startsWith("play.") || name.startsWith("java.") || name.startsWith("javax.")) { @@ -206,22 +211,27 @@ private NameEnvironmentAnswer findType(final String name) { } } + @Override public boolean isPackage(char[][] parentPackageName, char[] packageName) { // Rebuild something usable - StringBuilder sb = new StringBuilder(); - if (parentPackageName != null) { + String name; + if (parentPackageName == null) { + name = new String(packageName); + } else { + StringBuilder sb = new StringBuilder(parentPackageName.length * 7 + packageName.length); for (char[] p : parentPackageName) { - sb.append(new String(p)); + sb.append(p); sb.append("."); } + sb.append(new String(packageName)); + name = sb.toString(); } - sb.append(new String(packageName)); - String name = sb.toString(); + if (packagesCache.containsKey(name)) { - return packagesCache.get(name).booleanValue(); + return packagesCache.get(name); } - // Check if thera a .java or .class for this ressource - if (Play.classloader.getClassDefinition(name) != null) { + // Check if there are .java or .class for this resource + if (Play.classloader.getResource(name.replace('.', '/') + ".class") != null) { packagesCache.put(name, false); return false; } @@ -233,6 +243,7 @@ public boolean isPackage(char[][] parentPackageName, char[] packageName) { return true; } + @Override public void cleanup() { } }; @@ -242,10 +253,11 @@ public void cleanup() { */ ICompilerRequestor compilerRequestor = new ICompilerRequestor() { + @Override public void acceptResult(CompilationResult result) { // If error if (result.hasErrors()) { - for (IProblem problem: result.getErrors()) { + for (IProblem problem : result.getErrors()) { String className = new String(problem.getOriginatingFileName()).replace("/", "."); className = className.substring(0, className.length() - 5); String message = problem.getMessage(); @@ -253,15 +265,16 @@ public void acceptResult(CompilationResult result) { // Non sense ! message = problem.getArguments()[0] + " cannot be resolved"; } - throw new CompilationException(Play.classes.getApplicationClass(className).javaFile, message, problem.getSourceLineNumber(), problem.getSourceStart(), problem.getSourceEnd()); + throw new CompilationException(Play.classes.getApplicationClass(className).javaFile, message, + problem.getSourceLineNumber(), problem.getSourceStart(), problem.getSourceEnd()); } } // Something has been compiled ClassFile[] clazzFiles = result.getClassFiles(); for (int i = 0; i < clazzFiles.length; i++) { - final ClassFile clazzFile = clazzFiles[i]; - final char[][] compoundName = clazzFile.getCompoundName(); - final StringBuffer clazzName = new StringBuffer(); + ClassFile clazzFile = clazzFiles[i]; + char[][] compoundName = clazzFile.getCompoundName(); + StringBuilder clazzName = new StringBuilder(); for (int j = 0; j < compoundName.length; j++) { if (j != 0) { clazzName.append('.'); diff --git a/framework/src/play/classloading/BytecodeCache.java b/framework/src/play/classloading/BytecodeCache.java index 4eee88e326..3e9722be11 100644 --- a/framework/src/play/classloading/BytecodeCache.java +++ b/framework/src/play/classloading/BytecodeCache.java @@ -91,15 +91,11 @@ public static void cacheBytecode(byte[] byteCode, String name, String source) { return; } File f = cacheFile(name.replace("/", "_").replace("{", "_").replace("}", "_").replace(":", "_")); - FileOutputStream fos = new FileOutputStream(f); - try { + try (FileOutputStream fos = new FileOutputStream(f)) { fos.write(hash(source).getBytes("utf-8")); fos.write(0); fos.write(byteCode); } - finally { - fos.close(); - } // emit bytecode to standard class layout as well if (!name.contains("/") && !name.contains("{")) { @@ -122,13 +118,13 @@ public static void cacheBytecode(byte[] byteCode, String name, String source) { */ static String hash(String text) { try { - StringBuffer plugins = new StringBuffer(); + StringBuilder plugins = new StringBuilder(); for(PlayPlugin plugin : Play.pluginCollection.getEnabledPlugins()) { plugins.append(plugin.getClass().getName()); } MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.reset(); - messageDigest.update((Play.version + plugins.toString() + text).getBytes("utf-8")); + messageDigest.update((Play.version + plugins + text).getBytes("utf-8")); byte[] digest = messageDigest.digest(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < digest.length; ++i) { diff --git a/framework/src/play/classloading/enhancers/ContinuationEnhancer.java b/framework/src/play/classloading/enhancers/ContinuationEnhancer.java index d1628e881a..0340b5faaa 100644 --- a/framework/src/play/classloading/enhancers/ContinuationEnhancer.java +++ b/framework/src/play/classloading/enhancers/ContinuationEnhancer.java @@ -15,7 +15,7 @@ public class ContinuationEnhancer extends Enhancer { - static final List continuationMethods = new ArrayList(); + static final List continuationMethods = new ArrayList<>(); static { continuationMethods.add("play.mvc.Controller.await(java.lang.String)"); @@ -60,13 +60,9 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception // we add the interface EnhancedForContinuations to the class CtClass enhancedForContinuationsInterface; try { - InputStream in = getClass().getClassLoader().getResourceAsStream("play/classloading/enhancers/EnhancedForContinuations.class"); - try { + try (InputStream in = getClass().getClassLoader().getResourceAsStream("play/classloading/enhancers/EnhancedForContinuations.class")) { enhancedForContinuationsInterface = classPool.makeClass(in); } - finally { - in.close(); - } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/framework/src/play/classloading/enhancers/ControllersEnhancer.java b/framework/src/play/classloading/enhancers/ControllersEnhancer.java index 18eb20587b..10db39cc83 100644 --- a/framework/src/play/classloading/enhancers/ControllersEnhancer.java +++ b/framework/src/play/classloading/enhancers/ControllersEnhancer.java @@ -24,7 +24,7 @@ */ public class ControllersEnhancer extends Enhancer { - public static ThreadLocal> currentAction = new ThreadLocal>(); + public static final ThreadLocal> currentAction = new ThreadLocal<>(); @Override public void enhanceThisClass(final ApplicationClass applicationClass) throws Exception { @@ -40,7 +40,7 @@ public void enhanceThisClass(final ApplicationClass applicationClass) throws Exc for (final CtMethod ctMethod : ctClass.getDeclaredMethods()) { - // Threaded access + // Threaded access ctMethod.instrument(new ExprEditor() { @Override @@ -194,7 +194,7 @@ public static void initActionCall() { public static void stopActionCall() { allow.set(false); } - static ThreadLocal allow = new ThreadLocal(); + static final ThreadLocal allow = new ThreadLocal<>(); } @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/play/classloading/enhancers/EnhancedForContinuations.java b/framework/src/play/classloading/enhancers/EnhancedForContinuations.java index 03ff6e8930..5c249e8f8d 100644 --- a/framework/src/play/classloading/enhancers/EnhancedForContinuations.java +++ b/framework/src/play/classloading/enhancers/EnhancedForContinuations.java @@ -2,7 +2,7 @@ /** * This interface is added to all classes enhanced for Continuations. - * It's pressense is used to detect if a class is properly enhanched for continuations + * It's presence is used to detect if a class is properly enhanced for continuations */ public interface EnhancedForContinuations { } diff --git a/framework/src/play/classloading/enhancers/Enhancer.java b/framework/src/play/classloading/enhancers/Enhancer.java index f5cedb4e96..d2c3abd69b 100644 --- a/framework/src/play/classloading/enhancers/Enhancer.java +++ b/framework/src/play/classloading/enhancers/Enhancer.java @@ -1,15 +1,16 @@ package play.classloading.enhancers; -import java.io.File; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.FileInputStream; import java.lang.annotation.Annotation; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; + import javassist.ClassPath; import javassist.ClassPool; import javassist.CtClass; @@ -19,8 +20,8 @@ import javassist.NotFoundException; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.annotation.MemberValue; -import play.Play; import play.Logger; +import play.Play; import play.classloading.ApplicationClasses.ApplicationClass; /** @@ -33,7 +34,7 @@ public abstract class Enhancer { public Enhancer() { this.classPool = newClassPool(); } - + public static ClassPool newClassPool() { ClassPool classPool = new ClassPool(); classPool.appendSystemPath(); @@ -44,6 +45,12 @@ public static ClassPool newClassPool() { /** * Construct a javassist CtClass from an application class. + * + * @param applicationClass + * The application class to construct + * @return The javassist CtClass construct from the application class + * @throws IOException + * if problem occurred during construction */ public CtClass makeClass(ApplicationClass applicationClass) throws IOException { return classPool.makeClass(new ByteArrayInputStream(applicationClass.enhancedByteCode)); @@ -51,6 +58,11 @@ public CtClass makeClass(ApplicationClass applicationClass) throws IOException { /** * The magic happen here... + * + * @param applicationClass + * The application class to construct + * @throws Exception + * if problem occurred during construction */ public abstract void enhanceThisClass(ApplicationClass applicationClass) throws Exception; @@ -59,25 +71,27 @@ public CtClass makeClass(ApplicationClass applicationClass) throws IOException { */ public static class ApplicationClassesClasspath implements ClassPath { + @Override public InputStream openClassfile(String className) throws NotFoundException { - if(Play.usePrecompiled) { + if (Play.usePrecompiled) { try { File file = Play.getFile("precompiled/java/" + className.replace(".", "/") + ".class"); return new FileInputStream(file); - } catch(Exception e) { + } catch (Exception e) { Logger.error("Missing class %s", className); } } ApplicationClass appClass = Play.classes.getApplicationClass(className); - if ( appClass.enhancedByteCode == null) { + if (appClass.enhancedByteCode == null) { throw new RuntimeException("Trying to visit uncompiled class while enhancing. Uncompiled class: " + className); } return new ByteArrayInputStream(appClass.enhancedByteCode); } + @Override public URL find(String className) { if (Play.classes.getApplicationClass(className) != null) { String cname = className.replace('.', '/') + ".class"; @@ -90,16 +104,21 @@ public URL find(String className) { return null; } + @Override public void close() { } } /** - * Test if a class has the provided annotation - * @param ctClass the javassist class representation - * @param annotation fully qualified name of the annotation class eg."javax.persistence.Entity" + * Test if a class has the provided annotation + * + * @param ctClass + * the javassist class representation + * @param annotation + * fully qualified name of the annotation class eg."javax.persistence.Entity" * @return true if class has the annotation * @throws java.lang.ClassNotFoundException + * if class not found */ protected boolean hasAnnotation(CtClass ctClass, String annotation) throws ClassNotFoundException { for (Object object : ctClass.getAvailableAnnotations()) { @@ -112,12 +131,16 @@ protected boolean hasAnnotation(CtClass ctClass, String annotation) throws Class } /** - * Test if a field has the provided annotation - * @param ctField the javassist field representation - * @param annotation fully qualified name of the annotation class eg."javax.persistence.Entity" + * Test if a field has the provided annotation + * + * @param ctField + * the javassist field representation + * @param annotation + * fully qualified name of the annotation class eg."javax.persistence.Entity" * @return true if field has the annotation * @throws java.lang.ClassNotFoundException - */ + * if class not found + */ protected boolean hasAnnotation(CtField ctField, String annotation) throws ClassNotFoundException { for (Object object : ctField.getAvailableAnnotations()) { Annotation ann = (Annotation) object; @@ -127,14 +150,18 @@ protected boolean hasAnnotation(CtField ctField, String annotation) throws Class } return false; } - + /** * Test if a method has the provided annotation - * @param ctMethod the javassist method representation - * @param annotation fully qualified name of the annotation class eg."javax.persistence.Entity" - * @return true if field has the annotation - * @throws java.lang.ClassNotFoundException - */ + * + * @param ctMethod + * the javassist method representation + * @param annotation + * fully qualified name of the annotation class eg."javax.persistence.Entity" + * @return true if field has the annotation + * @throws java.lang.ClassNotFoundException + * if class not found + */ protected boolean hasAnnotation(CtMethod ctMethod, String annotation) throws ClassNotFoundException { for (Object object : ctMethod.getAvailableAnnotations()) { Annotation ann = (Annotation) object; @@ -147,9 +174,18 @@ protected boolean hasAnnotation(CtMethod ctMethod, String annotation) throws Cla /** * Create a new annotation to be dynamically inserted in the byte code. + * + * @param attribute + * annotation attribute + * @param annotationType + * Annotation + * @param members + * Member of the annotation */ - protected static void createAnnotation(AnnotationsAttribute attribute, Class annotationType, Map members) { - javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation(annotationType.getName(), attribute.getConstPool()); + protected static void createAnnotation(AnnotationsAttribute attribute, Class annotationType, + Map members) { + javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation(annotationType.getName(), + attribute.getConstPool()); for (Map.Entry member : members.entrySet()) { annotation.addMemberValue(member.getKey(), member.getValue()); } @@ -158,16 +194,26 @@ protected static void createAnnotation(AnnotationsAttribute attribute, Class annotationType) { createAnnotation(attribute, annotationType, new HashMap()); } /** * Retrieve all class annotations. + * + * @param ctClass + * The given class + * @return All class annotations */ protected static AnnotationsAttribute getAnnotations(CtClass ctClass) { - AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctClass.getClassFile().getAttribute(AnnotationsAttribute.visibleTag); + AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctClass.getClassFile() + .getAttribute(AnnotationsAttribute.visibleTag); if (annotationsAttribute == null) { annotationsAttribute = new AnnotationsAttribute(ctClass.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag); ctClass.getClassFile().addAttribute(annotationsAttribute); @@ -177,9 +223,14 @@ protected static AnnotationsAttribute getAnnotations(CtClass ctClass) { /** * Retrieve all field annotations. - */ + * + * @param ctField + * The given field + * @return All field annotations. + */ protected static AnnotationsAttribute getAnnotations(CtField ctField) { - AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctField.getFieldInfo().getAttribute(AnnotationsAttribute.visibleTag); + AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctField.getFieldInfo() + .getAttribute(AnnotationsAttribute.visibleTag); if (annotationsAttribute == null) { annotationsAttribute = new AnnotationsAttribute(ctField.getFieldInfo().getConstPool(), AnnotationsAttribute.visibleTag); ctField.getFieldInfo().addAttribute(annotationsAttribute); @@ -189,9 +240,14 @@ protected static AnnotationsAttribute getAnnotations(CtField ctField) { /** * Retrieve all method annotations. - */ + * + * @param ctMethod + * The given methods + * @return all method annotations. + */ protected static AnnotationsAttribute getAnnotations(CtMethod ctMethod) { - AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctMethod.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag); + AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctMethod.getMethodInfo() + .getAttribute(AnnotationsAttribute.visibleTag); if (annotationsAttribute == null) { annotationsAttribute = new AnnotationsAttribute(ctMethod.getMethodInfo().getConstPool(), AnnotationsAttribute.visibleTag); ctMethod.getMethodInfo().addAttribute(annotationsAttribute); @@ -200,8 +256,8 @@ protected static AnnotationsAttribute getAnnotations(CtMethod ctMethod) { } boolean isScalaObject(CtClass ctClass) throws Exception { - for(CtClass i : ctClass.getInterfaces()) { - if(i.getName().equals("scala.ScalaObject")) { + for (CtClass i : ctClass.getInterfaces()) { + if (i.getName().equals("scala.ScalaObject")) { return true; } } @@ -215,5 +271,4 @@ boolean isScala(ApplicationClass app) { boolean isAnon(ApplicationClass app) { return app.name.contains("$anonfun$") || app.name.contains("$anon$"); } - -} +} \ No newline at end of file diff --git a/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java index 667880ce8b..48d1b88df7 100644 --- a/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java +++ b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java @@ -1,24 +1,14 @@ package play.classloading.enhancers; -import java.lang.reflect.Method; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; -import javassist.ClassPool; import javassist.CtClass; -import javassist.CtConstructor; -import javassist.CtField; import javassist.CtMethod; -import javassist.Modifier; -import javassist.NotFoundException; import javassist.bytecode.Bytecode; import javassist.bytecode.CodeAttribute; import javassist.bytecode.CodeIterator; @@ -26,87 +16,21 @@ import javassist.bytecode.Opcode; import play.Logger; import play.classloading.ApplicationClasses.ApplicationClass; -import play.exceptions.UnexpectedException; -import play.libs.F.T2; /** * Track names of local variables ... */ public class LocalvariablesNamesEnhancer extends Enhancer { - public static List lookupParameterNames(Constructor constructor) { - try { - List parameters = new ArrayList(); - - ClassPool classPool = newClassPool(); - CtClass ctClass = classPool.get(constructor.getDeclaringClass().getName()); - CtClass[] cc = new CtClass[constructor.getParameterTypes().length]; - for (int i = 0; i < constructor.getParameterTypes().length; i++) { - cc[i] = classPool.get(constructor.getParameterTypes()[i].getName()); - } - CtConstructor ctConstructor = ctClass.getDeclaredConstructor(cc); - - // Signatures names - CodeAttribute codeAttribute = (CodeAttribute) ctConstructor.getMethodInfo().getAttribute("Code"); - if (codeAttribute != null) { - LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); - if (localVariableAttribute != null && localVariableAttribute.tableLength() >= ctConstructor.getParameterTypes().length) { - for (int i = 0; i < ctConstructor.getParameterTypes().length + 1; i++) { - String name = localVariableAttribute.getConstPool().getUtf8Info(localVariableAttribute.nameIndex(i)); - if (!name.equals("this")) { - parameters.add(name); - } - } - } - } - - return parameters; - } catch (Exception e) { - throw new UnexpectedException("Cannot extract parameter names", e); - } - } - - public static List lookupParameterNames(Method method) { - try { - List parameters = new ArrayList(); - - ClassPool classPool = newClassPool(); - CtClass ctClass = classPool.get(method.getDeclaringClass().getName()); - CtClass[] cc = new CtClass[method.getParameterTypes().length]; - for (int i = 0; i < method.getParameterTypes().length; i++) { - cc[i] = classPool.get(method.getParameterTypes()[i].getName()); - } - CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName(),cc); - - // Signatures names - CodeAttribute codeAttribute = (CodeAttribute) ctMethod.getMethodInfo().getAttribute("Code"); - if (codeAttribute != null) { - LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); - if (localVariableAttribute != null && localVariableAttribute.tableLength() >= ctMethod.getParameterTypes().length) { - for (int i = 0; i < ctMethod.getParameterTypes().length + 1; i++) { - String name = localVariableAttribute.getConstPool().getUtf8Info(localVariableAttribute.nameIndex(i)); - if (!name.equals("this")) { - parameters.add(name); - } - } - } - } - - return parameters; - } catch (Exception e) { - throw new UnexpectedException("Cannot extract parameter names", e); - } - } - - // @Override public void enhanceThisClass(ApplicationClass applicationClass) throws Exception { - if (isAnon(applicationClass)) { + if (isAnon(applicationClass) || isScala(applicationClass)) { return; } CtClass ctClass = makeClass(applicationClass); - if (!ctClass.subtypeOf(classPool.get(LocalVariablesSupport.class.getName())) && !ctClass.getName().matches("^controllers\\..*\\$class$")) { + if (!ctClass.subtypeOf(classPool.get(LocalVariablesSupport.class.getName())) + && !ctClass.getName().matches("^controllers\\..*\\$class$")) { return; } @@ -122,72 +46,9 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception if (codeAttribute == null || javassist.Modifier.isAbstract(method.getModifiers())) { continue; } + LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); - List> parameterNames = new ArrayList>(); - if (localVariableAttribute == null) { - if(method.getParameterTypes().length > 0) - continue; - } else { - if(localVariableAttribute.tableLength() < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1)) { - Logger.warn("weird: skipping method %s %s as its number of local variables is incorrect (lv=%s || lv.length=%s || params.length=%s || (isStatic? %s)", method.getReturnType().getName(), method.getLongName(), localVariableAttribute, localVariableAttribute != null ? localVariableAttribute.tableLength() : -1, method.getParameterTypes().length, Modifier.isStatic(method.getModifiers())); - } - for(int i=0; i(localVariableAttribute.startPc(i) + localVariableAttribute.index(i), localVariableAttribute.variableName(i))); - } - } - Collections.sort(parameterNames, new Comparator>() { - - public int compare(T2 o1, T2 o2) { - return o1._1.compareTo(o2._1); - } - - }); - } - List names = new ArrayList(); - for (int i = 0; i < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1); i++) { - if (localVariableAttribute == null) { - continue; - } - try { - String name = parameterNames.get(i)._2; - if (!name.equals("this")) { - names.add(name); - } - } catch (Exception e) { - Logger.warn(e, "While applying localvariables to %s.%s, param %s", ctClass.getName(), method.getName(), i); - } - } - StringBuilder iv = new StringBuilder(); - if (names.isEmpty()) { - iv.append("new String[0];"); - } else { - iv.append("new String[] {"); - for (Iterator i = names.iterator(); i.hasNext();) { - iv.append("\""); - String aliasedName = i.next(); - if (aliasedName.contains("$")) { - aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); - } - iv.append(aliasedName); - iv.append("\""); - if (i.hasNext()) { - iv.append(","); - } - } - iv.append("};"); - } - - String sigField = "$" + method.getName() + LocalVariablesNamesTracer.computeMethodHash(method.getParameterTypes()); - try { // #1198 - ctClass.getDeclaredField(sigField); - } catch (NotFoundException nfe) { - CtField signature = CtField.make("public static String[] " + sigField + " = " + iv.toString(), ctClass); - ctClass.addField(signature); - } - - if (localVariableAttribute == null || isScala(applicationClass)) { continue; } @@ -207,18 +68,11 @@ public int compare(T2 o1, T2 o2) { aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); } - - if (name.equals("this")) { + if ("this".equals(name)) { continue; } - /* DEBUG - IO.write(ctClass.toBytecode(), new File("/tmp/lv_"+applicationClass.name+".class")); - ctClass.defrost(); - */ - try { - // The instruction at which this local variable has been created Integer pc = localVariableAttribute.startPc(i); @@ -226,7 +80,7 @@ public int compare(T2 o1, T2 o2) { CodeIterator codeIterator = codeAttribute.iterator(); codeIterator.move(pc); pc = codeIterator.next(); - + Bytecode b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), name, localVariableAttribute.index(i)); codeIterator.insert(pc, b.get()); codeAttribute.setMaxStack(codeAttribute.computeMaxStack()); @@ -250,9 +104,12 @@ public int compare(T2 o1, T2 o2) { // Si c'est un store de la variable en cours d'examination // et que c'est dans la frame d'utilisation de cette variable on trace l'affectation. - // (en fait la frame commence à localVariableAttribute.startPc(i)-1 qui est la première affectation - // mais aussi l'initialisation de la variable qui est deja tracé plus haut, donc on commence à localVariableAttribute.startPc(i)) - if (varNumber == localVariableAttribute.index(i) && index < localVariableAttribute.startPc(i) + localVariableAttribute.codeLength(i)) { + // (en fait la frame commence à localVariableAttribute.startPc(i)-1 qui est la première + // affectation + // mais aussi l'initialisation de la variable qui est deja tracé plus haut, donc on commence à + // localVariableAttribute.startPc(i)) + if (varNumber == localVariableAttribute.index(i) + && index < localVariableAttribute.startPc(i) + localVariableAttribute.codeLength(i)) { b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), aliasedName, varNumber); codeIterator.insertEx(b.get()); codeAttribute.setMaxStack(codeAttribute.computeMaxStack()); @@ -275,37 +132,38 @@ public int compare(T2 o1, T2 o2) { ctClass.defrost(); } - - private static Bytecode makeBytecodeForLVStore(CtMethod method, String sig, String name, int slot) { + + static Bytecode makeBytecodeForLVStore(CtMethod method, String sig, String name, int slot) { Bytecode b = new Bytecode(method.getMethodInfo().getConstPool()); b.addLdc(name); - if("I".equals(sig) || "B".equals(sig) || "C".equals(sig) || "S".equals(sig) || "Z".equals(sig)) + if ("I".equals(sig) || "B".equals(sig) || "C".equals(sig) || "S".equals(sig) || "Z".equals(sig)) b.addIload(slot); - else if("F".equals(sig)) + else if ("F".equals(sig)) b.addFload(slot); - else if("J".equals(sig)) + else if ("J".equals(sig)) b.addLload(slot); - else if("D".equals(sig)) + else if ("D".equals(sig)) b.addDload(slot); else b.addAload(slot); - + String localVarDescriptor = sig; - if(!"B".equals(sig) && !"C".equals(sig) && !"D".equals(sig) && !"F".equals(sig) && - !"I".equals(sig) && !"J".equals(sig) && !"S".equals(sig) && !"Z".equals(sig)) + if (!"B".equals(sig) && !"C".equals(sig) && !"D".equals(sig) && !"F".equals(sig) && !"I".equals(sig) && !"J".equals(sig) + && !"S".equals(sig) && !"Z".equals(sig)) localVarDescriptor = "Ljava/lang/Object;"; Logger.trace("for variable '%s' in slot=%s, sig was '%s' and is now '%s'", name, slot, sig, localVarDescriptor); - b.addInvokestatic("play.classloading.enhancers.LocalvariablesNamesEnhancer$LocalVariablesNamesTracer", "addVariable", "(Ljava/lang/String;"+localVarDescriptor+")V"); - + b.addInvokestatic("play.classloading.enhancers.LocalvariablesNamesEnhancer$LocalVariablesNamesTracer", "addVariable", + "(Ljava/lang/String;" + localVarDescriptor + ")V"); + return b; } /** * Mark class that need local variables tracking */ - public static interface LocalVariablesSupport { + public interface LocalVariablesSupport { } /** @@ -313,53 +171,10 @@ public static interface LocalVariablesSupport { */ public static class LocalVariablesNamesTracer { - public static Integer computeMethodHash(CtClass[] parameters) { - String[] names = new String[parameters.length]; - for (int i = 0; i < parameters.length; i++) { - names[i] = parameters[i].getName(); - } - return computeMethodHash(names); - } - - public static Integer computeMethodHash(Class[] parameters) { - String[] names = new String[parameters.length]; - for (int i = 0; i < parameters.length; i++) { - Class param = parameters[i]; - names[i] = ""; - if (param.isArray()) { - int level = 1; - param = param.getComponentType(); - // Array of array - while (param.isArray()) { - level++; - param = param.getComponentType(); - } - names[i] = param.getName(); - for (int j = 0; j < level; j++) { - names[i] += "[]"; - } - } else { - names[i] = param.getName(); - } - } - return computeMethodHash(names); - } - - public static Integer computeMethodHash(String[] parameters) { - StringBuffer buffer = new StringBuffer(); - for (String param : parameters) { - buffer.append(param); - } - Integer hash = buffer.toString().hashCode(); - if (hash < 0) { - return -hash; - } - return hash; - } - static ThreadLocal>> localVariables = new ThreadLocal>>(); + static final ThreadLocal>> localVariables = new ThreadLocal<>(); public static void checkEmpty() { - if (localVariables.get() != null && localVariables.get().size() != 0) { + if (localVariables.get() != null && !localVariables.get().isEmpty()) { Logger.error("LocalVariablesNamesTracer.checkEmpty, constraint violated (%s)", localVariables.get().size()); } } @@ -388,7 +203,7 @@ public static Map locals() { if (localVariables.get() != null && !localVariables.get().empty()) { return localVariables.get().peek(); } - return new HashMap(); + return new HashMap<>(); } public static void addVariable(String name, Object o) { @@ -432,7 +247,7 @@ public static Map getLocalVariables() { } public static List getAllLocalVariableNames(Object o) { - List allNames = new ArrayList(); + List allNames = new ArrayList<>(); for (String variable : getLocalVariables().keySet()) { if (getLocalVariables().get(variable) == o) { allNames.add(variable); @@ -450,21 +265,23 @@ public static Object getLocalVariable(String variable) { public static Stack> getLocalVariablesStateBeforeAwait() { Stack> state = localVariables.get(); - // must clear the ThreadLocal to prevent destroying the state when exit() is called due to continuations-suspend + // must clear the ThreadLocal to prevent destroying the state when exit() is called due to + // continuations-suspend localVariables.set(new Stack>()); return state; } public static void setLocalVariablesStateAfterAwait(Stack> state) { - if (state==null) { - state = new Stack>(); + if (state == null) { + state = new Stack<>(); } - localVariables.set( state ); + localVariables.set(state); } } - private final static Map storeByCode = new HashMap(); - /** + static final Map storeByCode = new HashMap<>(); + + /* * Useful instructions */ static { @@ -502,11 +319,15 @@ public static void setLocalVariablesStateAfterAwait(Stack> s /** * Debug utility. Display a byte code op as plain text. + * + * @param op + * The given byte code */ public static void printOp(int op) { try { for (Field f : Opcode.class.getDeclaredFields()) { - if (java.lang.reflect.Modifier.isStatic(f.getModifiers()) && java.lang.reflect.Modifier.isPublic(f.getModifiers()) && f.getInt(null) == op) { + if (java.lang.reflect.Modifier.isStatic(f.getModifiers()) && java.lang.reflect.Modifier.isPublic(f.getModifiers()) + && f.getInt(null) == op) { System.out.println(op + " " + f.getName()); } } diff --git a/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java new file mode 100644 index 0000000000..993cc80f89 --- /dev/null +++ b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java @@ -0,0 +1,249 @@ +package play.classloading.enhancers; + +import javassist.*; +import javassist.bytecode.Bytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.LocalVariableAttribute; +import play.Logger; +import play.classloading.ApplicationClasses.ApplicationClass; +import play.exceptions.UnexpectedException; +import play.libs.F.T2; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * Track names of local variables + generate signature fields for Java 7 support + */ +public class LocalvariablesNamesEnhancerJava7 extends LocalvariablesNamesEnhancer { + + @Override + public void enhanceThisClass(ApplicationClass applicationClass) throws Exception { + if (isAnon(applicationClass)) { + return; + } + + CtClass ctClass = makeClass(applicationClass); + if (!ctClass.subtypeOf(classPool.get(LocalVariablesSupport.class.getName())) && !ctClass.getName().matches("^controllers\\..*\\$class$")) { + return; + } + + for (CtMethod method : ctClass.getDeclaredMethods()) { + + if (method.getName().contains("$")) { + // Generated method, skip + continue; + } + + // Signatures names + CodeAttribute codeAttribute = (CodeAttribute) method.getMethodInfo().getAttribute("Code"); + if (codeAttribute == null || Modifier.isAbstract(method.getModifiers())) { + continue; + } + LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); + List> parameterNames = new ArrayList<>(); + + if (localVariableAttribute == null) { + if(method.getParameterTypes().length > 0) + continue; + } else { + if(localVariableAttribute.tableLength() < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1)) { + Logger.warn("weird: skipping method %s %s as its number of local variables is incorrect (lv=%s || lv.length=%s || params.length=%s || (isStatic? %s)", method.getReturnType().getName(), method.getLongName(), localVariableAttribute, localVariableAttribute != null ? localVariableAttribute.tableLength() : -1, method.getParameterTypes().length, Modifier.isStatic(method.getModifiers())); + } + for(int i=0; i(localVariableAttribute.startPc(i) + localVariableAttribute.index(i), localVariableAttribute.variableName(i))); + } + } + Collections.sort(parameterNames, new Comparator>() { + @Override + public int compare(T2 o1, T2 o2) { + return o1._1.compareTo(o2._1); + } + + }); + } + List names = new ArrayList<>(); + for (int i = 0; i < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1); i++) { + if (localVariableAttribute == null) { + continue; + } + try { + String name = parameterNames.get(i)._2; + if (!"this".equals(name)) { + names.add(name); + } + } catch (Exception e) { + Logger.warn(e, "While applying localvariables to %s.%s, param %s", ctClass.getName(), method.getName(), i); + } + } + StringBuilder iv = new StringBuilder(); + if (names.isEmpty()) { + iv.append("new String[0];"); + } else { + iv.append("new String[] {"); + for (Iterator i = names.iterator(); i.hasNext();) { + iv.append("\""); + String aliasedName = i.next(); + if (aliasedName.contains("$")) { + aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); + } + iv.append(aliasedName); + iv.append("\""); + if (i.hasNext()) { + iv.append(","); + } + } + iv.append("};"); + } + + String sigField = "$" + method.getName() + computeMethodHash(method.getParameterTypes()); + try { // #1198 + ctClass.getDeclaredField(sigField); + } catch (NotFoundException nfe) { + CtField signature = CtField.make("public static String[] " + sigField + " = " + iv, ctClass); + ctClass.addField(signature); + } + + if (localVariableAttribute == null || isScala(applicationClass)) { + continue; + } + + // OK. + // Here after each local variable creation instruction, + // we insert a call to play.utils.LocalVariables.addVariable('var', var) + // without breaking everything... + for (int i = 0; i < localVariableAttribute.tableLength(); i++) { + + // name of the local variable + String name = localVariableAttribute.getConstPool().getUtf8Info(localVariableAttribute.nameIndex(i)); + + // Normalize the variable name + // For several reasons, both variables name and name$1 will be aliased to name + String aliasedName = name; + if (aliasedName.contains("$")) { + aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); + } + + + if ("this".equals(name)) { + continue; + } + + /* DEBUG + IO.write(ctClass.toBytecode(), new File("/tmp/lv_"+applicationClass.name+".class")); + ctClass.defrost(); + */ + + try { + + // The instruction at which this local variable has been created + Integer pc = localVariableAttribute.startPc(i); + + // Move to the next instruction (insertionPc) + CodeIterator codeIterator = codeAttribute.iterator(); + codeIterator.move(pc); + pc = codeIterator.next(); + + Bytecode b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), name, localVariableAttribute.index(i)); + codeIterator.insert(pc, b.get()); + codeAttribute.setMaxStack(codeAttribute.computeMaxStack()); + + // Bon chaque instruction de cette méthode + while (codeIterator.hasNext()) { + int index = codeIterator.next(); + int op = codeIterator.byteAt(index); + + // DEBUG + // printOp(op); + + int varNumber = -1; + // The variable changes + if (storeByCode.containsKey(op)) { + varNumber = storeByCode.get(op); + if (varNumber == -2) { + varNumber = codeIterator.byteAt(index + 1); + } + } + + // Si c'est un store de la variable en cours d'examination + // et que c'est dans la frame d'utilisation de cette variable on trace l'affectation. + // (en fait la frame commence à localVariableAttribute.startPc(i)-1 qui est la première affectation + // mais aussi l'initialisation de la variable qui est deja tracé plus haut, donc on commence à localVariableAttribute.startPc(i)) + if (varNumber == localVariableAttribute.index(i) && index < localVariableAttribute.startPc(i) + localVariableAttribute.codeLength(i)) { + b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), aliasedName, varNumber); + codeIterator.insertEx(b.get()); + codeAttribute.setMaxStack(codeAttribute.computeMaxStack()); + } + } + } catch (Exception e) { + // Well probably a compiled optimizer (I hope so) + } + + } + + // init variable tracer + method.insertBefore("play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter();"); + method.insertAfter("play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit();", true); + + } + + // Done. + applicationClass.enhancedByteCode = ctClass.toBytecode(); + ctClass.defrost(); + } + + public static Integer computeMethodHash(CtClass[] parameters) { + String[] names = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + names[i] = parameters[i].getName(); + } + return computeMethodHash(names); + } + + public static Integer computeMethodHash(Class[] parameters) { + String[] names = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + Class param = parameters[i]; + names[i] = ""; + if (param.isArray()) { + int level = 1; + param = param.getComponentType(); + // Array of array + while (param.isArray()) { + level++; + param = param.getComponentType(); + } + names[i] = param.getName(); + for (int j = 0; j < level; j++) { + names[i] += "[]"; + } + } else { + names[i] = param.getName(); + } + } + return computeMethodHash(names); + } + + public static Integer computeMethodHash(String[] parameters) { + StringBuilder buffer = new StringBuilder(); + for (String param : parameters) { + buffer.append(param); + } + Integer hash = buffer.toString().hashCode(); + if (hash < 0) { + return -hash; + } + return hash; + } + + public static String[] parameterNames(Method method) { + try { + return (String[]) method.getDeclaringClass().getDeclaredField("$" + method.getName() + computeMethodHash(method.getParameterTypes())).get(null); + } + catch (Exception e) { + throw new UnexpectedException("Cannot read parameter names for " + method, e); + } + } +} diff --git a/framework/src/play/classloading/enhancers/MailerEnhancer.java b/framework/src/play/classloading/enhancers/MailerEnhancer.java index 10244292cc..49a468cf9e 100644 --- a/framework/src/play/classloading/enhancers/MailerEnhancer.java +++ b/framework/src/play/classloading/enhancers/MailerEnhancer.java @@ -25,11 +25,11 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception return; } - for (final CtMethod ctMethod : ctClass.getDeclaredMethods()) { + for (CtMethod ctMethod : ctClass.getDeclaredMethods()) { if (Modifier.isPublic(ctMethod.getModifiers()) && Modifier.isStatic(ctMethod.getModifiers())) { try { - ctMethod.insertBefore("if(infos.get() != null) {play.Logger.warn(\"You call " + ctMethod.getLongName() + " from \" + ((java.util.Map)infos.get()).get(\"method\") + \". It's forbidden in a Mailer. It will propably fail...\", new Object[0]);}; infos.set(new java.util.HashMap());((java.util.Map)infos.get()).put(\"method\", \"" + ctMethod.getLongName() + "\");"); + ctMethod.insertBefore("if(infos.get() != null) {play.Logger.warn(\"You call " + ctMethod.getLongName() + " from \" + ((java.util.Map)infos.get()).get(\"method\") + \". It's forbidden in a Mailer. It will probably fail...\", new Object[0]);}; infos.set(new java.util.HashMap());((java.util.Map)infos.get()).put(\"method\", \"" + ctMethod.getLongName() + "\");"); ctMethod.insertAfter("infos.set(null);", true); } catch (Exception e) { Logger.error(e, "Error in MailerEnhancer"); diff --git a/framework/src/play/classloading/enhancers/PropertiesEnhancer.java b/framework/src/play/classloading/enhancers/PropertiesEnhancer.java index 8e249dedc0..3b0b8eba48 100644 --- a/framework/src/play/classloading/enhancers/PropertiesEnhancer.java +++ b/framework/src/play/classloading/enhancers/PropertiesEnhancer.java @@ -7,6 +7,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; + import javassist.CannotCompileException; import javassist.CtBehavior; import javassist.CtClass; @@ -23,48 +24,70 @@ import play.exceptions.UnexpectedException; /** - * Generate valid JavaBeans. + * Generate valid JavaBeans. */ public class PropertiesEnhancer extends Enhancer { + private boolean enabled = Boolean.parseBoolean(Play.configuration.getProperty("play.propertiesEnhancer.enabled", "true")); + private boolean generateAccessors = Boolean.parseBoolean(Play.configuration.getProperty("play.propertiesEnhancer.generateAccessors", "true")); + @Override public void enhanceThisClass(ApplicationClass applicationClass) throws Exception { - if(!Boolean.parseBoolean(Play.configuration.getProperty("play.propertiesEnhancer.enabled", "true"))) return; + if (!enabled) return; - final CtClass ctClass = makeClass(applicationClass); - if (ctClass.isInterface()) { + CtClass ctClass = makeClass(applicationClass); + if (ctClass.isInterface() || ctClass.getName().endsWith(".package")) { return; } - if(ctClass.getName().endsWith(".package")) { - return; + + addDefaultConstructor(ctClass); + + if (generateAccessors && !isScala(applicationClass)) { // Temporary hack for Scala: skip generating getters/setters + generateAccessors(ctClass); + addDefaultConstructor2(ctClass); + interceptAllFieldsAccess(ctClass); } - // Add a default constructor if needed + applicationClass.enhancedByteCode = ctClass.toBytecode(); + ctClass.defrost(); + } + + private void addDefaultConstructor(CtClass ctClass) { try { - boolean hasDefaultConstructor = false; - for (CtConstructor constructor : ctClass.getDeclaredConstructors()) { - if (constructor.getParameterTypes().length == 0) { - hasDefaultConstructor = true; - break; - } - } + boolean hasDefaultConstructor = hasDefaultConstructor(ctClass); if (!hasDefaultConstructor && !ctClass.isInterface()) { CtConstructor defaultConstructor = CtNewConstructor.make("public " + ctClass.getSimpleName() + "() {}", ctClass); ctClass.addConstructor(defaultConstructor); } } catch (Exception e) { - Logger.error(e, "Error in PropertiesEnhancer"); - throw new UnexpectedException("Error in PropertiesEnhancer", e); + Logger.error(e, "Failed to generate default constructor for " + ctClass.getName()); + throw new UnexpectedException("Failed to generate default constructor for " + ctClass.getName(), e); } + } - if (isScala(applicationClass)) { - // Temporary hack for Scala. Done. - applicationClass.enhancedByteCode = ctClass.toBytecode(); - ctClass.defrost(); - return; + private void addDefaultConstructor2(CtClass ctClass) { + try { + if (!hasDefaultConstructor(ctClass)) { + CtConstructor defaultConstructor = CtNewConstructor.defaultConstructor(ctClass); + ctClass.addConstructor(defaultConstructor); + } + } catch (Exception e) { + Logger.error(e, "Failed to generate default constructor for " + ctClass.getName()); + throw new UnexpectedException("Failed to generate default constructor for " + ctClass.getName(), e); + } + } + + private boolean hasDefaultConstructor(CtClass ctClass) throws NotFoundException { + for (CtConstructor constructor : ctClass.getDeclaredConstructors()) { + if (constructor.getParameterTypes().length == 0) { + return true; + } } + return false; + } + private void generateAccessors(CtClass ctClass) { for (CtField ctField : ctClass.getDeclaredFields()) { try { @@ -106,31 +129,14 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception } } catch (Exception e) { - Logger.error(e, "Error in PropertiesEnhancer"); - throw new UnexpectedException("Error in PropertiesEnhancer", e); + String message = "Failed to generate default accessor for " + ctClass.getName() + "." + ctField.getName(); + Logger.error(e, message); + throw new UnexpectedException(message, e); } - - } - - // Add a default constructor if needed - try { - boolean hasDefaultConstructor = false; - for (CtConstructor constructor : ctClass.getDeclaredConstructors()) { - if (constructor.getParameterTypes().length == 0) { - hasDefaultConstructor = true; - break; - } - } - if (!hasDefaultConstructor) { - CtConstructor defaultConstructor = CtNewConstructor.defaultConstructor(ctClass); - ctClass.addConstructor(defaultConstructor); - } - } catch (Exception e) { - Logger.error(e, "Error in PropertiesEnhancer"); - throw new UnexpectedException("Error in PropertiesEnhancer", e); } + } - // Intercept all fields access + private void interceptAllFieldsAccess(final CtClass ctClass) throws CannotCompileException { for (final CtBehavior ctMethod : ctClass.getDeclaredBehaviors()) { ctMethod.instrument(new ExprEditor() { @@ -138,16 +144,16 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception public void edit(FieldAccess fieldAccess) throws CannotCompileException { try { - // Check access to porperty ? + // Check access to property ? if (isProperty(fieldAccess.getField())) { // TODO : Check if it is a application class field (fieldAccess.getClassName()) // Getter or setter ? String propertyName = null; - + if (fieldAccess.getField().getDeclaringClass().equals(ctMethod.getDeclaringClass()) - || ctMethod.getDeclaringClass().subclassOf(fieldAccess.getField().getDeclaringClass())) { + || ctMethod.getDeclaringClass().subclassOf(fieldAccess.getField().getDeclaringClass())) { if ((ctMethod.getName().startsWith("get") || (!isFinal(fieldAccess.getField()) && ctMethod.getName().startsWith("set"))) && ctMethod.getName().length() > 3) { propertyName = ctMethod.getName().substring(3); propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); @@ -175,15 +181,12 @@ public void edit(FieldAccess fieldAccess) throws CannotCompileException { } } catch (Exception e) { - throw new UnexpectedException("Error in PropertiesEnhancer", e); + String message = "Failed to modify access to " + ctClass.getName() + "." + ctMethod.getName(); + throw new UnexpectedException(message, e); } } }); } - - // Done. - applicationClass.enhancedByteCode = ctClass.toBytecode(); - ctClass.defrost(); } /** diff --git a/framework/src/play/classloading/enhancers/SigEnhancer.java b/framework/src/play/classloading/enhancers/SigEnhancer.java index ba3a791df7..308674145b 100644 --- a/framework/src/play/classloading/enhancers/SigEnhancer.java +++ b/framework/src/play/classloading/enhancers/SigEnhancer.java @@ -21,30 +21,30 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception return; } - final CtClass ctClass = makeClass(applicationClass); + CtClass ctClass = makeClass(applicationClass); if (isScalaObject(ctClass)) { return; } StringBuilder sigChecksum = new StringBuilder(); - sigChecksum.append("Class->" + ctClass.getName() + ":"); + sigChecksum.append("Class->").append(ctClass.getName()).append(":"); for (Annotation annotation : getAnnotations(ctClass).getAnnotations()) { - sigChecksum.append(annotation + ","); + sigChecksum.append(annotation).append(","); } for (CtField field : ctClass.getDeclaredFields()) { - sigChecksum.append(" Field->" + ctClass.getName() + " " + field.getSignature() + ":"); + sigChecksum.append(" Field->").append(ctClass.getName()).append(" ").append(field.getSignature()).append(":"); sigChecksum.append(field.getSignature()); for (Annotation annotation : getAnnotations(field).getAnnotations()) { - sigChecksum.append(annotation + ","); + sigChecksum.append(annotation).append(","); } } for (CtMethod method : ctClass.getDeclaredMethods()) { - sigChecksum.append(" Method->" + method.getName() + method.getSignature() + ":"); + sigChecksum.append(" Method->").append(method.getName()).append(method.getSignature()).append(":"); for (Annotation annotation : getAnnotations(method).getAnnotations()) { - sigChecksum.append(annotation + " "); + sigChecksum.append(annotation).append(" "); } // Signatures names CodeAttribute codeAttribute = (CodeAttribute) method.getMethodInfo().getAttribute("Code"); @@ -54,7 +54,7 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); if (localVariableAttribute != null) { for (int i = 0; i < localVariableAttribute.tableLength(); i++) { - sigChecksum.append(localVariableAttribute.variableName(i) + ","); + sigChecksum.append(localVariableAttribute.variableName(i)).append(","); } } } @@ -66,8 +66,7 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception int op = i.byteAt(index); sigChecksum.append(op); if (op == Opcode.LDC) { - sigChecksum.append("[" + i.get().getConstPool().getLdcValue(i.byteAt(index + 1)) + "]"); - ; + sigChecksum.append("[").append(i.get().getConstPool().getLdcValue(i.byteAt(index + 1))).append("]"); } sigChecksum.append("."); } @@ -80,8 +79,7 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception int op = i.byteAt(index); sigChecksum.append(op); if (op == Opcode.LDC) { - sigChecksum.append("[" + i.get().getConstPool().getLdcValue(i.byteAt(index + 1)) + "]"); - ; + sigChecksum.append("[").append(i.get().getConstPool().getLdcValue(i.byteAt(index + 1))).append("]"); } sigChecksum.append("."); } diff --git a/framework/src/play/classloading/hash/ClassStateHashCreator.java b/framework/src/play/classloading/hash/ClassStateHashCreator.java index 8c1eb1882c..5ae01fdadb 100644 --- a/framework/src/play/classloading/hash/ClassStateHashCreator.java +++ b/framework/src/play/classloading/hash/ClassStateHashCreator.java @@ -40,10 +40,10 @@ public String getClassDefs() { } } - private final Map classDefsInFileCache = new HashMap(); + private final Map classDefsInFileCache = new HashMap<>(); public synchronized int computePathHash(List paths) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); for (VirtualFile virtualFile : paths) { scan(buf, virtualFile); } @@ -52,13 +52,13 @@ public synchronized int computePathHash(List paths) { return buf.toString().hashCode(); } - private void scan(StringBuffer buf, VirtualFile current) { + private void scan(StringBuilder buf, VirtualFile current) { if (!current.isDirectory()) { if (current.getName().endsWith(".java")) { buf.append( getClassDefsForFile(current)); } } else if (!current.getName().startsWith(".")) { - // TODO: we could later optimizie it further if we check if the entire folder is unchanged + // TODO: we could later optimize it further if we check if the entire folder is unchanged for (VirtualFile virtualFile : current.list()) { scan(buf, virtualFile); } diff --git a/framework/src/play/data/MemoryUpload.java b/framework/src/play/data/MemoryUpload.java index f92214b45b..be13600c9b 100644 --- a/framework/src/play/data/MemoryUpload.java +++ b/framework/src/play/data/MemoryUpload.java @@ -13,34 +13,42 @@ public MemoryUpload(FileItem fileItem) { this.fileItem = fileItem; } + @Override public File asFile() { throw new UnsupportedOperationException(); } - + + @Override public byte[] asBytes() { return fileItem.get(); } + @Override public InputStream asStream() { return new ByteArrayInputStream(fileItem.get()); } + @Override public String getContentType() { return fileItem.getContentType(); } + @Override public String getFileName() { return fileItem.getName(); } + @Override public String getFieldName() { return fileItem.getFieldName(); } + @Override public Long getSize() { return fileItem.getSize(); } - + + @Override public boolean isInMemory() { return fileItem.isInMemory(); } diff --git a/framework/src/play/data/binding/AnnotationHelper.java b/framework/src/play/data/binding/AnnotationHelper.java index 00fe549d89..8b5153ce3f 100644 --- a/framework/src/play/data/binding/AnnotationHelper.java +++ b/framework/src/play/data/binding/AnnotationHelper.java @@ -19,10 +19,12 @@ public class AnnotationHelper { * It can be something like As(lang={"fr,de","*"}, value={"dd-MM-yyyy","MM-dd-yyyy"}) * * @param annotations + * Annotations associated with on the date * @param value + * The formated date * @return null if it cannot be converted because there is no annotation. * @throws ParseException - * + * if problem occurred during parsing the date */ public static Date getDateAs(Annotation[] annotations, String value) throws ParseException { // Look up for the BindAs annotation @@ -35,7 +37,7 @@ public static Date getDateAs(Annotation[] annotations, String value) throws Pars Locale locale = Lang.getLocale(); String format = as.value()[0]; // According to Binder.java line 328 : Fixtures can use (iso) dates as default - if(format != null && format.equals(Fixtures.PROFILE_NAME)){ + if (format != null && format.equals(Fixtures.PROFILE_NAME)) { format = DateBinder.ISO8601; locale = null; } else if (!StringUtils.isEmpty(format)) { @@ -50,12 +52,7 @@ public static Date getDateAs(Annotation[] annotations, String value) throws Pars if (StringUtils.isEmpty(format)) { format = I18N.getDateFormat(); } - SimpleDateFormat sdf = null; - if(locale != null) { - sdf = new SimpleDateFormat(format, locale); - } else { - sdf = new SimpleDateFormat(format); - } + SimpleDateFormat sdf = locale != null ? new SimpleDateFormat(format, locale) : new SimpleDateFormat(format); sdf.setLenient(false); return sdf.parse(value); } @@ -90,7 +87,7 @@ public static Tuple getLocale(String[] langs) { * Contains the index of the locale inside the @As */ private static class Tuple { - + public int index = -1; public Locale locale; diff --git a/framework/src/play/data/binding/As.java b/framework/src/play/data/binding/As.java index 5f72e6bb4f..e0b2dace56 100644 --- a/framework/src/play/data/binding/As.java +++ b/framework/src/play/data/binding/As.java @@ -19,8 +19,9 @@ Class> binder() default DEFAULT.class; Class> unbinder() default DEFAULT.class; - public static final class DEFAULT implements TypeBinder, TypeUnbinder { - public Object bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) throws Exception { + final class DEFAULT implements TypeBinder, TypeUnbinder { + @Override + public Object bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { throw new UnsupportedOperationException("Not supported."); } diff --git a/framework/src/play/data/binding/BeanWrapper.java b/framework/src/play/data/binding/BeanWrapper.java index 7c7867683d..1a1d854749 100644 --- a/framework/src/play/data/binding/BeanWrapper.java +++ b/framework/src/play/data/binding/BeanWrapper.java @@ -13,15 +13,15 @@ */ public class BeanWrapper { - final static int notwritableField = Modifier.FINAL | Modifier.NATIVE | Modifier.STATIC; - final static int notaccessibleMethod = Modifier.NATIVE | Modifier.STATIC; + static final int notwritableField = Modifier.FINAL | Modifier.NATIVE | Modifier.STATIC; + static final int notaccessibleMethod = Modifier.NATIVE | Modifier.STATIC; private Class beanClass; /** * a cache for our properties and setters */ - private Map wrappers = new HashMap(); + private Map wrappers = new HashMap<>(); public BeanWrapper(Class forClass) { if (Logger.isTraceEnabled()) { @@ -71,7 +71,7 @@ private boolean isScalaSetter(Method method) { } protected Object newBeanInstance() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - Constructor constructor = beanClass.getDeclaredConstructor(); + Constructor constructor = beanClass.getDeclaredConstructor(); constructor.setAccessible(true); return constructor.newInstance(); } @@ -224,8 +224,6 @@ Annotation[] getAnnotations() { public String toString() { return type + "." + name; } - - } public Object bind(String name, Type type, Map params, String prefix, Annotation[] annotations) throws Exception { diff --git a/framework/src/play/data/binding/Binder.java b/framework/src/play/data/binding/Binder.java index 1e6fd64058..124c90f160 100644 --- a/framework/src/play/data/binding/Binder.java +++ b/framework/src/play/data/binding/Binder.java @@ -1,34 +1,61 @@ package play.data.binding; -import org.apache.commons.lang.StringUtils; -import org.joda.time.DateTime; -import play.Logger; -import play.Play; -import play.data.Upload; -import play.data.binding.types.*; -import play.data.validation.Validation; -import play.db.Model; -import play.exceptions.UnexpectedException; - import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; -import java.util.*; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.DateTime; +import play.Logger; +import play.Play; +import play.data.Upload; +import play.data.binding.types.BinaryBinder; +import play.data.binding.types.ByteArrayArrayBinder; +import play.data.binding.types.ByteArrayBinder; +import play.data.binding.types.CalendarBinder; +import play.data.binding.types.DateBinder; +import play.data.binding.types.DateTimeBinder; +import play.data.binding.types.FileArrayBinder; +import play.data.binding.types.FileBinder; +import play.data.binding.types.LocaleBinder; +import play.data.binding.types.UploadArrayBinder; +import play.data.binding.types.UploadBinder; +import play.data.validation.Validation; +import play.db.Model; +import play.exceptions.UnexpectedException; /** * The binder try to convert String values to Java objects. */ public abstract class Binder { - public final static Object MISSING = new Object(); - private final static Object DIRECTBINDING_NO_RESULT = new Object(); - public final static Object NO_BINDING = new Object(); + public static final Object MISSING = new Object(); + private static final Object DIRECTBINDING_NO_RESULT = new Object(); + public static final Object NO_BINDING = new Object(); - static final Map, TypeBinder> supportedTypes = new HashMap, TypeBinder>(); + static final Map, TypeBinder> supportedTypes = new HashMap<>(); // TODO: something a bit more dynamic? The As annotation allows you to inject your own binder static { @@ -45,12 +72,39 @@ public abstract class Binder { supportedTypes.put(byte[][].class, new ByteArrayArrayBinder()); } - + /** + * Add custom binder for any given class + * + * E.g. @{code Binder.register(BigDecimal.class, new MyBigDecimalBinder());} + * + * NB! Do not forget to UNREGISTER your custom binder when applications is reloaded (most probably in method + * onApplicationStop()). Otherwise you will have a memory leak. + * + * @param clazz + * The class to register + * @param typeBinder + * The custom binder + * @param + * The Class type to register + * @see #unregister(java.lang.Class) + */ public static void register(Class clazz, TypeBinder typeBinder) { supportedTypes.put(clazz, typeBinder); } - static Map, BeanWrapper> beanwrappers = new HashMap, BeanWrapper>(); + /** + * Remove custom binder that was add with method #register(java.lang.Class, play.data.binding.TypeBinder) + * + * @param clazz + * The class to remove the custom binder + * @param + * The Class type to register + */ + public static void unregister(Class clazz) { + supportedTypes.remove(clazz); + } + + static Map, BeanWrapper> beanwrappers = new HashMap<>(); static BeanWrapper getBeanWrapper(Class clazz) { if (!beanwrappers.containsKey(clazz)) { @@ -74,6 +128,14 @@ public MethodAndParamInfo(Object objectInstance, Method method, int parameterInd /** * Deprecated. Use bindBean() instead. + * + * @param o + * Object to bind + * @param name + * Name of the object + * @param params + * List of the parameters + * @return : The binding object */ @Deprecated public static Object bind(Object o, String name, Map params) { @@ -92,15 +154,16 @@ public static Object bind(RootParamNode parentParamNode, String name, Class c return bind(parentParamNode, name, clazz, type, annotations, null); } - public static Object bind(RootParamNode parentParamNode, String name, Class clazz, Type type, Annotation[] annotations, MethodAndParamInfo methodAndParamInfo) { - final ParamNode paramNode = parentParamNode.getChild(name, true); + public static Object bind(RootParamNode parentParamNode, String name, Class clazz, Type type, Annotation[] annotations, + MethodAndParamInfo methodAndParamInfo) { + ParamNode paramNode = parentParamNode.getChild(name, true); Object result = null; if (paramNode == null) { result = MISSING; } - final BindingAnnotations bindingAnnotations = new BindingAnnotations(annotations); + BindingAnnotations bindingAnnotations = new BindingAnnotations(annotations); if (bindingAnnotations.checkNoBinding()) { return NO_BINDING; @@ -122,11 +185,12 @@ public static Object bind(RootParamNode parentParamNode, String name, Class c if (methodAndParamInfo != null) { try { Method method = methodAndParamInfo.method; - Method defaultMethod = method.getDeclaringClass().getDeclaredMethod(method.getName() + "$default$" + methodAndParamInfo.parameterIndex); + Method defaultMethod = method.getDeclaringClass() + .getDeclaredMethod(method.getName() + "$default$" + methodAndParamInfo.parameterIndex); return defaultMethod.invoke(methodAndParamInfo.objectInstance); - } catch (NoSuchMethodException e) { - // + } catch (NoSuchMethodException ignore) { } catch (Exception e) { + logBindingNormalFailure(paramNode, e); throw new UnexpectedException(e); } } @@ -159,7 +223,6 @@ public static Object bind(RootParamNode parentParamNode, String name, Class c } - protected static Object internalBind(ParamNode paramNode, Class clazz, Type type, BindingAnnotations bindingAnnotations) { if (paramNode == null) { @@ -181,36 +244,55 @@ protected static Object internalBind(ParamNode paramNode, Class clazz, Type t } if (Map.class.isAssignableFrom(clazz)) { - return bindMap(clazz, type, paramNode, bindingAnnotations); + return bindMap(type, paramNode, bindingAnnotations); } if (Collection.class.isAssignableFrom(clazz)) { return bindCollection(clazz, type, paramNode, bindingAnnotations); } - Object directBindResult = internalDirectBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, paramNode.getFirstValue(clazz), clazz, type); - + Object directBindResult = internalDirectBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, + paramNode.getFirstValue(clazz), clazz, type); + if (directBindResult != DIRECTBINDING_NO_RESULT) { // we found a value/result when direct binding return directBindResult; } - // Must do the default array-check after direct binding, since some custom-binders checks for specific arrays + // Must do the default array-check after direct binding, since some custom-binders checks for specific + // arrays if (clazz.isArray()) { return bindArray(clazz, paramNode, bindingAnnotations); } - - if (!paramNode.getAllChildren().isEmpty()) { - return internalBindBean(clazz, paramNode, bindingAnnotations); - } + + if (!paramNode.getAllChildren().isEmpty()) { + return internalBindBean(clazz, paramNode, bindingAnnotations); + } return null; // give up + } catch (NumberFormatException | ParseException e) { + logBindingNormalFailure(paramNode, e); + addValidationError(paramNode); } catch (Exception e) { - Validation.addError(paramNode.getOriginalKey(), "validation.invalid"); + // TODO This is bad catch. I would like to remove it in next version. + logBindingUnexpectedFailure(paramNode, e); + addValidationError(paramNode); } return MISSING; } + private static void addValidationError(ParamNode paramNode) { + Validation.addError(paramNode.getOriginalKey(), "validation.invalid"); + } + + private static void logBindingUnexpectedFailure(ParamNode paramNode, Exception e) { + Logger.error(e, "Failed to bind %s=%s", paramNode.getOriginalKey(), Arrays.toString(paramNode.getValues())); + } + + private static void logBindingNormalFailure(ParamNode paramNode, Exception e) { + Logger.debug("Failed to bind %s=%s: %s", paramNode.getOriginalKey(), Arrays.toString(paramNode.getValues()), e); + } + private static Object bindArray(Class clazz, ParamNode paramNode, BindingAnnotations bindingAnnotations) { Class componentType = clazz.getComponentType(); @@ -225,7 +307,7 @@ private static Object bindArray(Class clazz, ParamNode paramNode, BindingAnno for (Annotation annotation : bindingAnnotations.annotations) { if (annotation.annotationType().equals(As.class)) { As as = ((As) annotation); - final String separator = as.value()[0]; + String separator = as.value()[0]; values = values[0].split(separator); } } @@ -236,9 +318,10 @@ private static Object bindArray(Class clazz, ParamNode paramNode, BindingAnno for (int i = 0; i < size; i++) { String thisValue = values[i]; try { - Array.set(array, i - invalidItemsCount, directBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, thisValue, componentType, componentType)); + Array.set(array, i - invalidItemsCount, directBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, + thisValue, componentType, componentType)); } catch (Exception e) { - // bad item.. + Logger.debug("Bad item #%s: %s", i, e); invalidItemsCount++; } } @@ -252,7 +335,7 @@ private static Object bindArray(Class clazz, ParamNode paramNode, BindingAnno try { Array.set(array, i - invalidItemsCount, childValue); } catch (Exception e) { - // bad item.. + Logger.debug("Bad item #%s: %s", i, e); invalidItemsCount++; } } @@ -273,14 +356,35 @@ private static Object bindArray(Class clazz, ParamNode paramNode, BindingAnno return array; } - private static Object internalBindBean(Class clazz, ParamNode paramNode, BindingAnnotations bindingAnnotations) throws Exception { - Object bean = clazz.newInstance(); + private static Object internalBindBean(Class clazz, ParamNode paramNode, BindingAnnotations bindingAnnotations) { + Object bean = createNewInstance(clazz); internalBindBean(paramNode, bean, bindingAnnotations); return bean; } + private static T createNewInstance(Class clazz) { + try { + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + Logger.warn("Failed to create instance of %s: %s", clazz.getName(), e); + throw new UnexpectedException(e); + } catch (NoSuchMethodException | InvocationTargetException e) { + Logger.error("Failed to create instance of %s: %s", clazz.getName(), e); + throw new UnexpectedException(e); + } + } + /** * Invokes the plugins before using the internal bindBean. + * + * @param rootParamNode + * List of parameters + * @param name + * The object name + * @param bean + * the bean object */ public static void bindBean(RootParamNode rootParamNode, String name, Object bean) { @@ -294,20 +398,28 @@ public static void bindBean(RootParamNode rootParamNode, String name, Object bea try { internalBindBean(paramNode, bean, new BindingAnnotations()); - } catch (Exception e) { - Validation.addError(paramNode.getOriginalKey(), "validation.invalid"); + } catch (NumberFormatException e) { + logBindingNormalFailure(paramNode, e); + addValidationError(paramNode); } } /** * Does NOT invoke plugins + * + * @param paramNode + * List of parameters + * @param bean + * the bean object + * @param annotations + * annotations associated with the object */ - public static void bindBean(ParamNode paramNode, Object bean, Annotation[] annotations) throws Exception { + public static void bindBean(ParamNode paramNode, Object bean, Annotation[] annotations) { internalBindBean(paramNode, bean, new BindingAnnotations(annotations)); } - private static void internalBindBean(ParamNode paramNode, Object bean, BindingAnnotations bindingAnnotations) throws Exception { + private static void internalBindBean(ParamNode paramNode, Object bean, BindingAnnotations bindingAnnotations) { BeanWrapper bw = getBeanWrapper(bean.getClass()); for (BeanWrapper.Property prop : bw.getWrappers()) { @@ -336,7 +448,7 @@ private static void internalBindBean(ParamNode paramNode, Object bean, BindingAn } @SuppressWarnings("unchecked") - private static Object bindEnum(Class clazz, ParamNode paramNode) throws Exception { + private static Object bindEnum(Class clazz, ParamNode paramNode) { if (paramNode.getValues() == null) { return MISSING; } @@ -349,7 +461,7 @@ private static Object bindEnum(Class clazz, ParamNode paramNode) throws Excep return Enum.valueOf((Class) clazz, value); } - private static Object bindMap(Class clazz, Type type, ParamNode paramNode, BindingAnnotations bindingAnnotations) throws Exception { + private static Object bindMap(Type type, ParamNode paramNode, BindingAnnotations bindingAnnotations) { Class keyClass = String.class; Class valueClass = String.class; if (type instanceof ParameterizedType) { @@ -357,18 +469,23 @@ private static Object bindMap(Class clazz, Type type, ParamNode paramNode, Bi valueClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; } - Map r = new HashMap(); + Map r = new HashMap<>(); for (ParamNode child : paramNode.getAllChildren()) { try { - Object keyObject = directBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, child.getName(), keyClass, keyClass); + Object keyObject = directBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, child.getName(), keyClass, + keyClass); Object valueObject = internalBind(child, valueClass, valueClass, bindingAnnotations); if (valueObject == NO_BINDING || valueObject == MISSING) { valueObject = null; } r.put(keyObject, valueObject); - } catch (Exception e) { + } catch (ParseException | NumberFormatException e) { // Just ignore the exception and continue on the next item + logBindingNormalFailure(paramNode, e); + } catch (Exception e) { + // TODO This is bad catch. I would like to remove it in next version. + logBindingUnexpectedFailure(paramNode, e); } } @@ -376,7 +493,7 @@ private static Object bindMap(Class clazz, Type type, ParamNode paramNode, Bi } @SuppressWarnings("unchecked") - private static Object bindCollection(Class clazz, Type type, ParamNode paramNode, BindingAnnotations bindingAnnotations) throws Exception { + private static Object bindCollection(Class clazz, Type type, ParamNode paramNode, BindingAnnotations bindingAnnotations) { if (clazz.isInterface()) { if (clazz.equals(List.class)) { clazz = ArrayList.class; @@ -393,7 +510,7 @@ private static Object bindCollection(Class clazz, Type type, ParamNode paramN Type componentType = String.class; if (type instanceof ParameterizedType) { componentType = ((ParameterizedType) type).getActualTypeArguments()[0]; - if(componentType instanceof ParameterizedType) { + if (componentType instanceof ParameterizedType) { componentClass = (Class) ((ParameterizedType) componentType).getRawType(); } else { componentClass = (Class) componentType; @@ -412,42 +529,50 @@ private static Object bindCollection(Class clazz, Type type, ParamNode paramN for (Annotation annotation : bindingAnnotations.annotations) { if (annotation.annotationType().equals(As.class)) { As as = ((As) annotation); - final String separator = as.value()[0]; - if (separator != null && !separator.isEmpty()){ - values = values[0].split(separator); + String separator = as.value()[0]; + if (separator != null && !separator.isEmpty()) { + values = values[0].split(separator); } } } } - Collection l = (Collection) clazz.newInstance(); + Collection l; + if (clazz.equals(EnumSet.class)) { + l = EnumSet.noneOf(componentClass); + } else { + l = (Collection) createNewInstance(clazz); + } boolean hasMissing = false; for (int i = 0; i < values.length; i++) { try { - Object value = internalDirectBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, values[i], componentClass, componentType); - if ( value == DIRECTBINDING_NO_RESULT) { - hasMissing = true; - } else { + Object value = internalDirectBind(paramNode.getOriginalKey(), bindingAnnotations.annotations, values[i], componentClass, + componentType); + if (value == DIRECTBINDING_NO_RESULT) { + hasMissing = true; + } else { l.add(value); } } catch (Exception e) { // Just ignore the exception and continue on the next item + logBindingNormalFailure(paramNode, e); // TODO debug or error? } } - if(hasMissing && l.size() == 0){ + if (hasMissing && l.size() == 0) { return MISSING; } - return l; + return l; } - Collection r = (Collection) clazz.newInstance(); + Collection r = (Collection) createNewInstance(clazz); if (List.class.isAssignableFrom(clazz)) { // Must add items at position resolved from each child's key List l = (List) r; // must get all indexes and sort them so we add items in correct order. - Set indexes = new TreeSet(new Comparator() { + Set indexes = new TreeSet<>(new Comparator() { + @Override public int compare(String arg0, String arg1) { try { return Integer.parseInt(arg0) - Integer.parseInt(arg1); @@ -493,34 +618,54 @@ public int compare(String arg0, String arg1) { } /** + * Bind a object + * * @param value + * value to bind * @param clazz + * class of the object * @return The binding object * @throws Exception + * if problem occurred during binding */ public static Object directBind(String value, Class clazz) throws Exception { return directBind(null, value, clazz, null); } /** + * Bind a object + * * @param name + * name of the object * @param annotations + * annotation on the object * @param value + * Value to bind * @param clazz + * The class of the object + * * @return The binding object * @throws Exception + * if problem occurred during binding */ public static Object directBind(String name, Annotation[] annotations, String value, Class clazz) throws Exception { return directBind(name, annotations, value, clazz, null); } /** + * Bind a object + * * @param annotations + * annotation on the object * @param value + * value to bind * @param clazz + * class of the object * @param type + * type to bind * @return The binding object * @throws Exception + * if problem occurred during binding */ public static Object directBind(Annotation[] annotations, String value, Class clazz, Type type) throws Exception { return directBind(null, annotations, value, clazz, type); @@ -528,19 +673,25 @@ public static Object directBind(Annotation[] annotations, String value, Class /** * This method calls the user's defined binders prior to bind simple type - * + * * @param name + * name of the object * @param annotations + * annotation on the object * @param value + * value to bind * @param clazz + * class of the object * @param type + * type to bind * @return The binding object * @throws Exception + * if problem occurred during binding */ public static Object directBind(String name, Annotation[] annotations, String value, Class clazz, Type type) throws Exception { // calls the direct binding and returns null if no value could be resolved.. Object r = internalDirectBind(name, annotations, value, clazz, type); - if ( r == DIRECTBINDING_NO_RESULT) { + if (r == DIRECTBINDING_NO_RESULT) { return null; } else { return r; @@ -549,16 +700,17 @@ public static Object directBind(String name, Annotation[] annotations, String va // If internalDirectBind was not able to bind it, it returns a special variable instance: DIRECTBIND_MISSING // Needs this because sometimes we need to know if no value was returned.. - private static Object internalDirectBind(String name, Annotation[] annotations, String value, Class clazz, Type type) throws Exception { + private static Object internalDirectBind(String name, Annotation[] annotations, String value, Class clazz, Type type) + throws Exception { boolean nullOrEmpty = value == null || value.trim().length() == 0; if (annotations != null) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(As.class)) { - Class> toInstanciate = ((As) annotation).binder(); - if (!(toInstanciate.equals(As.DEFAULT.class))) { + Class> toInstantiate = ((As) annotation).binder(); + if (!(toInstantiate.equals(As.DEFAULT.class))) { // Instantiate the binder - TypeBinder myInstance = toInstanciate.newInstance(); + TypeBinder myInstance = createNewInstance(toInstantiate); return myInstance.bind(name, annotations, value, clazz, type); } } @@ -570,7 +722,7 @@ private static Object internalDirectBind(String name, Annotation[] annotations, if (c.isAnnotationPresent(Global.class)) { Class forType = (Class) ((ParameterizedType) c.getGenericInterfaces()[0]).getActualTypeArguments()[0]; if (forType.isAssignableFrom(clazz)) { - Object result = c.newInstance().bind(name, annotations, value, clazz, type); + Object result = createNewInstance(c).bind(name, annotations, value, clazz, type); if (result != null) { return result; } @@ -681,6 +833,4 @@ private static Object internalDirectBind(String name, Annotation[] annotations, return DIRECTBINDING_NO_RESULT; } - - } diff --git a/framework/src/play/data/binding/BindingAnnotations.java b/framework/src/play/data/binding/BindingAnnotations.java index 73c3b796a9..e90d319e77 100644 --- a/framework/src/play/data/binding/BindingAnnotations.java +++ b/framework/src/play/data/binding/BindingAnnotations.java @@ -1,17 +1,14 @@ package play.data.binding; -import play.data.binding.As; -import play.data.binding.NoBinding; - import java.lang.annotation.Annotation; public class BindingAnnotations { public final Annotation[] annotations; - private String[] profiles = null; - private String[] noBindingProfiles = null; + private String[] profiles; + private String[] noBindingProfiles; public BindingAnnotations() { - this.annotations = null; + this(null); } public BindingAnnotations(Annotation[] annotations) { @@ -19,7 +16,7 @@ public BindingAnnotations(Annotation[] annotations) { } public BindingAnnotations(Annotation[] annotations, String[] profiles) { - this.annotations = annotations; + this(annotations); this.profiles = profiles; } @@ -69,8 +66,8 @@ private String[] getNoBindingProfiles() { public boolean checkNoBinding() { - final String[] _profiles = getProfiles(); - final String[] _noBindingProfiles = getNoBindingProfiles(); + String[] _profiles = getProfiles(); + String[] _noBindingProfiles = getNoBindingProfiles(); if (_noBindingProfiles.length>0) { for (String l : _noBindingProfiles) { diff --git a/framework/src/play/data/binding/CachedBoundActionMethodArgs.java b/framework/src/play/data/binding/CachedBoundActionMethodArgs.java index 2cb9022c9a..db8e4caeaa 100644 --- a/framework/src/play/data/binding/CachedBoundActionMethodArgs.java +++ b/framework/src/play/data/binding/CachedBoundActionMethodArgs.java @@ -5,13 +5,13 @@ import java.util.Map; // ActionInvoker.getActionMethodArgs() is called twice when using validation -// so we use this threadlocal cache to store the binding-result pr method pr request. +// so we use this ThreadLocal cache to store the binding-result pr method pr request. // This way we don't have to do it twice. public class CachedBoundActionMethodArgs { - private static ThreadLocal current = new ThreadLocal(); + private static final ThreadLocal current = new ThreadLocal<>(); - private Map preBoundActionMethodArgs = new HashMap(1); + private Map preBoundActionMethodArgs = new HashMap<>(1); public static void init() { current.set( new CachedBoundActionMethodArgs()); diff --git a/framework/src/play/data/binding/ParamNode.java b/framework/src/play/data/binding/ParamNode.java index 018a35632b..e9a4edb047 100644 --- a/framework/src/play/data/binding/ParamNode.java +++ b/framework/src/play/data/binding/ParamNode.java @@ -10,18 +10,18 @@ public class ParamNode { private final String name; - private final Map _children = new HashMap(8); + private final Map _children = new HashMap<>(8); private String[] values = null; private String originalKey; // splits a string on one-ore-more instances of .[] - // this works so that all the following strings (param naming syntaxes) + // this works so that all the following strings (param naming syntax) // is resolved into the same structural hierarchy: // a.b.c=12 // a[b].c=12 // a[b][c]=12 // a.b[c]=12 - private final static String keyPartDelimiterRegexpString = "[\\.\\[\\]]+"; + private static final String keyPartDelimiterRegexpString = "[\\.\\[\\]]+"; public ParamNode(String name) { this.name = name; diff --git a/framework/src/play/data/binding/TypeBinder.java b/framework/src/play/data/binding/TypeBinder.java index ef7111e71f..599137bbe1 100644 --- a/framework/src/play/data/binding/TypeBinder.java +++ b/framework/src/play/data/binding/TypeBinder.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.text.ParseException; /** * Supported type for binding. This interface is used to implement custom binders. @@ -18,7 +19,8 @@ public interface TypeBinder { * @param actualClass The class of the object you want to associate the value with * @param genericType The generic type associated with the object you want to bound the value to * @return the 'bound' object for example a date object if the value was '12/12/2002' - * @throws Exception + * @throws ParseException if parameter has invalid format (e.g. date) + * @throws Exception deprecated! Will be removed in Play 1.5 */ Object bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) throws Exception; diff --git a/framework/src/play/data/binding/TypeUnbinder.java b/framework/src/play/data/binding/TypeUnbinder.java index 71986dd1bf..0ac06e6f37 100644 --- a/framework/src/play/data/binding/TypeUnbinder.java +++ b/framework/src/play/data/binding/TypeUnbinder.java @@ -5,19 +5,28 @@ /** * Supported type for unbinding. This interface is used to implement custom unbinders. + * + * @param + * Type of the unbinder * */ public interface TypeUnbinder { - - /** + + /** * @param result - * @param src the object you want to unbind - * @param srcClazz The class of the object you want to associate the value with - * @param name the name of you parameter ie myparam for a simple param but can also be a complex one : mybean.address.street - * @param annotations An array of annotation that may be bound to your method parameter or your bean property - * @return true si unbinder is successful, otherwise false and will use the default unbinder + * The result container + * @param src + * the object you want to unbind + * @param srcClazz + * The class of the object you want to associate the value with + * @param name + * the name of you parameter ie myparam for a simple param but can also be a complex one : + * mybean.address.street + * @param annotations + * An array of annotation that may be bound to your method parameter or your bean property + * @return true if unwinder is successful, otherwise false and will use the default unbinder * @throws Exception + * if problem occurred */ boolean unBind(Map result, Object src, Class srcClazz, String name, Annotation[] annotations) throws Exception; - } diff --git a/framework/src/play/data/binding/Unbinder.java b/framework/src/play/data/binding/Unbinder.java index dcfe5adf25..577826560a 100644 --- a/framework/src/play/data/binding/Unbinder.java +++ b/framework/src/play/data/binding/Unbinder.java @@ -11,7 +11,7 @@ import play.libs.I18N; /** - * Try to unbind an object to a Map + * Try to unbind an object to a Map<String,String> */ public class Unbinder { @@ -63,11 +63,11 @@ private static void unbindCollection(Map result, Object src, Cla private static void unbindMap(Map result, Object src, Class srcClazz, String name, Annotation[] annotations) { if(src == null){ - directUnbind( result, src, srcClazz, name, annotations); + directUnbind( result, null, srcClazz, name, annotations); } else { - Map map = (Map) src; + Map map = (Map) src; - for (Map.Entry entry : map.entrySet()) { + for (Map.Entry entry : map.entrySet()) { Object key = entry.getKey(); if (!isDirect(key.getClass())) { throw new UnsupportedOperationException("Unbind won't work with indirect map keys yet"); @@ -118,19 +118,17 @@ private static void internalUnbind(Map result, Object src, Class for (Annotation annotation : annotations) { if (annotation.annotationType().equals(As.class)) { // Check the unbinder param first - Class> toInstanciate = (Class>) ((As) annotation) + Class> toInstantiate = (Class>) ((As) annotation) .unbinder(); - if (!(toInstanciate.equals(As.DEFAULT.class))) { - // Instantiate the binder - TypeUnbinder myInstance = (TypeUnbinder) toInstanciate.newInstance(); + if (!(toInstantiate.equals(As.DEFAULT.class))) { + TypeUnbinder myInstance = toInstantiate.newInstance(); isExtendedTypeBinder = myInstance.unBind(result, src, srcClazz, name, annotations); }else{ // unbinder is default, test if binder handle the unbinder too - Class> toInstanciateBinder = (Class>) ((As) annotation) - .binder(); - if (!(toInstanciateBinder.equals(As.DEFAULT.class)) - && TypeUnbinder.class.isAssignableFrom(toInstanciateBinder)) { - TypeUnbinder myInstance = (TypeUnbinder) toInstanciateBinder.newInstance(); + Class> toInstantiateBinder = ((As) annotation).binder(); + if (!(toInstantiateBinder.equals(As.DEFAULT.class)) + && TypeUnbinder.class.isAssignableFrom(toInstantiateBinder)) { + TypeUnbinder myInstance = (TypeUnbinder) toInstantiateBinder.newInstance(); isExtendedTypeBinder = myInstance.unBind(result, src, srcClazz, name, annotations); } } @@ -141,10 +139,6 @@ private static void internalUnbind(Map result, Object src, Class if (!isExtendedTypeBinder) { unBind(result, src, srcClazz, name, annotations); } - } catch (IllegalAccessException e) { - throw new RuntimeException("Object " + srcClazz + " won't unbind field " + name, e); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Object " + srcClazz + " won't unbind field " + name, e); } catch (Exception e) { throw new RuntimeException("Object " + srcClazz + " won't unbind field " + name, e); } @@ -180,7 +174,7 @@ private static void unBind(Map result, Object src, Class srcC field.setAccessible(true); // first we try with annotations resolved from property - List allAnnotations = new ArrayList(); + List allAnnotations = new ArrayList<>(); if (annotations != null && annotations.length > 0) { allAnnotations.addAll(Arrays.asList(annotations)); } @@ -193,11 +187,9 @@ private static void unBind(Map result, Object src, Class srcC try { internalUnbind(result, field.get(src), field.getType(), newName, allAnnotations.toArray(new Annotation[0])); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Object " + field.getType() + " won't unbind field " + newName, e); - } catch (IllegalAccessException e) { + } catch (IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException("Object " + field.getType() + " won't unbind field " + newName, e); - }finally{ + } finally{ field.setAccessible(oldAcc); } } diff --git a/framework/src/play/data/binding/types/BinaryBinder.java b/framework/src/play/data/binding/types/BinaryBinder.java index 87f1423c85..50bfaa67b8 100644 --- a/framework/src/play/data/binding/types/BinaryBinder.java +++ b/framework/src/play/data/binding/types/BinaryBinder.java @@ -14,6 +14,7 @@ public class BinaryBinder implements TypeBinder { @SuppressWarnings("unchecked") + @Override public Object bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { if (value == null || value.trim().length() == 0) { return null; diff --git a/framework/src/play/data/binding/types/ByteArrayArrayBinder.java b/framework/src/play/data/binding/types/ByteArrayArrayBinder.java index 9ba11108a9..d038ab3970 100644 --- a/framework/src/play/data/binding/types/ByteArrayArrayBinder.java +++ b/framework/src/play/data/binding/types/ByteArrayArrayBinder.java @@ -16,13 +16,14 @@ public class ByteArrayArrayBinder implements TypeBinder { @SuppressWarnings("unchecked") + @Override public byte[][] bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { if (value == null || value.trim().length() == 0) { return null; } Request req = Request.current(); if (req != null && req.args != null) { - List byteList = new ArrayList(); + List byteList = new ArrayList<>(); List uploads = (List) req.args.get("__UPLOADS"); if(uploads != null){ for (Upload upload : uploads) { diff --git a/framework/src/play/data/binding/types/ByteArrayBinder.java b/framework/src/play/data/binding/types/ByteArrayBinder.java index 2e3f82a121..043ce5e47a 100644 --- a/framework/src/play/data/binding/types/ByteArrayBinder.java +++ b/framework/src/play/data/binding/types/ByteArrayBinder.java @@ -14,6 +14,7 @@ public class ByteArrayBinder implements TypeBinder { @SuppressWarnings("unchecked") + @Override public byte[] bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { if (value == null || value.trim().length() == 0) { return null; diff --git a/framework/src/play/data/binding/types/CalendarBinder.java b/framework/src/play/data/binding/types/CalendarBinder.java index ef32a76b8f..54f6562e34 100644 --- a/framework/src/play/data/binding/types/CalendarBinder.java +++ b/framework/src/play/data/binding/types/CalendarBinder.java @@ -3,7 +3,6 @@ import play.data.binding.TypeBinder; import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -17,24 +16,21 @@ */ public class CalendarBinder implements TypeBinder { + @Override public Calendar bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) throws Exception { if (value == null || value.trim().length() == 0) { return null; } Calendar cal = Calendar.getInstance(Lang.getLocale()); - try { - Date date = AnnotationHelper.getDateAs(annotations, value); - if (date != null) { - cal.setTime(date); - } else { - SimpleDateFormat sdf = new SimpleDateFormat(I18N.getDateFormat()); - sdf.setLenient(false); - cal.setTime(sdf.parse(value)); - } - } catch (ParseException e) { - throw new IllegalArgumentException("Cannot convert [" + value + "] to a Calendar: " + e.toString()); - } + Date date = AnnotationHelper.getDateAs(annotations, value); + if (date != null) { + cal.setTime(date); + } else { + SimpleDateFormat sdf = new SimpleDateFormat(I18N.getDateFormat()); + sdf.setLenient(false); + cal.setTime(sdf.parse(value)); + } return cal; } } diff --git a/framework/src/play/data/binding/types/DateBinder.java b/framework/src/play/data/binding/types/DateBinder.java index ee6788a87b..2116a05a14 100644 --- a/framework/src/play/data/binding/types/DateBinder.java +++ b/framework/src/play/data/binding/types/DateBinder.java @@ -16,6 +16,7 @@ public class DateBinder implements TypeBinder { public static final String ISO8601 = "'ISO8601:'yyyy-MM-dd'T'HH:mm:ssZ"; + @Override public Date bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) throws Exception { if (value == null || value.trim().length() == 0) { return null; @@ -34,13 +35,8 @@ public Date bind(String name, Annotation[] annotations, String value, Class actu // Ignore } - try { - SimpleDateFormat sdf = new SimpleDateFormat(ISO8601); - sdf.setLenient(false); - return sdf.parse(value); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot convert [" + value + "] to a Date: " + e.toString()); - } - + SimpleDateFormat sdf = new SimpleDateFormat(ISO8601); + sdf.setLenient(false); + return sdf.parse(value); } } diff --git a/framework/src/play/data/binding/types/DateTimeBinder.java b/framework/src/play/data/binding/types/DateTimeBinder.java index 1fd86cbb82..3fb6d1ecea 100644 --- a/framework/src/play/data/binding/types/DateTimeBinder.java +++ b/framework/src/play/data/binding/types/DateTimeBinder.java @@ -14,6 +14,7 @@ public class DateTimeBinder implements TypeBinder { private static DateBinder dateBinder = new DateBinder(); + @Override public DateTime bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) throws Exception { if (value == null || value.trim().length() == 0) { return null; diff --git a/framework/src/play/data/binding/types/FileArrayBinder.java b/framework/src/play/data/binding/types/FileArrayBinder.java index 57cedebbfc..8b31e37a3b 100644 --- a/framework/src/play/data/binding/types/FileArrayBinder.java +++ b/framework/src/play/data/binding/types/FileArrayBinder.java @@ -17,13 +17,14 @@ public class FileArrayBinder implements TypeBinder { @SuppressWarnings("unchecked") + @Override public File[] bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { if (value == null || value.trim().length() == 0) { return null; } Request req = Request.current(); if (req != null && req.args != null) { - List fileArray = new ArrayList(); + List fileArray = new ArrayList<>(); List uploads = (List) req.args.get("__UPLOADS"); if (uploads != null) { for (Upload upload : uploads) { diff --git a/framework/src/play/data/binding/types/LocaleBinder.java b/framework/src/play/data/binding/types/LocaleBinder.java index 4077f5c6c6..7226badb03 100644 --- a/framework/src/play/data/binding/types/LocaleBinder.java +++ b/framework/src/play/data/binding/types/LocaleBinder.java @@ -10,6 +10,7 @@ */ public class LocaleBinder implements TypeBinder { + @Override public Locale bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { if( value == null ) return null; diff --git a/framework/src/play/data/binding/types/UploadArrayBinder.java b/framework/src/play/data/binding/types/UploadArrayBinder.java index d16795577f..7a87bc663a 100644 --- a/framework/src/play/data/binding/types/UploadArrayBinder.java +++ b/framework/src/play/data/binding/types/UploadArrayBinder.java @@ -17,13 +17,14 @@ public class UploadArrayBinder implements TypeBinder { @SuppressWarnings("unchecked") + @Override public Upload[] bind(String name, Annotation[] annotations, String value, Class actualClass, Type genericType) { if (value == null || value.trim().length() == 0) { return null; } Request req = Request.current(); if (req != null && req.args != null) { - List uploadArray = new ArrayList(); + List uploadArray = new ArrayList<>(); List uploads = (List) req.args.get("__UPLOADS"); if(uploads != null){ for (Upload upload : uploads) { diff --git a/framework/src/play/data/parsing/ApacheMultipartParser.java b/framework/src/play/data/parsing/ApacheMultipartParser.java index c000881768..eedea0e89d 100644 --- a/framework/src/play/data/parsing/ApacheMultipartParser.java +++ b/framework/src/play/data/parsing/ApacheMultipartParser.java @@ -1,6 +1,28 @@ package play.data.parsing; -import org.apache.commons.fileupload.*; +import static org.apache.commons.io.FileUtils.readFileToByteArray; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemHeaders; +import org.apache.commons.fileupload.FileItemIterator; +import org.apache.commons.fileupload.FileItemStream; +import org.apache.commons.fileupload.FileUploadBase; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.ParameterParser; +import org.apache.commons.fileupload.RequestContext; import org.apache.commons.fileupload.disk.DiskFileItem; import org.apache.commons.fileupload.util.Closeable; import org.apache.commons.fileupload.util.LimitedInputStream; @@ -9,6 +31,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.output.DeferredFileOutputStream; + import play.Logger; import play.Play; import play.data.FileUpload; @@ -18,14 +41,8 @@ import play.mvc.Http.Request; import play.utils.HTTP; -import java.io.*; -import java.util.*; - -import static org.apache.commons.io.FileUtils.readFileToByteArray; - /** - * From Apache commons fileupload. - * http://commons.apache.org/fileupload/ + * From Apache commons fileupload. http://commons.apache.org/fileupload/ */ public class ApacheMultipartParser extends DataParser { @@ -46,35 +63,33 @@ private static void putMapEntry(Map map, String name, String v /* * Copyright 2001-2004 The Apache Software Foundation * - * Licensed 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 + * Licensed 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 + * 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. + * 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. * - *

The default implementation of the - * {@link org.apache.commons.fileupload.FileItem FileItem} interface. + *

The default implementation of the {@link org.apache.commons.fileupload.FileItem FileItem} interface. * - *

After retrieving an instance of this class from a {@link - * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see - * {@link org.apache.commons.fileupload.DiskFileUpload - * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may - * either request all contents of file at once using {@link #get()} or - * request an {@link java.io.InputStream InputStream} with - * {@link #getInputStream()} and process the file without attempting to load - * it into memory, which may come handy with large files. + *

After retrieving an instance of this class from a {@link org.apache.commons.fileupload.DiskFileUpload + * DiskFileUpload} instance (see {@link org.apache.commons.fileupload.DiskFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may either request all contents of file at once using + * {@link #get()} or request an {@link java.io.InputStream InputStream} with {@link #getInputStream()} and process + * the file without attempting to load it into memory, which may come handy with large files. * * @author Rafal Krzewski + * * @author Sean Legassick + * * @author Jason van Zyl + * * @author John McNally + * * @author Martin Cooper + * * @author Sean C. Sullivan * * @since FileUpload 1.1 @@ -91,10 +106,9 @@ public static class AutoFileItem implements FileItem { // ----------------------------------------------------- Manifest constants /** - * Default content charset to be used when no explicit charset - * parameter is provided by the sender. Media subtypes of the - * "text" type are defined to have a default charset value of - * "ISO-8859-1" when received via HTTP. + * Default content charset to be used when no explicit charset parameter is provided by the sender. Media + * subtypes of the "text" type are defined to have a default charset value of "ISO-8859-1" when received via + * HTTP. */ public static final String DEFAULT_CHARSET = "ISO-8859-1"; /** @@ -112,8 +126,7 @@ public static class AutoFileItem implements FileItem { */ private String fieldName; /** - * The content type passed by the browser, or null if - * not defined. + * The content type passed by the browser, or null if not defined. */ private String contentType; /** @@ -140,9 +153,9 @@ public static class AutoFileItem implements FileItem { * Output stream for this item. */ private DeferredFileOutputStream dfos; - + /** - * The file items headers. + * The file items headers. */ private FileItemHeaders headers; @@ -157,15 +170,14 @@ public AutoFileItem(FileItemStream stream) { // ------------------------------- Methods from javax.activation.DataSource /** - * Returns an {@link java.io.InputStream InputStream} that can be - * used to retrieve the contents of the file. + * Returns an {@link java.io.InputStream InputStream} that can be used to retrieve the contents of the file. * - * @return An {@link java.io.InputStream InputStream} that can be - * used to retrieve the contents of the file. - * @throws IOException if an error occurs. + * @return An {@link java.io.InputStream InputStream} that can be used to retrieve the contents of the file. + * @throws IOException + * if an error occurs. */ - public InputStream getInputStream() - throws IOException { + @Override + public InputStream getInputStream() throws IOException { if (!dfos.isInMemory()) { return new FileInputStream(dfos.getFile()); } @@ -177,29 +189,26 @@ public InputStream getInputStream() } /** - * Returns the content type passed by the agent or null if - * not defined. + * Returns the content type passed by the agent or null if not defined. * - * @return The content type passed by the agent or null if - * not defined. + * @return The content type passed by the agent or null if not defined. */ + @Override public String getContentType() { return contentType; } /** - * Returns the content charset passed by the agent or null if - * not defined. + * Returns the content charset passed by the agent or null if not defined. * - * @return The content charset passed by the agent or null if - * not defined. + * @return The content charset passed by the agent or null if not defined. */ public String getCharSet() { ParameterParser parser = new ParameterParser(); parser.setLowerCaseNames(true); // Parameter parser can handle null input - Map params = parser.parse(getContentType(), ';'); - return (String) params.get("charset"); + Map params = parser.parse(getContentType(), ';'); + return params.get("charset"); } /** @@ -207,18 +216,18 @@ public String getCharSet() { * * @return The original filename in the client's filesystem. */ + @Override public String getName() { return fileName; } // ------------------------------------------------------- FileItem methods /** - * Provides a hint as to whether or not the file contents will be read - * from memory. + * Provides a hint as to whether or not the file contents will be read from memory. * - * @return true if the file contents will be read - * from memory; false otherwise. + * @return true if the file contents will be read from memory; false otherwise. */ + @Override public boolean isInMemory() { return (dfos.isInMemory()); } @@ -228,6 +237,7 @@ public boolean isInMemory() { * * @return The size of the file, in bytes. */ + @Override public long getSize() { if (cachedContent != null) { return cachedContent.length; @@ -239,12 +249,12 @@ public long getSize() { } /** - * Returns the contents of the file as an array of bytes. If the - * contents of the file were not yet cached in memory, they will be - * loaded from the disk storage and cached. + * Returns the contents of the file as an array of bytes. If the contents of the file were not yet cached in + * memory, they will be loaded from the disk storage and cached. * * @return The contents of the file as an array of bytes. */ + @Override public byte[] get() { if (dfos.isInMemory()) { if (cachedContent == null) { @@ -261,28 +271,31 @@ public byte[] get() { } /** - * Returns the contents of the file as a String, using the specified - * encoding. This method uses {@link #get()} to retrieve the - * contents of the file. + * Returns the contents of the file as a String, using the specified encoding. This method uses {@link #get()} + * to retrieve the contents of the file. * - * @param charset The charset to use. + * @param charset + * The charset to use. * @return The contents of the file, as a string. - * @throws UnsupportedEncodingException if the requested character - * encoding is not available. + * @throws UnsupportedEncodingException + * if the requested character encoding is not available. */ - public String getString(final String charset) - throws UnsupportedEncodingException { + @Override + public String getString(String charset) throws UnsupportedEncodingException { return new String(get(), charset); } /** - * Returns the contents of the file as a String, using the default - * character encoding. This method uses {@link #get()} to retrieve the - * contents of the file. - * + * Returns the contents of the file as a String, using the default character encoding. This method uses + * {@link #get()} to retrieve the contents of the file. + *

+ * + * @play.todo Note: TODO Consider making this method throw UnsupportedEncodingException . + *

+ * * @return The contents of the file, as a string. - * @todo Consider making this method throw UnsupportedEncodingException. */ + @Override public String getString() { byte[] rawdata = get(); String charset = getCharSet(); @@ -297,24 +310,23 @@ public String getString() { } /** - * A convenience method to write an uploaded item to disk. The client code - * is not concerned with whether or not the item is stored in memory, or on - * disk in a temporary location. They just want to write the uploaded item + * A convenience method to write an uploaded item to disk. The client code is not concerned with whether or not + * the item is stored in memory, or on disk in a temporary location. They just want to write the uploaded item * to a file. - *

- * This implementation first attempts to rename the uploaded item to the - * specified destination file, if the item was originally written to disk. - * Otherwise, the data will be copied to the specified file. - *

- * This method is only guaranteed to work once, the first time it - * is invoked for a particular item. This is because, in the event that the - * method renames a temporary file, that file will no longer be available + *

+ * This implementation first attempts to rename the uploaded item to the specified destination file, if the item + * was originally written to disk. Otherwise, the data will be copied to the specified file. + *

+ * This method is only guaranteed to work once, the first time it is invoked for a particular item. + * This is because, in the event that the method renames a temporary file, that file will no longer be available * to copy or rename again at a later time. * - * @param file The File into which the uploaded item should - * be stored. - * @throws Exception if an error occurs. + * @param file + * The File into which the uploaded item should be stored. + * @throws Exception + * if an error occurs. */ + @Override public void write(File file) throws Exception { if (isInMemory()) { FileUtils.writeByteArrayToFile(file, get()); @@ -322,31 +334,27 @@ public void write(File file) throws Exception { File outputFile = getStoreLocation(); if (outputFile != null) { /* - * The uploaded file is being stored on disk - * in a temporary location so move it to the - * desired file. + * The uploaded file is being stored on disk in a temporary location so move it to the desired file. */ if (!outputFile.renameTo(file)) { FileUtils.copyFile(outputFile, file); } } else { /* - * For whatever reason we cannot write the - * file to disk. + * For whatever reason we cannot write the file to disk. */ - throw new FileUploadException( - "Cannot write uploaded file to disk!"); + throw new FileUploadException("Cannot write uploaded file to disk!"); } } } /** - * Deletes the underlying storage for a file item, including deleting any - * associated temporary disk file. Although this storage will be deleted - * automatically when the FileItem instance is garbage - * collected, this method can be used to ensure that this is done at an - * earlier time, thus preserving system resources. + * Deletes the underlying storage for a file item, including deleting any associated temporary disk file. + * Although this storage will be deleted automatically when the FileItem instance is garbage + * collected, this method can be used to ensure that this is done at an earlier time, thus preserving system + * resources. */ + @Override public void delete() { cachedContent = null; File outputFile = getStoreLocation(); @@ -356,12 +364,12 @@ public void delete() { } /** - * Returns the name of the field in the multipart form corresponding to - * this file item. + * Returns the name of the field in the multipart form corresponding to this file item. * * @return The name of the form field. * @see #setFieldName(java.lang.String) */ + @Override public String getFieldName() { return fieldName; } @@ -369,45 +377,48 @@ public String getFieldName() { /** * Sets the field name used to reference this file item. * - * @param fieldName The name of the form field. + * @param fieldName + * The name of the form field. * @see #getFieldName() */ + @Override public void setFieldName(String fieldName) { this.fieldName = fieldName; } /** - * Determines whether or not a FileItem instance represents - * a simple form field. + * Determines whether or not a FileItem instance represents a simple form field. * - * @return true if the instance represents a simple form - * field; false if it represents an uploaded file. + * @return true if the instance represents a simple form field; false if it represents + * an uploaded file. * @see #setFormField(boolean) */ + @Override public boolean isFormField() { return isFormField; } /** - * Specifies whether or not a FileItem instance represents - * a simple form field. + * Specifies whether or not a FileItem instance represents a simple form field. * - * @param state true if the instance represents a simple form - * field; false if it represents an uploaded file. + * @param state + * true if the instance represents a simple form field; false if it + * represents an uploaded file. * @see #isFormField() */ + @Override public void setFormField(boolean state) { isFormField = state; } /** - * Returns an {@link java.io.OutputStream OutputStream} that can - * be used for storing the contents of the file. + * Returns an {@link java.io.OutputStream OutputStream} that can be used for storing the contents of the file. * - * @return An {@link java.io.OutputStream OutputStream} that can be used - * for storing the contensts of the file. - * @throws IOException if an error occurs. + * @return An {@link java.io.OutputStream OutputStream} that can be used for storing the contents of the file. + * @throws IOException + * if an error occurs. */ + @Override public OutputStream getOutputStream() throws IOException { if (dfos == null) { File outputFile = null; @@ -421,17 +432,13 @@ public OutputStream getOutputStream() throws IOException { // --------------------------------------------------------- Public methods /** - * Returns the {@link java.io.File} object for the FileItem's - * data's temporary location on the disk. Note that for - * FileItems that have their data stored in memory, - * this method will return null. When handling large - * files, you can use {@link java.io.File#renameTo(java.io.File)} to - * move the file to new location without copying the data, if the - * source and destination locations reside within the same logical - * volume. + * Returns the {@link java.io.File} object for the FileItem's data's temporary location on the + * disk. Note that for FileItems that have their data stored in memory, this method will return + * null. When handling large files, you can use {@link java.io.File#renameTo(java.io.File)} to move + * the file to new location without copying the data, if the source and destination locations reside within the + * same logical volume. * - * @return The data file, or null if the data is stored in - * memory. + * @return The data file, or null if the data is stored in memory. */ public File getStoreLocation() { return dfos.getFile(); @@ -439,21 +446,9 @@ public File getStoreLocation() { // ------------------------------------------------------ Protected methods /** - * Removes the file contents from the temporary storage. - */ - protected void finalize() { - File outputFile = dfos.getFile(); - - if (outputFile != null && outputFile.exists()) { - outputFile.delete(); - } - } - - /** - * Creates and returns a {@link java.io.File File} representing a uniquely - * named temporary file in the configured repository path. The lifetime of - * the file is tied to the lifetime of the FileItem instance; - * the file will be deleted when the instance is garbage collected. + * Creates and returns a {@link java.io.File File} representing a uniquely named temporary file in the + * configured repository path. The lifetime of the file is tied to the lifetime of the FileItem + * instance; the file will be deleted when the instance is garbage collected. * * @return The {@link java.io.File File} to be used for temporary storage. */ @@ -472,8 +467,8 @@ protected File getTempFile() { // -------------------------------------------------------- Private methods /** - * Returns an identifier that is unique within the class loader used to - * load this class, but does not have random-like apearance. + * Returns an identifier that is unique within the class loader used to load this class, but does not have + * random-like apearance. * * @return A String with the non-random looking instance identifier. */ @@ -492,12 +487,14 @@ private static String getUniqueId() { return id; } + @Override public String toString() { - return "name=" + this.getName() + ", StoreLocation=" + String.valueOf(this.getStoreLocation()) + ", size=" + this.getSize() + "bytes, " + "isFormField=" + isFormField() + ", FieldName=" + this.getFieldName(); + return "name=" + this.getName() + ", StoreLocation=" + String.valueOf(this.getStoreLocation()) + ", size=" + this.getSize() + + "bytes, " + "isFormField=" + isFormField() + ", FieldName=" + this.getFieldName(); } /** - * Returns the file item headers. + * Returns the file item headers. * * @return The file items headers. */ @@ -514,58 +511,59 @@ public FileItemHeaders getHeaders() { */ @Override public void setHeaders(FileItemHeaders pHeaders) { - headers = pHeaders; + headers = pHeaders; } } + @Override public Map parse(InputStream body) { - Map result = new HashMap(); + Map result = new HashMap<>(); try { - FileItemIteratorImpl iter = new FileItemIteratorImpl(body, Request.current().headers.get("content-type").value(), Request.current().encoding); + FileItemIteratorImpl iter = new FileItemIteratorImpl(body, Request.current().headers.get("content-type").value(), + Request.current().encoding); while (iter.hasNext()) { FileItemStream item = iter.next(); FileItem fileItem = new AutoFileItem(item); try { - Streams.copy(item.openStream(), fileItem.getOutputStream(), true); - } catch (FileUploadIOException e) { - throw (FileUploadException) e.getCause(); - } catch (IOException e) { - throw new IOFileUploadException("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage(), e); - } - if (fileItem.isFormField()) { - // must resolve encoding - String _encoding = Request.current().encoding; // this is our default - String _contentType = fileItem.getContentType(); - if( _contentType != null ) { - HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType(_contentType); - if( contentTypeEncoding.encoding != null ) { - _encoding = contentTypeEncoding.encoding; - } + try { + Streams.copy(item.openStream(), fileItem.getOutputStream(), true); + } catch (FileUploadIOException e) { + throw (FileUploadException) e.getCause(); + } catch (IOException e) { + throw new IOFileUploadException("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage(), e); } + if (fileItem.isFormField()) { + // must resolve encoding + String _encoding = Request.current().encoding; // this is our default + String _contentType = fileItem.getContentType(); + if (_contentType != null) { + HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType(_contentType); + if (contentTypeEncoding.encoding != null) { + _encoding = contentTypeEncoding.encoding; + } + } - putMapEntry(result, fileItem.getFieldName(), fileItem.getString( _encoding )); - } else { - @SuppressWarnings("unchecked") List uploads = (List) Request.current().args.get("__UPLOADS"); - if (uploads == null) { - uploads = new ArrayList(); - Request.current().args.put("__UPLOADS", uploads); - } - try { - uploads.add(new FileUpload(fileItem)); - } catch (Exception e) { - // GAE does not support it, we try in memory - uploads.add(new MemoryUpload(fileItem)); + putMapEntry(result, fileItem.getFieldName(), fileItem.getString(_encoding)); + } else { + @SuppressWarnings("unchecked") + List uploads = (List) Request.current().args.get("__UPLOADS"); + if (uploads == null) { + uploads = new ArrayList<>(); + Request.current().args.put("__UPLOADS", uploads); + } + try { + uploads.add(new FileUpload(fileItem)); + } catch (Exception e) { + // GAE does not support it, we try in memory + uploads.add(new MemoryUpload(fileItem)); + } + putMapEntry(result, fileItem.getFieldName(), fileItem.getFieldName()); } - putMapEntry(result, fileItem.getFieldName(), fileItem.getFieldName()); + } finally { + fileItem.delete(); } } - } catch (FileUploadIOException e) { - Logger.debug(e, "error"); - throw new IllegalStateException("Error when handling upload", e); - } catch (IOException e) { - Logger.debug(e, "error"); - throw new IllegalStateException("Error when handling upload", e); - } catch (FileUploadException e) { + } catch (IOException | FileUploadException e) { Logger.debug(e, "error"); throw new IllegalStateException("Error when handling upload", e); } catch (Exception e) { @@ -573,8 +571,9 @@ public Map parse(InputStream body) { throw new UnexpectedException(e); } return result; - } // ---------------------------------------------------------- Class methods - // ----------------------------------------------------- Manifest constants + } // ---------------------------------------------------------- Class methods + // ----------------------------------------------------- Manifest constants + /** * HTTP content type header name. */ @@ -606,22 +605,22 @@ public Map parse(InputStream body) { // ----------------------------------------------------------- Data members /** * The maximum size permitted for the complete request, as opposed to - * {@link #fileSizeMax}. A value of -1 indicates no maximum. + * {@link #maxFileSize}. A value of -1 indicates no maximum. */ - private long sizeMax = -1; + private long maxRequestSize = Integer.parseInt(Play.configuration.getProperty("upload.maxRequestSize", "-1")); /** * The maximum size permitted for a single uploaded file, as opposed to - * {@link #sizeMax}. A value of -1 indicates no maximum. + * {@link #maxRequestSize}. A value of -1 indicates no maximum. */ - private long fileSizeMax = -1; + private long maxFileSize = Integer.parseInt(Play.configuration.getProperty("upload.maxFileSize", "-1")); // ------------------------------------------------------ Protected methods /** * Retrieves the boundary from the Content-type header. * - * @param contentType The value of the content type header from which to extract the - * boundary value. + * @param contentType + * The value of the content type header from which to extract the boundary value. * @return The boundary, as a byte array. */ private byte[] getBoundary(String contentType) { @@ -629,8 +628,8 @@ private byte[] getBoundary(String contentType) { ParameterParser parser = new ParameterParser(); parser.setLowerCaseNames(true); // Parameter parser can handle null input - Map params = parser.parse(contentType, ';'); - String boundaryStr = (String) params.get("boundary"); + Map params = parser.parse(contentType, ';'); + String boundaryStr = params.get("boundary"); if (boundaryStr == null) { return null; @@ -645,13 +644,13 @@ private byte[] getBoundary(String contentType) { } /** - * Retrieves the file name from the Content-disposition - * header. + * Retrieves the file name from the Content-disposition header. * - * @param headers A Map containing the HTTP request headers. + * @param headers + * A Map containing the HTTP request headers. * @return The file name for the current encapsulation. */ - private String getFileName(Map /* String, String */ headers) { + private String getFileName(Map headers) { String fileName = null; String cd = getHeader(headers, CONTENT_DISPOSITION); if (cd != null) { @@ -660,9 +659,9 @@ private String getFileName(Map /* String, String */ headers) { ParameterParser parser = new ParameterParser(); parser.setLowerCaseNames(true); // Parameter parser can handle null input - Map params = parser.parse(cd, ';'); + Map params = parser.parse(cd, ';'); if (params.containsKey("filename")) { - fileName = (String) params.get("filename"); + fileName = params.get("filename"); if (fileName != null) { fileName = fileName.trim(); // IE7 returning fullpath name (#300920) @@ -683,22 +682,22 @@ private String getFileName(Map /* String, String */ headers) { } /** - * Retrieves the field name from the Content-disposition - * header. + * Retrieves the field name from the Content-disposition header. * - * @param headers A Map containing the HTTP request headers. + * @param headers + * A Map containing the HTTP request headers. * @return The field name for the current encapsulation. */ - private String getFieldName(Map /* String, String */ headers) { + private String getFieldName(Map headers) { String fieldName = null; String cd = getHeader(headers, CONTENT_DISPOSITION); - if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) { + if (cd != null && (cd.toLowerCase().startsWith(FORM_DATA) || cd.toLowerCase().startsWith(ATTACHMENT))) { ParameterParser parser = new ParameterParser(); parser.setLowerCaseNames(true); // Parameter parser can handle null input - Map params = parser.parse(cd, ';'); - fieldName = (String) params.get("name"); + Map params = parser.parse(cd, ';'); + fieldName = params.get("name"); if (fieldName != null) { fieldName = fieldName.trim(); } @@ -711,18 +710,18 @@ private String getFieldName(Map /* String, String */ headers) { * Parses the header-part and returns as key/value pairs. *

*

- * If there are multiple headers of the same names, the name will map to a - * comma-separated list containing the values. + * If there are multiple headers of the same names, the name will map to a comma-separated list containing the + * values. * - * @param headerPart The header-part of the current - * encapsulation. + * @param headerPart + * The header-part of the current encapsulation. * @return A Map containing the parsed HTTP request headers. */ - private Map /* String, String */ parseHeaders(String headerPart) { - final int len = headerPart.length(); - Map headers = new HashMap(); + private Map parseHeaders(String headerPart) { + int len = headerPart.length(); + Map headers = new HashMap<>(); int start = 0; - for (; ;) { + for (;;) { int end = parseEndOfLine(headerPart, start); if (start == end) { break; @@ -754,13 +753,15 @@ private String getFieldName(Map /* String, String */ headers) { /** * Skips bytes until the end of the current line. * - * @param headerPart The headers, which are being parsed. - * @param end Index of the last byte, which has yet been processed. + * @param headerPart + * The headers, which are being parsed. + * @param end + * Index of the last byte, which has yet been processed. * @return Index of the \r\n sequence, which indicates end of line. */ private int parseEndOfLine(String headerPart, int end) { int index = end; - for (; ;) { + for (;;) { int offset = headerPart.indexOf('\r', index); if (offset == -1 || offset + 1 >= headerPart.length()) { throw new IllegalStateException("Expected headers to be terminated by an empty line."); @@ -775,11 +776,13 @@ private int parseEndOfLine(String headerPart, int end) { /** * Reads the next header line. * - * @param headers String with all headers. - * @param header Map where to store the current header. + * @param headers + * String with all headers. + * @param header + * Map where to store the current header. */ private void parseHeaderLine(Map headers, String header) { - final int colonOffset = header.indexOf(':'); + int colonOffset = header.indexOf(':'); if (colonOffset == -1) { // This header line is malformed, skip it. return; @@ -796,21 +799,20 @@ private void parseHeaderLine(Map headers, String header) { } /** - * Returns the header with the specified name from the supplied map. The - * header lookup is case-insensitive. + * Returns the header with the specified name from the supplied map. The header lookup is case-insensitive. * - * @param headers A Map containing the HTTP request headers. - * @param name The name of the header to return. - * @return The value of specified header, or a comma-separated list if there - * were multiple headers of that name. + * @param headers + * A Map containing the HTTP request headers. + * @param name + * The name of the header to return. + * @return The value of specified header, or a comma-separated list if there were multiple headers of that name. */ - private final String getHeader(Map /* String, String */ headers, String name) { - return (String) headers.get(name.toLowerCase()); + private String getHeader(Map headers, String name) { + return headers.get(name.toLowerCase()); } /** - * The iterator, which is returned by - * {@link FileUploadBase#getItemIterator(RequestContext)}. + * The iterator, which is returned by {@link FileUploadBase#getItemIterator(RequestContext)}. */ private class FileItemIteratorImpl implements FileItemIterator { @@ -849,10 +851,14 @@ private class FileItemStreamImpl implements FileItemStream { /** * CReates a new instance. * - * @param pName The items file name, or null. - * @param pFieldName The items field name. - * @param pContentType The items content type, or null. - * @param pFormField Whether the item is a form field. + * @param pName + * The items file name, or null. + * @param pFieldName + * The items field name. + * @param pContentType + * The items content type, or null. + * @param pFormField + * Whether the item is a form field. */ FileItemStreamImpl(String pName, String pFieldName, String pContentType, boolean pFormField) { name = pName; @@ -860,11 +866,14 @@ private class FileItemStreamImpl implements FileItemStream { contentType = pContentType; formField = pFormField; InputStream istream = multi.newInputStream(); - if (fileSizeMax != -1) { - istream = new LimitedInputStream(istream, fileSizeMax) { + if (maxFileSize != -1) { + istream = new LimitedInputStream(istream, maxFileSize) { + @Override protected void raiseError(long pSizeMax, long pCount) throws IOException { - FileUploadException e = new FileSizeLimitExceededException("The field " + fieldName + " exceeds its maximum permitted " + " size of " + pSizeMax + " characters.", pCount, pSizeMax); + FileUploadException e = new FileSizeLimitExceededException( + "The field " + fieldName + " exceeds its maximum permitted " + " size of " + pSizeMax + " characters.", + pCount, pSizeMax); throw new FileUploadIOException(e); } }; @@ -872,20 +881,22 @@ protected void raiseError(long pSizeMax, long pCount) throws IOException { stream = istream; } + @Override public FileItemHeaders getHeaders() { return fileItemHeaders; } + @Override public void setHeaders(FileItemHeaders fileItemHeaders) { this.fileItemHeaders = fileItemHeaders; } - /** * Returns the items content type, or null. * * @return Content type, if known, or null. */ + @Override public String getContentType() { return contentType; } @@ -895,6 +906,7 @@ public String getContentType() { * * @return Field name. */ + @Override public String getFieldName() { return fieldName; } @@ -904,6 +916,7 @@ public String getFieldName() { * * @return File name, if known, or null. */ + @Override public String getName() { return name; } @@ -913,17 +926,19 @@ public String getName() { * * @return True, if the item is a form field, otherwise false. */ + @Override public boolean isFormField() { return formField; } /** - * Returns an input stream, which may be used to read the items - * contents. + * Returns an input stream, which may be used to read the items contents. * * @return Opened input stream. - * @throws IOException An I/O error occurred. + * @throws IOException + * An I/O error occurred. */ + @Override public InputStream openStream() throws IOException { if (opened) { throw new IllegalStateException("The stream was already opened."); @@ -937,7 +952,8 @@ public InputStream openStream() throws IOException { /** * Closes the file item. * - * @throws IOException An I/O error occurred. + * @throws IOException + * An I/O error occurred. */ void close() throws IOException { stream.close(); @@ -976,23 +992,27 @@ void close() throws IOException { /** * Creates a new instance. * - * @param ctx The request context. - * @throws FileUploadException An error occurred while parsing the request. - * @throws IOException An I/O error occurred. + * @throws FileUploadException + * An error occurred while parsing the request. + * @throws IOException + * An I/O error occurred. */ FileItemIteratorImpl(InputStream input, String contentType, String charEncoding) throws FileUploadException, IOException { if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) { - throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType); + throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + + " stream, content type header is " + contentType); } - if (sizeMax >= 0) { + if (maxRequestSize >= 0) { // TODO check size - input = new LimitedInputStream(input, sizeMax) { + input = new LimitedInputStream(input, maxRequestSize) { + @Override protected void raiseError(long pSizeMax, long pCount) throws IOException { - FileUploadException ex = new SizeLimitExceededException("the request was rejected because" + " its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax); + FileUploadException ex = new SizeLimitExceededException("the request was rejected because" + " its size (" + pCount + + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax); throw new FileUploadIOException(ex); } }; @@ -1015,7 +1035,8 @@ protected void raiseError(long pSizeMax, long pCount) throws IOException { * Called for finding the nex item, if any. * * @return True, if an next item was found, otherwise false. - * @throws IOException An I/O error occurred. + * @throws IOException + * An I/O error occurred. */ private boolean findNextItem() throws IOException { if (eof) { @@ -1025,7 +1046,7 @@ private boolean findNextItem() throws IOException { currentItem.close(); currentItem = null; } - for (; ;) { + for (;;) { boolean nextPart; if (skipPreamble) { nextPart = multi.skipPreamble(); @@ -1043,7 +1064,7 @@ private boolean findNextItem() throws IOException { currentFieldName = null; continue; } - Map headers = parseHeaders(multi.readHeaders()); + Map headers = parseHeaders(multi.readHeaders()); if (currentFieldName == null) { // We're parsing the outer multipart String fieldName = getFieldName(headers); @@ -1076,14 +1097,15 @@ private boolean findNextItem() throws IOException { } /** - * Returns, whether another instance of {@link FileItemStream} is - * available. + * Returns, whether another instance of {@link FileItemStream} is available. * - * @return True, if one or more additional file items are available, - * otherwise false. - * @throws FileUploadException Parsing or processing the file item failed. - * @throws IOException Reading the file item failed. + * @return True, if one or more additional file items are available, otherwise false. + * @throws FileUploadException + * Parsing or processing the file item failed. + * @throws IOException + * Reading the file item failed. */ + @Override public boolean hasNext() throws FileUploadException, IOException { if (eof) { return false; @@ -1097,14 +1119,15 @@ public boolean hasNext() throws FileUploadException, IOException { /** * Returns the next available {@link FileItemStream}. * - * @return FileItemStream instance, which provides access to the next - * file item. + * @return FileItemStream instance, which provides access to the next file item. * @throws java.util.NoSuchElementException - * No more items are available. Use {@link #hasNext()} to - * prevent this exception. - * @throws FileUploadException Parsing or processing the file item failed. - * @throws IOException Reading the file item failed. + * No more items are available. Use {@link #hasNext()} to prevent this exception. + * @throws FileUploadException + * Parsing or processing the file item failed. + * @throws IOException + * Reading the file item failed. */ + @Override public FileItemStream next() throws FileUploadException, IOException { if (eof || (!itemValid && !hasNext())) { throw new NoSuchElementException(); @@ -1115,8 +1138,7 @@ public FileItemStream next() throws FileUploadException, IOException { } /** - * This exception is thrown for hiding an inner {@link FileUploadException} - * in an {@link IOException}. + * This exception is thrown for hiding an inner {@link FileUploadException} in an {@link IOException}. */ private static class FileUploadIOException extends IOException { @@ -1125,15 +1147,15 @@ private static class FileUploadIOException extends IOException { */ private static final long serialVersionUID = -7047616958165584154L; /** - * The exceptions cause; we overwrite the parent classes field, which is - * available since Java 1.4 only. + * The exceptions cause; we overwrite the parent classes field, which is available since Java 1.4 only. */ private final FileUploadException cause; /** * Creates a FileUploadIOException with the given cause. * - * @param pCause The exceptions cause, if any, or null. + * @param pCause + * The exceptions cause, if any, or null. */ public FileUploadIOException(FileUploadException pCause) { // We're not doing super(pCause) cause of 1.3 compatibility. @@ -1145,6 +1167,7 @@ public FileUploadIOException(FileUploadException pCause) { * * @return The exceptions cause, if any, or null. */ + @Override public Throwable getCause() { return cause; } @@ -1161,10 +1184,10 @@ private static class InvalidContentTypeException extends FileUploadException { private static final long serialVersionUID = -9073026332015646668L; /** - * Constructs an InvalidContentTypeException with the - * specified detail message. + * Constructs an InvalidContentTypeException with the specified detail message. * - * @param message The detail message. + * @param message + * The detail message. */ public InvalidContentTypeException(String message) { super(message); @@ -1181,16 +1204,17 @@ private static class IOFileUploadException extends FileUploadException { */ private static final long serialVersionUID = 1749796615868477269L; /** - * The exceptions cause; we overwrite the parent classes field, which is - * available since Java 1.4 only. + * The exceptions cause; we overwrite the parent classes field, which is available since Java 1.4 only. */ private final IOException cause; /** * Creates a new instance with the given cause. * - * @param pMsg The detail message. - * @param pException The exceptions cause. + * @param pMsg + * The detail message. + * @param pException + * The exceptions cause. */ public IOFileUploadException(String pMsg, IOException pException) { super(pMsg); @@ -1202,6 +1226,7 @@ public IOFileUploadException(String pMsg, IOException pException) { * * @return The exceptions cause, if any, or null. */ + @Override public Throwable getCause() { return cause; } @@ -1224,9 +1249,12 @@ protected abstract static class SizeException extends FileUploadException { /** * Creates a new instance. * - * @param message The detail message. - * @param actual The actual number of bytes in the request. - * @param permitted The requests size limit, in bytes. + * @param message + * The detail message. + * @param actual + * The actual number of bytes in the request. + * @param permitted + * The requests size limit, in bytes. */ protected SizeException(String message, long actual, long permitted) { super(message); @@ -1264,12 +1292,15 @@ private static class SizeLimitExceededException extends SizeException { private static final long serialVersionUID = -2474893167098052828L; /** - * Constructs a SizeExceededException with the specified - * detail message, and actual and permitted sizes. + * Constructs a SizeExceededException with the specified detail message, and actual and permitted + * sizes. * - * @param message The detail message. - * @param actual The actual request size. - * @param permitted The maximum permitted request size. + * @param message + * The detail message. + * @param actual + * The actual request size. + * @param permitted + * The maximum permitted request size. */ public SizeLimitExceededException(String message, long actual, long permitted) { super(message, actual, permitted); @@ -1287,12 +1318,15 @@ private static class FileSizeLimitExceededException extends SizeException { private static final long serialVersionUID = 8150776562029630058L; /** - * Constructs a SizeExceededException with the specified - * detail message, and actual and permitted sizes. + * Constructs a SizeExceededException with the specified detail message, and actual and permitted + * sizes. * - * @param message The detail message. - * @param actual The actual request size. - * @param permitted The maximum permitted request size. + * @param message + * The detail message. + * @param actual + * The actual request size. + * @param permitted + * The maximum permitted request size. */ public FileSizeLimitExceededException(String message, long actual, long permitted) { super(message, actual, permitted); diff --git a/framework/src/play/data/parsing/DataParser.java b/framework/src/play/data/parsing/DataParser.java index 50f5e55f2d..ecd31352f2 100644 --- a/framework/src/play/data/parsing/DataParser.java +++ b/framework/src/play/data/parsing/DataParser.java @@ -4,7 +4,7 @@ import java.util.Map; /** - * A data parser parse the HTTP request data to a Map + * A data parser parse the HTTP request data to a Map<String,String[]> */ public abstract class DataParser { diff --git a/framework/src/play/data/parsing/DataParsers.java b/framework/src/play/data/parsing/DataParsers.java index c5c321acbf..d2ca4fb087 100644 --- a/framework/src/play/data/parsing/DataParsers.java +++ b/framework/src/play/data/parsing/DataParsers.java @@ -4,7 +4,7 @@ import java.util.Map; public class DataParsers { - private static final Map parsers = new HashMap(); + private static final Map parsers = new HashMap<>(); // These are our injected Parser. Maybe we later want to allow dynamic injection static { diff --git a/framework/src/play/data/parsing/MultipartStream.java b/framework/src/play/data/parsing/MultipartStream.java index a3f860e1c4..783bd60513 100644 --- a/framework/src/play/data/parsing/MultipartStream.java +++ b/framework/src/play/data/parsing/MultipartStream.java @@ -25,18 +25,18 @@ * multipart-body := preamble 1*encapsulation close-delimiter epilogue
* encapsulation := delimiter body CRLF
* delimiter := "--" boundary CRLF
- * close-delimiter := "--" boudary "--"
+ * close-delimiter := "--" boundary "--"
* preamble := <ignore>
* epilogue := <ignore>
* body := header-part CRLF body-part
* header-part := 1*header CRLF
* header := header-name ":" header-value
* header-name := <printable ascii characters except ":">
- * header-value := <any ascii characters except CR & LF>
+ * header-value := <any ascii characters except CR & LF>
* body-data := <arbitrary data>
* * - *

Note that body-data can contain another mulipart entity. There + *

Note that body-data can contain another multipart entity. There * is limited support for single pass processing of such nested * streams. The nested stream is required to have a * boundary token of the same length as the parent stream (see {@link @@ -215,7 +215,7 @@ private void notifyListener() { */ private int head; /** - * The index of last valid characer in the buffer + 1. + * The index of last valid character in the buffer + 1. *
* 0 <= tail <= bufSize */ @@ -286,7 +286,7 @@ public MultipartStream(InputStream input, byte[] boundary, int bufSize) { this.buffer = new byte[bufSize]; this.notifier = pNotifier; - // We prepend CR/LF to the boundary to chop trailng CR/LF from + // We prepend CR/LF to the boundary to chop trailing CR/LF from // body-data tokens. this.boundary = new byte[boundary.length + BOUNDARY_PREFIX.length]; this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; @@ -389,7 +389,7 @@ public byte readByte() * @return true if there are more encapsulations in * this stream; false otherwise. * - * @throws MalformedStreamException if the stream ends unexpecetedly or + * @throws MalformedStreamException if the stream ends unexpectedly or * fails to follow required syntax. */ public boolean readBoundary() @@ -420,7 +420,7 @@ public boolean readBoundary() "Unexpected characters follow a boundary"); } } catch (IOException e) { - throw new MalformedStreamException("Stream ended unexpectedly"); + throw new MalformedStreamException("Stream ended unexpectedly", e); } return nextChunk; } @@ -467,7 +467,7 @@ public void setBoundary(byte[] boundary) * * @return The header-part of the current encapsulation. * - * @throws MalformedStreamException if the stream ends unexpecetedly. + * @throws MalformedStreamException if the stream ends unexpectedly. */ public String readHeaders() throws MalformedStreamException { @@ -481,7 +481,7 @@ public String readHeaders() try { b[0] = readByte(); } catch (IOException e) { - throw new MalformedStreamException("Stream ended unexpectedly"); + throw new MalformedStreamException("Stream ended unexpectedly", e); } size++; if (b[0] == HEADER_SEPARATOR[i]) { @@ -530,7 +530,7 @@ public String readHeaders() */ public int readBodyData(OutputStream output) throws MalformedStreamException, IOException { - final InputStream istream = newInputStream(); + InputStream istream = newInputStream(); return (int) Streams.copy(istream, output, false); } @@ -570,7 +570,7 @@ public int discardBodyData() */ public boolean skipPreamble() throws IOException { - // First delimiter may be not preceeded with a CRLF. + // First delimiter may be not preceded with a CRLF. System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); boundaryLength = boundary.length - 2; try { @@ -689,6 +689,10 @@ public MalformedStreamException() { public MalformedStreamException(String message) { super(message); } + + public MalformedStreamException(String message, Throwable cause) { + super(message, cause); + } } /** @@ -771,6 +775,7 @@ public long getBytesRead() { * @throws IOException An I/O error occurs. * @return Number of bytes in the buffer. */ + @Override public int available() throws IOException { if (pos == -1) { return tail - head - pad; @@ -787,6 +792,7 @@ public int available() throws IOException { * integer, or -1 for EOF. * @throws IOException An I/O error occurred. */ + @Override public int read() throws IOException { if (closed) { throw new FileItemStream.ItemSkippedException(); @@ -813,6 +819,7 @@ public int read() throws IOException { * or -1 for EOF. * @throws IOException An I/O error occurred. */ + @Override public int read(byte[] b, int off, int len) throws IOException { if (closed) { throw new FileItemStream.ItemSkippedException(); @@ -838,6 +845,7 @@ public int read(byte[] b, int off, int len) throws IOException { * Closes the input stream. * @throws IOException An I/O error occurred. */ + @Override public void close() throws IOException { if (closed) { return; @@ -862,6 +870,7 @@ public void close() throws IOException { * skipped. * @throws IOException An I/O error occurred. */ + @Override public long skip(long bytes) throws IOException { if (closed) { throw new FileItemStream.ItemSkippedException(); @@ -912,6 +921,7 @@ private int makeAvailable() throws IOException { * Returns, whether the stream is closed. * @return True, if the stream is closed, otherwise false. */ + @Override public boolean isClosed() { return closed; } diff --git a/framework/src/play/data/parsing/TempFilePlugin.java b/framework/src/play/data/parsing/TempFilePlugin.java index 363303fe45..d2d8e1c505 100644 --- a/framework/src/play/data/parsing/TempFilePlugin.java +++ b/framework/src/play/data/parsing/TempFilePlugin.java @@ -27,7 +27,7 @@ public class TempFilePlugin extends PlayPlugin { private static synchronized long getCountLocal() { return count++; } - public static ThreadLocal tempFolder = new ThreadLocal(); + public static final ThreadLocal tempFolder = new ThreadLocal<>(); public static File createTempFolder() { if (Play.tmpDir == null || Play.readOnlyTmp) { diff --git a/framework/src/play/data/parsing/TextParser.java b/framework/src/play/data/parsing/TextParser.java index e4f64dfa68..bcc84cc37e 100644 --- a/framework/src/play/data/parsing/TextParser.java +++ b/framework/src/play/data/parsing/TextParser.java @@ -12,7 +12,7 @@ public class TextParser extends DataParser { @Override public Map parse(InputStream is) { try { - Map params = new HashMap(); + Map params = new HashMap<>(); ByteArrayOutputStream os = new ByteArrayOutputStream(); int b; while ((b = is.read()) != -1) { diff --git a/framework/src/play/data/parsing/UrlEncodedParser.java b/framework/src/play/data/parsing/UrlEncodedParser.java index 0766133318..0b555a353e 100644 --- a/framework/src/play/data/parsing/UrlEncodedParser.java +++ b/framework/src/play/data/parsing/UrlEncodedParser.java @@ -29,7 +29,7 @@ public class UrlEncodedParser extends DataParser { public static Map parse(String urlEncoded) { try { - final String encoding = Http.Request.current().encoding; + String encoding = Http.Request.current().encoding; return new UrlEncodedParser().parse(new ByteArrayInputStream(urlEncoded.getBytes( encoding ))); } catch (UnsupportedEncodingException ex) { throw new UnexpectedException(ex); @@ -45,9 +45,9 @@ public static Map parseQueryString(InputStream is) { @Override public Map parse(InputStream is) { // Encoding is either retrieved from contentType or it is the default encoding - final String encoding = Http.Request.current().encoding; + String encoding = Http.Request.current().encoding; try { - Map params = new LinkedHashMap(); + Map params = new LinkedHashMap<>(); ByteArrayOutputStream os = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int bytesRead; @@ -58,7 +58,7 @@ public Map parse(InputStream is) { String data = new String(os.toByteArray(), encoding); if (data.length() == 0) { //data is empty - can skip the rest - return new HashMap(0); + return new HashMap<>(0); } // data is o the form: @@ -112,13 +112,13 @@ public Map parse(InputStream is) { "test".getBytes(providedCharset); charset = providedCharset; // it works.. } catch (Exception e) { - Logger.debug("Got invalid _charset_ in form: " + providedCharset); + Logger.debug(e, "Got invalid _charset_ in form: " + providedCharset); // lets just use the default one.. } } // We're ready to decode the params - Map decodedParams = new LinkedHashMap(params.size()); + Map decodedParams = new LinkedHashMap<>(params.size()); URLCodec codec = new URLCodec(); for (Map.Entry e : params.entrySet()) { String key = e.getKey(); diff --git a/framework/src/play/data/validation/CheckWithCheck.java b/framework/src/play/data/validation/CheckWithCheck.java index 74ae6d727a..b81e131dfa 100644 --- a/framework/src/play/data/validation/CheckWithCheck.java +++ b/framework/src/play/data/validation/CheckWithCheck.java @@ -12,9 +12,9 @@ @SuppressWarnings("serial") public class CheckWithCheck extends AbstractAnnotationCheck { - final static String mes = "validation.invalid"; + static final String mes = "validation.invalid"; - Map variables = new TreeMap(); + Map variables = new TreeMap<>(); Check check; @Override @@ -35,6 +35,7 @@ protected Map createMessageVariables() { return variables; } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { return check.isSatisfied(validatedObject, value); } diff --git a/framework/src/play/data/validation/EmailCheck.java b/framework/src/play/data/validation/EmailCheck.java index a371ce1d2a..7bc20a7053 100644 --- a/framework/src/play/data/validation/EmailCheck.java +++ b/framework/src/play/data/validation/EmailCheck.java @@ -8,7 +8,7 @@ @SuppressWarnings("serial") public class EmailCheck extends AbstractAnnotationCheck { - final static String mes = "validation.email"; + static final String mes = "validation.email"; static Pattern emailPattern = Pattern.compile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?"); @Override @@ -16,6 +16,7 @@ public void configure(Email email) { setMessage(email.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { value = Validation.willBeValidated(value); if (value == null || value.toString().length() == 0) { diff --git a/framework/src/play/data/validation/Equals.java b/framework/src/play/data/validation/Equals.java index 24e112269f..f0b9d228ce 100644 --- a/framework/src/play/data/validation/Equals.java +++ b/framework/src/play/data/validation/Equals.java @@ -4,24 +4,23 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + import net.sf.oval.configuration.annotation.Constraint; /** - * This field must be equals to another field. - * Message key: validation.equals - * $1: field name - * $2: other field name + * This field must be equals to another field. Message key: validation.equals $1: field name $2: other field name */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Constraint(checkWith = EqualsCheck.class) public @interface Equals { String message() default EqualsCheck.mes; - + /** * The other field name + * + * @return The other field name */ String value(); } - diff --git a/framework/src/play/data/validation/EqualsCheck.java b/framework/src/play/data/validation/EqualsCheck.java index d7c2991204..14fd5e6a63 100644 --- a/framework/src/play/data/validation/EqualsCheck.java +++ b/framework/src/play/data/validation/EqualsCheck.java @@ -19,7 +19,7 @@ @SuppressWarnings("serial") public class EqualsCheck extends AbstractAnnotationCheck { - final static String mes = "validation.equals"; + static final String mes = "validation.equals"; String to; String otherKey; @@ -31,6 +31,7 @@ public void configure(Equals equals) { setMessage(equals.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); try { @@ -81,7 +82,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("to", otherKey); return messageVariables; } diff --git a/framework/src/play/data/validation/IPv4AddressCheck.java b/framework/src/play/data/validation/IPv4AddressCheck.java index 35de3ee25a..4d2faf07b9 100644 --- a/framework/src/play/data/validation/IPv4AddressCheck.java +++ b/framework/src/play/data/validation/IPv4AddressCheck.java @@ -9,7 +9,7 @@ public class IPv4AddressCheck extends AbstractAnnotationCheck { - final static String mes = "validation.ipv4"; + static final String mes = "validation.ipv4"; @Override public void configure(IPv4Address ipv4Address) { @@ -29,7 +29,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con } for (int i = 0; i < parts.length; i++) { - // Check taht we don't have empty part or (+-) sign + // Check that we don't have empty part or (+-) sign if (parts[i].isEmpty() || !parts[i].matches("[0-9]{1,3}")) { return false; } diff --git a/framework/src/play/data/validation/IPv6AddressCheck.java b/framework/src/play/data/validation/IPv6AddressCheck.java index 23c3c172bc..e022d8d1ad 100644 --- a/framework/src/play/data/validation/IPv6AddressCheck.java +++ b/framework/src/play/data/validation/IPv6AddressCheck.java @@ -11,13 +11,14 @@ public class IPv6AddressCheck extends AbstractAnnotationCheck { - final static String mes = "validation.ipv6"; + static final String mes = "validation.ipv6"; @Override public void configure(IPv6Address phone) { setMessage(phone.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) throws OValException { if (value == null || value.toString().length() == 0) { diff --git a/framework/src/play/data/validation/InFutureCheck.java b/framework/src/play/data/validation/InFutureCheck.java index 91d71e8894..27e11a403f 100644 --- a/framework/src/play/data/validation/InFutureCheck.java +++ b/framework/src/play/data/validation/InFutureCheck.java @@ -15,7 +15,7 @@ @SuppressWarnings("serial") public class InFutureCheck extends AbstractAnnotationCheck { - final static String mes = "validation.future"; + static final String mes = "validation.future"; Date reference; @@ -33,6 +33,7 @@ public void configure(InFuture future) { } } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null) { @@ -57,7 +58,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("reference", new SimpleDateFormat(I18N.getDateFormat()).format(reference)); return messageVariables; } diff --git a/framework/src/play/data/validation/InPastCheck.java b/framework/src/play/data/validation/InPastCheck.java index 4c6ab0fefe..30f349cb6b 100644 --- a/framework/src/play/data/validation/InPastCheck.java +++ b/framework/src/play/data/validation/InPastCheck.java @@ -15,7 +15,7 @@ @SuppressWarnings("serial") public class InPastCheck extends AbstractAnnotationCheck { - final static String mes = "validation.past"; + static final String mes = "validation.past"; Date reference; @Override @@ -32,6 +32,7 @@ public void configure(InPast past) { } } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null) { @@ -56,7 +57,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("reference", new SimpleDateFormat(I18N.getDateFormat()).format(reference)); return messageVariables; } diff --git a/framework/src/play/data/validation/IsTrueCheck.java b/framework/src/play/data/validation/IsTrueCheck.java index 380d82c8bd..2db5d1725f 100644 --- a/framework/src/play/data/validation/IsTrueCheck.java +++ b/framework/src/play/data/validation/IsTrueCheck.java @@ -7,13 +7,14 @@ @SuppressWarnings("serial") public class IsTrueCheck extends AbstractAnnotationCheck { - final static String mes = "validation.isTrue"; + static final String mes = "validation.isTrue"; @Override public void configure(IsTrue isTrue) { setMessage(isTrue.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { if (value == null) { return false; diff --git a/framework/src/play/data/validation/MatchCheck.java b/framework/src/play/data/validation/MatchCheck.java index 45e850fcd9..fbe6d62f48 100644 --- a/framework/src/play/data/validation/MatchCheck.java +++ b/framework/src/play/data/validation/MatchCheck.java @@ -10,7 +10,7 @@ @SuppressWarnings("serial") public class MatchCheck extends AbstractAnnotationCheck { - final static String mes = "validation.match"; + static final String mes = "validation.match"; Pattern pattern = null; @Override @@ -19,6 +19,7 @@ public void configure(Match match) { pattern = Pattern.compile(match.value()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null || value.toString().length() == 0) { @@ -29,7 +30,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("pattern", pattern.toString()); return messageVariables; } diff --git a/framework/src/play/data/validation/MaxCheck.java b/framework/src/play/data/validation/MaxCheck.java index a8a87a353a..c38d95aa14 100644 --- a/framework/src/play/data/validation/MaxCheck.java +++ b/framework/src/play/data/validation/MaxCheck.java @@ -9,7 +9,7 @@ @SuppressWarnings("serial") public class MaxCheck extends AbstractAnnotationCheck { - final static String mes = "validation.max"; + static final String mes = "validation.max"; double max; @@ -19,6 +19,7 @@ public void configure(Max max) { setMessage(max.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null) { @@ -43,7 +44,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("max", Double.toString(max)); return messageVariables; } diff --git a/framework/src/play/data/validation/MaxSizeCheck.java b/framework/src/play/data/validation/MaxSizeCheck.java index c15a0778f2..04d20880a9 100644 --- a/framework/src/play/data/validation/MaxSizeCheck.java +++ b/framework/src/play/data/validation/MaxSizeCheck.java @@ -9,7 +9,7 @@ @SuppressWarnings("serial") public class MaxSizeCheck extends AbstractAnnotationCheck { - final static String mes = "validation.maxSize"; + static final String mes = "validation.maxSize"; int maxSize; @@ -19,6 +19,7 @@ public void configure(MaxSize annotation) { setMessage(annotation.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null || value.toString().length() == 0) { @@ -29,7 +30,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("maxSize", Integer.toString(maxSize)); return messageVariables; } diff --git a/framework/src/play/data/validation/MinCheck.java b/framework/src/play/data/validation/MinCheck.java index c5dabde264..1c52addd1f 100644 --- a/framework/src/play/data/validation/MinCheck.java +++ b/framework/src/play/data/validation/MinCheck.java @@ -9,7 +9,7 @@ @SuppressWarnings("serial") public class MinCheck extends AbstractAnnotationCheck { - final static String mes = "validation.min"; + static final String mes = "validation.min"; double min; @@ -19,6 +19,7 @@ public void configure(Min min) { setMessage(min.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null) { @@ -43,7 +44,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("min", Double.toString(min)); return messageVariables; } diff --git a/framework/src/play/data/validation/MinSizeCheck.java b/framework/src/play/data/validation/MinSizeCheck.java index 0f244b8cb7..94638cc77d 100644 --- a/framework/src/play/data/validation/MinSizeCheck.java +++ b/framework/src/play/data/validation/MinSizeCheck.java @@ -9,7 +9,7 @@ @SuppressWarnings("serial") public class MinSizeCheck extends AbstractAnnotationCheck { - final static String mes = "validation.minSize"; + static final String mes = "validation.minSize"; int minSize; @@ -19,6 +19,7 @@ public void configure(MinSize annotation) { setMessage(annotation.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null || value.toString().length() == 0) { @@ -29,7 +30,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("minSize", Integer.toString(minSize)); return messageVariables; } diff --git a/framework/src/play/data/validation/PhoneCheck.java b/framework/src/play/data/validation/PhoneCheck.java index c85bcd0c13..dd2893468a 100644 --- a/framework/src/play/data/validation/PhoneCheck.java +++ b/framework/src/play/data/validation/PhoneCheck.java @@ -9,7 +9,7 @@ public class PhoneCheck extends AbstractAnnotationCheck { - final static String mes = "validation.phone"; + static final String mes = "validation.phone"; static Pattern phonePattern = Pattern.compile("^([\\+][0-9]{1,3}([ \\.\\-]))?([\\(]{1}[0-9]{1,6}[\\)])?([0-9 \\.\\-/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$"); @@ -18,6 +18,7 @@ public void configure(Phone phone) { setMessage(phone.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) throws OValException { if (value == null || value.toString().length() == 0) { diff --git a/framework/src/play/data/validation/RangeCheck.java b/framework/src/play/data/validation/RangeCheck.java index 1125aac8ed..df64c55882 100644 --- a/framework/src/play/data/validation/RangeCheck.java +++ b/framework/src/play/data/validation/RangeCheck.java @@ -9,7 +9,7 @@ @SuppressWarnings("serial") public class RangeCheck extends AbstractAnnotationCheck { - final static String mes = "validation.range"; + static final String mes = "validation.range"; double min; double max; @@ -21,6 +21,7 @@ public void configure(Range range) { setMessage(range.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { requireMessageVariablesRecreation(); if (value == null) { @@ -46,7 +47,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con @Override public Map createMessageVariables() { - Map messageVariables = new HashMap(); + Map messageVariables = new HashMap<>(); messageVariables.put("min", Double.toString(min)); messageVariables.put("max", Double.toString(max)); return messageVariables; diff --git a/framework/src/play/data/validation/RequiredCheck.java b/framework/src/play/data/validation/RequiredCheck.java index 95116d49e3..75f18f6909 100644 --- a/framework/src/play/data/validation/RequiredCheck.java +++ b/framework/src/play/data/validation/RequiredCheck.java @@ -10,8 +10,9 @@ @SuppressWarnings("serial") public class RequiredCheck extends AbstractAnnotationCheck { - final static String mes = "validation.required"; + static final String mes = "validation.required"; + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { if (value == null) { return false; diff --git a/framework/src/play/data/validation/URLCheck.java b/framework/src/play/data/validation/URLCheck.java index 5774a9bb43..f288c1e801 100644 --- a/framework/src/play/data/validation/URLCheck.java +++ b/framework/src/play/data/validation/URLCheck.java @@ -8,7 +8,7 @@ @SuppressWarnings("serial") public class URLCheck extends AbstractAnnotationCheck { - final static String mes = "validation.url"; + static final String mes = "validation.url"; static Pattern urlPattern = Pattern.compile("^(http|https|ftp)\\://[a-zA-Z0-9\\-\\.]+\\.[a-z" + "A-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\\-\\._\\?\\,\\'/\\\\\\+&%\\$#\\=~\\!])*$"); @@ -17,6 +17,7 @@ public void configure(URL url) { setMessage(url.message()); } + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { if (value == null || value.toString().length() == 0) { return true; diff --git a/framework/src/play/data/validation/UniqueCheck.java b/framework/src/play/data/validation/UniqueCheck.java index 8a5b83b694..fc4613616d 100755 --- a/framework/src/play/data/validation/UniqueCheck.java +++ b/framework/src/play/data/validation/UniqueCheck.java @@ -1,8 +1,5 @@ package play.data.validation; -import java.lang.reflect.Field; -import java.util.Map; -import java.util.TreeMap; import net.sf.oval.Validator; import net.sf.oval.configuration.annotation.AbstractAnnotationCheck; import net.sf.oval.context.FieldContext; @@ -12,13 +9,17 @@ import play.db.jpa.Model; import play.exceptions.UnexpectedException; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.TreeMap; + /** * Check which proof if one or a set of properties is unique. * */ public class UniqueCheck extends AbstractAnnotationCheck { - final static String mes = "validation.unique"; + static final String mes = "validation.unique"; private String uniqueKeyContext = null; @Override @@ -29,7 +30,7 @@ public void configure(Unique constraintAnnotation) { @Override public Map createMessageVariables() { - Map messageVariables = new TreeMap(); + Map messageVariables = new TreeMap<>(); messageVariables.put("2-properties", uniqueKeyContext); return messageVariables; } @@ -55,21 +56,21 @@ public boolean isSatisfied(Object validatedObject, Object value, if (value == null) { return true; } - final String[] propertyNames = getPropertyNames( + String[] propertyNames = getPropertyNames( ((FieldContext) context).getField().getName()); - final GenericModel model = (GenericModel) validatedObject; - final Model.Factory factory = Model.Manager.factoryFor(model.getClass()); - final String keyProperty = factory.keyName(); - final Object keyValue = factory.keyValue(model); + GenericModel model = (GenericModel) validatedObject; + Model.Factory factory = Model.Manager.factoryFor(model.getClass()); + String keyProperty = factory.keyName(); + Object keyValue = factory.keyValue(model); //In case of an update make sure that we won't read the current record from database. - final boolean isUpdate = (keyValue != null); - final String entityName = model.getClass().getName(); - final StringBuffer jpql = new StringBuffer("SELECT COUNT(o) FROM "); + boolean isUpdate = (keyValue != null); + String entityName = model.getClass().getName(); + StringBuilder jpql = new StringBuilder("SELECT COUNT(o) FROM "); jpql.append(entityName).append(" AS o where "); - final Object[] values = new Object[isUpdate ? propertyNames.length + 1 : + Object[] values = new Object[isUpdate ? propertyNames.length + 1 : propertyNames.length]; - final Class clazz = validatedObject.getClass(); - int index = 1; + Class clazz = validatedObject.getClass(); + int index = 1; for (int i = 0; i < propertyNames.length; i++) { Field field = getField(clazz, propertyNames[i]); field.setAccessible(true); @@ -81,11 +82,11 @@ public boolean isSatisfied(Object validatedObject, Object value, if (i > 0) { jpql.append(" And "); } - jpql.append("o.").append(propertyNames[i]).append(" = ?" + String.valueOf(index++) + " "); + jpql.append("o.").append(propertyNames[i]).append(" = ?").append(String.valueOf(index++)).append(" "); } if (isUpdate) { values[propertyNames.length] = keyValue; - jpql.append(" and o.").append(keyProperty).append(" <> ?" + String.valueOf(index++) + " "); + jpql.append(" and o.").append(keyProperty).append(" <> ?").append(String.valueOf(index++)).append(" "); } return JPQL.instance.count(entityName, jpql.toString(), values) == 0L; } @@ -102,7 +103,7 @@ private Field getField(Class clazz, String fieldName) { } } catch (Exception e) { throw new UnexpectedException("Error while determining the field " + - fieldName + " for an object of type " + clazz); + fieldName + " for an object of type " + clazz, e); } throw new UnexpectedException("Cannot get the field " + fieldName + " for an object of type " + clazz); diff --git a/framework/src/play/data/validation/ValidCheck.java b/framework/src/play/data/validation/ValidCheck.java index e25a6fa66e..af6a35a3ae 100644 --- a/framework/src/play/data/validation/ValidCheck.java +++ b/framework/src/play/data/validation/ValidCheck.java @@ -14,9 +14,10 @@ @SuppressWarnings("serial") public class ValidCheck extends AbstractAnnotationCheck { - final static String mes = "validation.object"; + static final String mes = "validation.object"; String key; + @Override public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) { String superKey = ValidationPlugin.keys.get().get(validatedObject); if (value == null) { @@ -41,7 +42,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con key = superKey + "." + key; } if(value instanceof Collection) { - Collection valueCollection = (Collection)value; + Collection valueCollection = (Collection) value; boolean everythingIsValid = true; int index = 0; for(Object item : valueCollection) { @@ -51,11 +52,7 @@ public boolean isSatisfied(Object validatedObject, Object value, OValContext con } index++; } - if(!everythingIsValid) { - return false; - } else { - return true; - } + return everythingIsValid; } else { return validateObject(key, value); } @@ -70,9 +67,9 @@ boolean validateObject(String key, Object value) { } else { for (ConstraintViolation violation : violations) { if (violation.getContext() instanceof FieldContext) { - final FieldContext ctx = (FieldContext) violation.getContext(); - final String fkey = (key == null ? "" : key + ".") + ctx.getField().getName(); - final Error error = new Error( + FieldContext ctx = (FieldContext) violation.getContext(); + String fkey = (key == null ? "" : key + ".") + ctx.getField().getName(); + Error error = new Error( fkey, violation.getMessage(), violation.getMessageVariables() == null ? new String[0] diff --git a/framework/src/play/data/validation/Validation.java b/framework/src/play/data/validation/Validation.java index 8d73d5c563..7042acf39c 100644 --- a/framework/src/play/data/validation/Validation.java +++ b/framework/src/play/data/validation/Validation.java @@ -19,8 +19,8 @@ public class Validation { - public static ThreadLocal current = new ThreadLocal(); - List errors = new ArrayList(); + public static final ThreadLocal current = new ThreadLocal<>(); + List errors = new ArrayList<>(); boolean keep = false; protected Validation() { @@ -58,7 +58,7 @@ public List allForKey(String key) { * @return All errors keyed by field name */ public Map> errorsMap() { - Map> result = new LinkedHashMap>(); + Map> result = new LinkedHashMap<>(); for (Error error : errors()) { result.put(error.key, errors(error.key)); } @@ -168,7 +168,7 @@ public static List errors(String field) { if (validation == null) return Collections.emptyList(); - List errors = new ArrayList(); + List errors = new ArrayList<>(); for (Error error : validation.errors) { if (error.key!=null && error.key.equals(field)) { errors.add(error); @@ -201,14 +201,14 @@ public static void clear() { // ~~~~ Integration helper public static Map> getValidators(Class clazz, String name) { - Map> result = new HashMap>(); + Map> result = new HashMap<>(); searchValidator(clazz, name, result); return result; } public static List getValidators(Class clazz, String property, String name) { try { - List validators = new ArrayList(); + List validators = new ArrayList<>(); while (!clazz.equals(Object.class)) { try { Field field = clazz.getDeclaredField(property); @@ -234,14 +234,14 @@ public static List getValidators(Class clazz, String property, Str } return validators; } catch (Exception e) { - return new ArrayList(); + return new ArrayList<>(); } } static void searchValidator(Class clazz, String name, Map> result) { for (Field field : clazz.getDeclaredFields()) { - List validators = new ArrayList(); + List validators = new ArrayList<>(); String key = name + "." + field.getName(); boolean containsAtValid = false; for (Annotation annotation : field.getDeclaredAnnotations()) { @@ -275,7 +275,7 @@ static void searchValidator(Class clazz, String name, Map params = new HashMap(); + public Map params = new HashMap<>(); public Validator(Annotation annotation) { this.annotation = annotation; diff --git a/framework/src/play/data/validation/ValidationPlugin.java b/framework/src/play/data/validation/ValidationPlugin.java index d0869ff349..be3a8a0584 100644 --- a/framework/src/play/data/validation/ValidationPlugin.java +++ b/framework/src/play/data/validation/ValidationPlugin.java @@ -1,32 +1,33 @@ package play.data.validation; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import net.sf.oval.ConstraintViolation; import net.sf.oval.context.MethodParameterContext; import net.sf.oval.guard.Guard; import play.PlayPlugin; import play.exceptions.ActionNotFoundException; import play.exceptions.UnexpectedException; -import play.utils.Java; import play.mvc.ActionInvoker; import play.mvc.Http; import play.mvc.Http.Cookie; import play.mvc.Scope; import play.mvc.results.Result; +import play.utils.Java; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ValidationPlugin extends PlayPlugin { - public static ThreadLocal> keys = new ThreadLocal>(); + public static final ThreadLocal> keys = new ThreadLocal<>(); private boolean isAwakingFromAwait() { Http.Request request = Http.Request.current(); @@ -70,7 +71,7 @@ public void beforeActionInvocation(Method actionMethod) { return; } List violations = new Validator().validateAction(actionMethod); - ArrayList errors = new ArrayList(); + ArrayList errors = new ArrayList<>(); String[] paramNames = Java.parameterNames(actionMethod); for (ConstraintViolation violation : violations) { errors.add(new Error( @@ -111,7 +112,7 @@ public void invocationFinally() { static class Validator extends Guard { public List validateAction(Method actionMethod) throws Exception { - List violations = new ArrayList(); + List violations = new ArrayList<>(); Object instance = null; // Patch for scala defaults if (!Modifier.isStatic(actionMethod.getModifiers()) && actionMethod.getDeclaringClass().getSimpleName().endsWith("$")) { @@ -137,7 +138,7 @@ static Validation restore() { String errorsData = URLDecoder.decode(cookie.value, "utf-8"); Matcher matcher = errorsParser.matcher(errorsData); while (matcher.find()) { - String[] g2 = matcher.group(2).split("\u0001"); + String[] g2 = matcher.group(2).split("\u0001", -1); String message = g2[0]; String[] args = new String[g2.length - 1]; System.arraycopy(g2, 1, args, 0, args.length); diff --git a/framework/src/play/db/Configuration.java b/framework/src/play/db/Configuration.java index 844681f895..a76661e3d9 100644 --- a/framework/src/play/db/Configuration.java +++ b/framework/src/play/db/Configuration.java @@ -1,125 +1,137 @@ package play.db; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; -import javax.sql.DataSource; - -import play.*; import jregex.Matcher; import jregex.Pattern; +import play.Play; public class Configuration { - + + /** definition of regex to filter db related settings. */ + final String regexDbRelatedSettings = "^(db|javax\\.persistence|jpa|(?:org\\.)?hibernate){1}"; + + /** compiled regex as a pattern for reuse to filter all db related settings. */ + final java.util.regex.Pattern compiledRegexDbRelatedSettings = java.util.regex.Pattern.compile(regexDbRelatedSettings +".*"); + public String configName; - - - public boolean isDefault(){ + + public boolean isDefault() { return DB.DEFAULT.equals(this.configName); } - - public Configuration(String configurationName){ + + public Configuration(String configurationName) { this.configName = configurationName; } - - - public static Set getDbNames() { - TreeSet dbNames = new TreeSet(); - // search for case db= or db.url= as at least one of these property is required - final String DB_CONFIG_PATTERN = "^db\\.([^\\.]*)$|^db\\.([^\\.]*)\\.url$"; - Pattern pattern = new jregex.Pattern(DB_CONFIG_PATTERN); - - // List of properties with 2 words - List dbProperties = Arrays.asList("db.driver", "db.url", "db.user", "db.pass", "db.isolation", "db.destroyMethod", "db.testquery"); - - for (String property : Play.configuration.stringPropertyNames()) { - Matcher m = pattern.matcher(property); - if (m.matches() && !dbProperties.contains(property)) { - String dbName = (m.group(1) != null ? m.group(1) : m.group(2)); - dbNames.add(dbName); - } - // Special case db=... and - if ("db".equals(property) || "db.url".equals(property)) { - dbNames.add("default"); - } - } - return new TreeSet(dbNames); - } - - public String getProperty(String key) { - return this.getProperty(key, null); - } - - - public String getProperty(String key, String defaultString) { - if(key != null){ - String newKey = key; - Pattern pattern = new jregex.Pattern("^(db|jpa|hibernate){1}(\\.)*(.)*$"); - Matcher m = pattern.matcher(key); - if(m.matches() && m.groupCount() > 1){ - newKey = m.group(1) + "." + this.configName + key.substring(m.group(1).length()); - } - Object value = Play.configuration.get(newKey); - if(value == null && this.isDefault()){ - value = Play.configuration.get(key); - } - if(value != null){ - return value.toString(); - } - } - - return defaultString; - } - - /** - * - * @param key - * @param value - * @return the previous value of the specified key in this hashtable, or null if it did not have one - */ - public Object put(String key, String value) { - if(key != null){ - String newKey = key; - Pattern pattern = new jregex.Pattern("^([db|jpa|hibernate]{1})(\\.[\\da-zA-Z.-_]*)$"); - Matcher m = pattern.matcher(key); - if(m.matches()){ - newKey = m.group(0) + "." + this.configName; - } - - return Play.configuration.put(newKey, value); - } - return null; - } - + + public static Set getDbNames() { + TreeSet dbNames = new TreeSet<>(); + // search for case db= or db.url= as at least one of these property is required + String DB_CONFIG_PATTERN = "^db\\.([^\\.]*)$|^db\\.([^\\.]*)\\.url$"; + Pattern pattern = new jregex.Pattern(DB_CONFIG_PATTERN); + + // List of properties with 2 words + List dbProperties = Arrays.asList("db.driver", "db.url", "db.user", "db.pass", "db.isolation", "db.destroyMethod", + "db.testquery"); + + for (String property : Play.configuration.stringPropertyNames()) { + Matcher m = pattern.matcher(property); + if (m.matches() && !dbProperties.contains(property)) { + String dbName = (m.group(1) != null ? m.group(1) : m.group(2)); + dbNames.add(dbName); + } + // Special case db=... and + if ("db".equals(property) || "db.url".equals(property)) { + dbNames.add("default"); + } + } + return new TreeSet<>(dbNames); + } + + public String getProperty(String key) { + return this.getProperty(key, null); + } + + public String getProperty(String key, String defaultString) { + if (key != null) { + String newKey = generateKey(key); + Object value = Play.configuration.get(newKey); + if (value == null && this.isDefault()) { + value = Play.configuration.get(key); + } + if (value != null) { + return value.toString(); + } + } + + return defaultString; + } + + /** + * Add a parameter in the configuration + * + * @param key + * the key of the parameter + * @param value + * the value of the parameter + * @return the previous value of the specified key in this hashtable, or null if it did not have one + */ + public Object put(String key, String value) { + if (key != null) { + return Play.configuration.put(generateKey(key), value); + } + return null; + } + + String generateKey(String key) { + Pattern pattern = new Pattern(regexDbRelatedSettings + "(\\.?[\\da-zA-Z\\.-_]*)$"); + Matcher m = pattern.matcher(key); + if (m.matches()) { + return m.group(1) + "." + this.configName + m.group(2); + } + return key; + } public Map getProperties() { - Map properties = new HashMap(); + Map properties = new HashMap<>(); Properties props = Play.configuration; - + for (Object key : Collections.list(props.keys())) { String keyName = key.toString(); - if (keyName.startsWith("db") || keyName.startsWith("hibernate") ) { - if (keyName.startsWith("db." + this.configName) || keyName.startsWith("hibernate." + this.configName) ) { - String type = keyName.substring(0, keyName.indexOf('.')); + + final java.util.regex.Matcher matcher = compiledRegexDbRelatedSettings.matcher(keyName); + if (matcher.matches()) { + final String key_prefix_for_db_related_setting = matcher.group(1); + if (keyName.startsWith(key_prefix_for_db_related_setting + "." + this.configName)) { + String type = key_prefix_for_db_related_setting; String newKey = type; - if(keyName.length() > (type + "." + this.configName).length()){ - newKey += "." + keyName.substring((type + "." + this.configName).length() + 1); + if (keyName.length() > (type + "." + this.configName).length()) { + newKey += "." + keyName.substring((type + "." + this.configName).length() + 1); } properties.put(newKey, props.get(key).toString()); - }else if(this.isDefault()){ - boolean isDefaultProperty = true; + } else if (this.isDefault()) { + boolean isDefaultProperty = true; Set dBNames = Configuration.getDbNames(); - for(String dbName : dBNames){ - if(key.toString().startsWith("db." + dbName) || key.toString().startsWith("hibernate." + dbName)){ + for (String dbName : dBNames) { + if (keyName.startsWith("db." + dbName) || + keyName.startsWith("hibernate." + dbName)) { isDefaultProperty = false; break; } } - if(isDefaultProperty){ - properties.put(key.toString(), props.get(key).toString()); + if (isDefaultProperty) { + properties.put(keyName, props.get(key).toString()); } } } - } + } return properties; } } diff --git a/framework/src/play/db/DB.java b/framework/src/play/db/DB.java index d913802848..b538b8a242 100644 --- a/framework/src/play/db/DB.java +++ b/framework/src/play/db/DB.java @@ -9,20 +9,20 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; import javax.sql.RowSet; import javax.sql.rowset.CachedRowSet; +import org.hibernate.jpa.HibernateEntityManager; import org.hibernate.internal.SessionImpl; import com.sun.rowset.CachedRowSetImpl; +import play.Logger; import play.db.jpa.JPA; import play.exceptions.DatabaseException; -import play.Logger; - -import java.util.concurrent.ConcurrentHashMap; /** * Database connection utilities. @@ -31,9 +31,10 @@ public class DB { /** * The loaded datasource. + * * @see ExtendedDatasource */ - protected static final Map datasources = new ConcurrentHashMap(); + protected static final Map datasources = new ConcurrentHashMap<>(); public static class ExtendedDatasource { @@ -81,9 +82,9 @@ public DataSource getDataSource() { @Deprecated public static String destroyMethod = ""; - public final static String DEFAULT = "default"; + public static final String DEFAULT = "default"; - static ThreadLocal> localConnection = new ThreadLocal>(); + static final ThreadLocal> localConnection = new ThreadLocal<>(); public static DataSource getDataSource(String name) { if (datasources.get(name) != null) { @@ -119,7 +120,7 @@ private static Connection getLocalConnection(String name) { private static void registerLocalConnection(String name, Connection connection) { Map map = localConnection.get(); if (map == null) { - map = new HashMap(); + map = new HashMap<>(); } map.put(name, connection); localConnection.set(map); @@ -131,7 +132,7 @@ private static void registerLocalConnection(String name, Connection connection) public static void closeAll() { Map map = localConnection.get(); if (map != null) { - Set keySet = new HashSet(map.keySet()); + Set keySet = new HashSet<>(map.keySet()); for (String name : keySet) { close(name); } @@ -147,6 +148,9 @@ public static void close() { /** * Close an given open connections for the current thread + * + * @param name + * Name of the DB */ public static void close(String name) { Map map = localConnection.get(); @@ -158,8 +162,7 @@ public static void close(String name) { try { connection.close(); } catch (Exception e) { - throw new DatabaseException("It's possible than the connection '" + name - + "'was not properly closed !", e); + throw new DatabaseException("It's possible than the connection '" + name + "'was not properly closed !", e); } } } @@ -168,15 +171,17 @@ public static void close(String name) { /** * Open a connection for the current thread. * + * @param name + * Name of the DB * @return A valid SQL connection */ public static Connection getConnection(String name) { try { if (JPA.isEnabled()) { - return ((SessionImpl) ((org.hibernate.ejb.EntityManagerImpl) JPA.em(name)).getSession()).connection(); + return ((SessionImpl) ((HibernateEntityManager) JPA.em(name)).getSession()).connection(); } - final Connection localConnection = getLocalConnection(name); + Connection localConnection = getLocalConnection(name); if (localConnection != null) { return localConnection; } @@ -202,10 +207,13 @@ public static Connection getConnection() { /** * Execute an SQL update - * + * + * @param name + * the DB name * @param SQL - * @return true if the next result is a ResultSet object; false if it is an - * update count or there are no more results + * the SQL statement + * @return true if the next result is a ResultSet object; false if it is an update count or there are no more + * results */ public static boolean execute(String name, String SQL) { Statement statement = null; @@ -222,10 +230,25 @@ public static boolean execute(String name, String SQL) { return false; } + /** + * Execute an SQL update + * + * @param SQL + * the SQL statement + * @return true if the next result is a ResultSet object; false if it is an update count or there are no more + * results + */ public static boolean execute(String SQL) { return execute(DEFAULT, SQL); } + /** + * Execute an SQL query + * + * @param SQL + * the SQL statement + * @return The ResultSet object; false if it is an update count or there are no more results + */ public static RowSet executeQuery(String SQL) { return executeQuery(DEFAULT, SQL); } @@ -233,7 +256,10 @@ public static RowSet executeQuery(String SQL) { /** * Execute an SQL query * + * @param name + * the DB name * @param SQL + * the SQL statement * @return The rowSet of the query */ public static RowSet executeQuery(String name, String SQL) { @@ -281,13 +307,15 @@ public static void safeCloseStatement(Statement statement) { /** * Destroy the datasource + * + * @param name + * the DB name */ public static void destroy(String name) { try { ExtendedDatasource extDatasource = datasources.get(name); if (extDatasource != null && extDatasource.getDestroyMethod() != null) { - Method close = extDatasource.datasource.getClass().getMethod(extDatasource.getDestroyMethod(), - new Class[] {}); + Method close = extDatasource.datasource.getClass().getMethod(extDatasource.getDestroyMethod(), new Class[] {}); if (close != null) { close.invoke(extDatasource.getDataSource(), new Object[] {}); datasources.remove(name); @@ -311,7 +339,7 @@ public static void destroy() { * Destroy all datasources */ public static void destroyAll() { - Set keySet = new HashSet(datasources.keySet()); + Set keySet = new HashSet<>(datasources.keySet()); for (String name : keySet) { destroy(name); } diff --git a/framework/src/play/db/DBPlugin.java b/framework/src/play/db/DBPlugin.java index 23a31321a5..9c7f818d2f 100644 --- a/framework/src/play/db/DBPlugin.java +++ b/framework/src/play/db/DBPlugin.java @@ -22,7 +22,6 @@ import org.apache.commons.lang.StringUtils; import jregex.Matcher; -import org.apache.log4j.Level; import play.Logger; import play.Play; import play.PlayPlugin; @@ -35,9 +34,7 @@ import com.mchange.v2.c3p0.ConnectionCustomizer; import play.db.DB.ExtendedDatasource; -/** - * The DB plugin - */ + public class DBPlugin extends PlayPlugin { public static String url = ""; @@ -49,7 +46,7 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio response.status = Http.StatusCode.FOUND; String serverOptions[] = new String[] { }; - // For H2 embeded database, we'll also start the Web console + // For H2 embedded database, we'll also start the Web console if (h2Server != null) { h2Server.stop(); } @@ -92,7 +89,7 @@ public void onApplicationStart() { Set dbNames = Configuration.getDbNames(); Iterator it = dbNames.iterator(); - while(it.hasNext()) { + while (it.hasNext()) { dbName = it.next(); Configuration dbConfig = new Configuration(dbName); @@ -120,7 +117,7 @@ public void onApplicationStart() { Driver d = (Driver) Class.forName(driver, true, Play.classloader).newInstance(); DriverManager.registerDriver(new ProxyDriver(d)); } catch (Exception e) { - throw new Exception("Database [" + dbName + "] Driver not found (" + driver + ")"); + throw new Exception("Database [" + dbName + "] Driver not found (" + driver + ")", e); } // Try the connection @@ -142,14 +139,29 @@ public void onApplicationStart() { ds.setJdbcUrl(dbConfig.getProperty("db.url")); ds.setUser(dbConfig.getProperty("db.user")); ds.setPassword(dbConfig.getProperty("db.pass")); - ds.setAcquireRetryAttempts(10); + ds.setAcquireIncrement(Integer.parseInt(dbConfig.getProperty("db.pool.acquireIncrement", "3"))); + ds.setAcquireRetryAttempts(Integer.parseInt(dbConfig.getProperty("db.pool.acquireRetryAttempts", "10"))); + ds.setAcquireRetryDelay(Integer.parseInt(dbConfig.getProperty("db.pool.acquireRetryDelay", "1000"))); ds.setCheckoutTimeout(Integer.parseInt(dbConfig.getProperty("db.pool.timeout", "5000"))); - ds.setBreakAfterAcquireFailure(false); + ds.setBreakAfterAcquireFailure(Boolean.parseBoolean(dbConfig.getProperty("db.pool.breakAfterAcquireFailure", "false"))); ds.setMaxPoolSize(Integer.parseInt(dbConfig.getProperty("db.pool.maxSize", "30"))); ds.setMinPoolSize(Integer.parseInt(dbConfig.getProperty("db.pool.minSize", "1"))); + ds.setInitialPoolSize(Integer.parseInt(dbConfig.getProperty("db.pool.initialSize", "1"))); ds.setMaxIdleTimeExcessConnections(Integer.parseInt(dbConfig.getProperty("db.pool.maxIdleTimeExcessConnections", "0"))); - ds.setIdleConnectionTestPeriod(10); - ds.setTestConnectionOnCheckin(true); + ds.setIdleConnectionTestPeriod(Integer.parseInt(dbConfig.getProperty("db.pool.idleConnectionTestPeriod", "10"))); + ds.setMaxIdleTime(Integer.parseInt(dbConfig.getProperty("db.pool.maxIdleTime", "0"))); + ds.setTestConnectionOnCheckin(Boolean.parseBoolean(dbConfig.getProperty("db.pool.testConnectionOnCheckin", "true"))); + ds.setTestConnectionOnCheckout(Boolean.parseBoolean(dbConfig.getProperty("db.pool.testConnectionOnCheckout", "false"))); + ds.setLoginTimeout(Integer.parseInt(dbConfig.getProperty("db.pool.loginTimeout", "0"))); + ds.setMaxAdministrativeTaskTime(Integer.parseInt(dbConfig.getProperty("db.pool.maxAdministrativeTaskTime", "0"))); + ds.setMaxConnectionAge(Integer.parseInt(dbConfig.getProperty("db.pool.maxConnectionAge", "0"))); + ds.setMaxStatements(Integer.parseInt(dbConfig.getProperty("db.pool.maxStatements", "0"))); + ds.setMaxStatementsPerConnection(Integer.parseInt(dbConfig.getProperty("db.pool.maxStatementsPerConnection", "0"))); + ds.setNumHelperThreads(Integer.parseInt(dbConfig.getProperty("db.pool.numHelperThreads", "3"))); + ds.setUnreturnedConnectionTimeout(Integer.parseInt(dbConfig.getProperty("db.pool.unreturnedConnectionTimeout", "0"))); + ds.setDebugUnreturnedConnectionStackTraces(Boolean.parseBoolean(dbConfig.getProperty("db.pool.debugUnreturnedConnectionStackTraces", "false"))); + ds.setContextClassLoaderSource("library"); + ds.setPrivilegeSpawnedThreads(true); if (dbConfig.getProperty("db.testquery") != null) { ds.setPreferredTestQuery(dbConfig.getProperty("db.testquery")); @@ -229,11 +241,18 @@ public String getStatus() { out.println("Jdbc url: " + datasource.getJdbcUrl()); out.println("Jdbc driver: " + datasource.getDriverClass()); out.println("Jdbc user: " + datasource.getUser()); - if (Play.mode.isDev()) { + if (Play.mode.isDev()) { out.println("Jdbc password: " + datasource.getPassword()); } out.println("Min pool size: " + datasource.getMinPoolSize()); out.println("Max pool size: " + datasource.getMaxPoolSize()); + try { + out.println("Busy connection numbers: " + datasource.getNumBusyConnections()); + out.println("Idle connection numbers: " + datasource.getNumIdleConnections()); + out.println("Connection numbers: " + datasource.getNumConnections()); + } catch (SQLException e) { + out.println("Connection status error: " + e.getMessage()); + } out.println("Initial pool size: " + datasource.getInitialPoolSize()); out.println("Checkout timeout: " + datasource.getCheckoutTimeout()); out.println("Test query : " + datasource.getPreferredTestQuery()); @@ -275,7 +294,7 @@ private static boolean changed() { String datasourceName = dbConfig.getProperty("db", ""); DataSource ds = DB.getDataSource(dbName); - if ((datasourceName.startsWith("java:")) && dbConfig.getProperty("db.url") == null) { + if ((datasourceName.startsWith("java:") || datasourceName.startsWith("jndi:")) && dbConfig.getProperty("db.url") == null) { if (ds == null) { return true; } @@ -293,8 +312,8 @@ private static boolean changed() { String name = m.group("name"); String host = m.group("host"); String parameters = m.group("parameters"); - - Map paramMap = new HashMap(); + + Map paramMap = new HashMap<>(); paramMap.put("useUnicode", "yes"); paramMap.put("characterEncoding", "UTF-8"); paramMap.put("connectionCollation", "utf8_general_ci"); @@ -364,24 +383,24 @@ private static boolean changed() { } private static void addParameters(Map paramsMap, String urlQuery) { - if (!StringUtils.isBlank(urlQuery)) { - String[] params = urlQuery.split("[\\&]"); - for (String param : params) { - String[] parts = param.split("[=]"); - if (parts.length > 0 && !StringUtils.isBlank(parts[0])) { - paramsMap.put(parts[0], parts.length > 1 ? StringUtils.stripToNull(parts[1]) : null); - } - } - } + if (!StringUtils.isBlank(urlQuery)) { + String[] params = urlQuery.split("[\\&]"); + for (String param : params) { + String[] parts = param.split("[=]"); + if (parts.length > 0 && !StringUtils.isBlank(parts[0])) { + paramsMap.put(parts[0], parts.length > 1 ? StringUtils.stripToNull(parts[1]) : null); + } + } + } } private static String toQueryString(Map paramMap) { - StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : paramMap.entrySet()) { - if (builder.length() > 0) builder.append("&"); - builder.append(entry.getKey()).append("=").append(entry.getValue() != null ? entry.getValue() : ""); - } - return builder.toString(); + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : paramMap.entrySet()) { + if (builder.length() > 0) builder.append("&"); + builder.append(entry.getKey()).append("=").append(entry.getValue() != null ? entry.getValue() : ""); + } + return builder.toString(); } /** @@ -395,26 +414,32 @@ public static class ProxyDriver implements Driver { this.driver = d; } + @Override public boolean acceptsURL(String u) throws SQLException { return this.driver.acceptsURL(u); } + @Override public Connection connect(String u, Properties p) throws SQLException { return this.driver.connect(u, p); } + @Override public int getMajorVersion() { return this.driver.getMajorVersion(); } + @Override public int getMinorVersion() { return this.driver.getMinorVersion(); } + @Override public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException { return this.driver.getPropertyInfo(u, p); } + @Override public boolean jdbcCompliant() { return this.driver.jdbcCompliant(); } @@ -422,6 +447,7 @@ public boolean jdbcCompliant() { // Method not annotated with @Override since getParentLogger() is a new method // in the CommonDataSource interface starting with JDK7 and this annotation // would cause compilation errors with JDK6. + @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { try { return (java.util.logging.Logger) Driver.class.getDeclaredMethod("getParentLogger").invoke(this.driver); @@ -436,7 +462,7 @@ public static class PlayConnectionCustomizer implements ConnectionCustomizer { public static Map isolationLevels; static { - isolationLevels = new HashMap(); + isolationLevels = new HashMap<>(); isolationLevels.put("NONE", Connection.TRANSACTION_NONE); isolationLevels.put("READ_UNCOMMITTED", Connection.TRANSACTION_READ_UNCOMMITTED); isolationLevels.put("READ_COMMITTED", Connection.TRANSACTION_READ_COMMITTED); @@ -444,6 +470,7 @@ public static class PlayConnectionCustomizer implements ConnectionCustomizer { isolationLevels.put("SERIALIZABLE", Connection.TRANSACTION_SERIALIZABLE); } + @Override public void onAcquire(Connection c, String parentDataSourceIdentityToken) { Integer isolation = getIsolationLevel(); if (isolation != null) { @@ -456,8 +483,13 @@ public void onAcquire(Connection c, String parentDataSourceIdentityToken) { } } + @Override public void onDestroy(Connection c, String parentDataSourceIdentityToken) {} + + @Override public void onCheckOut(Connection c, String parentDataSourceIdentityToken) {} + + @Override public void onCheckIn(Connection c, String parentDataSourceIdentityToken) {} /** diff --git a/framework/src/play/db/Evolutions.java b/framework/src/play/db/Evolutions.java index e046a77e43..94256f2605 100644 --- a/framework/src/play/db/Evolutions.java +++ b/framework/src/play/db/Evolutions.java @@ -1,5 +1,18 @@ package play.db; +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Stack; + import org.apache.commons.lang.StringUtils; import play.Logger; @@ -19,22 +32,6 @@ import play.mvc.results.Redirect; import play.vfs.VirtualFile; -import java.io.File; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Stack; - - /** * Handles migration of data. * @@ -44,25 +41,18 @@ public class Evolutions extends PlayPlugin { private static String EVOLUTIONS_TABLE_NAME = "play_evolutions"; protected static File evolutionsDirectory = Play.getFile("db/evolutions"); - - private static Map modulesWithEvolutions = new LinkedHashMap(); + private static Map modulesWithEvolutions = new LinkedHashMap<>(); public static void main(String[] args) throws SQLException { /** Start the DB plugin **/ - Play.id = System.getProperty("play.id"); - Play.applicationPath = new File(System.getProperty("application.path")); Play.guessFrameworkPath(); Play.readConfiguration(); - Play.javaPath = new ArrayList(); Play.classes = new ApplicationClasses(); Play.classloader = new ApplicationClassloader(); - Play.templatesPath = new ArrayList(); - Play.modulesRoutes = new HashMap(); Play.loadModules(VirtualFile.open(Play.applicationPath)); - if (System.getProperty("modules") != null) { populateModulesWithSpecificModules(); } else { @@ -78,11 +68,11 @@ public static void main(String[] args) throws SQLException { Logger.init(); Logger.setUp("ERROR"); new DBPlugin().onApplicationStart(); - + // Look over all the DB Set dBNames = Configuration.getDbNames(); boolean defaultExitCode = true; - + for (String dbName : dBNames) { Configuration dbConfig = new Configuration(dbName); /** Connected **/ @@ -111,22 +101,21 @@ public static void main(String[] args) throws SQLException { System.out.println(""); System.out.println(e.getEvolutionScript()); System.out.println(""); - System.out.println("~ The following error occured:"); + System.out.println("~ The following error occurred:"); System.out.println(""); System.out.println(e.getError()); System.out.println(""); - System.out - .println("~ Please correct it manually, and mark it resolved by running `play evolutions:resolve`"); + System.out.println("~ Please correct it manually, and mark it resolved by running `play evolutions:resolve`"); System.out.println("~"); continue; } catch (InvalidDatabaseRevision e) { // see later } - System.out.print("~ '" + moduleRoot.getKey() + "' Application revision is " + application.revision - + " [" + application.hash.substring(0, 7) + "]"); - System.out.println(" and '" + moduleRoot.getKey() + "' Database revision is " + database.revision - + " [" + database.hash.substring(0, 7) + "]"); + System.out.print("~ '" + moduleRoot.getKey() + "' Application revision is " + application.revision + " [" + + application.hash.substring(0, 7) + "]"); + System.out.println(" and '" + moduleRoot.getKey() + "' Database revision is " + database.revision + " [" + + database.hash.substring(0, 7) + "]"); System.out.println("~"); /** Evolution script **/ @@ -151,43 +140,46 @@ public static void main(String[] args) throws SQLException { } } } - + if (!defaultExitCode) { System.exit(-1); } } + /** * Method to handle the "default" action - * @param dbName : database name - * @param moduleRoot : the module root of evolutions - * @param evolutions : list of evolutions + * + * @param dbName + * database name + * @param moduleRoot + * the module root of evolutions + * @param evolutions + * list of evolutions */ - private static void handleDefaultAction(String dbName, Entry moduleRoot, List evolutions){ + private static void handleDefaultAction(String dbName, Entry moduleRoot, List evolutions) { System.out.println("~ Your database " + dbName + " needs evolutions for " + moduleRoot.getKey() + "!"); System.out.println(""); - System.out - .println("# ------------------------------------------------------------------------------"); + System.out.println("# ------------------------------------------------------------------------------"); System.out.println(""); System.out.println(toHumanReadableScript(evolutions)); System.out.println(""); - System.out - .println("# ------------------------------------------------------------------------------"); + System.out.println("# ------------------------------------------------------------------------------"); System.out.println(""); - System.out - .println("~ Run `play evolutions:apply` to automatically apply this script to the database"); - System.out - .println("~ or apply it yourself and mark it done using `play evolutions:markApplied`"); - System.out.println("~"); + System.out.println("~ Run `play evolutions:apply` to automatically apply this script to the database"); + System.out.println("~ or apply it yourself and mark it done using `play evolutions:markApplied`"); + System.out.println("~"); } - + /** * Method to handle the "resolve" action - * @param dbName : database name - * @param moduleRoot : the module root of evolutions - * @param evolutions : list of evolutions + * + * @param dbName + * database name + * @param moduleRoot + * the module root of evolutions * @return true if need to check, false otherwise */ - private static boolean handleResolveAction(String dbName, Entry moduleRoot){ + private static boolean handleResolveAction(String dbName, Entry moduleRoot) { try { checkEvolutionsState(dbName); System.out.println("~"); @@ -197,37 +189,37 @@ private static boolean handleResolveAction(String dbName, Entry moduleRoot, List evolutions){ + private static boolean handleApplyAction(String dbName, Entry moduleRoot, List evolutions) { System.out.println("~ Applying evolutions for " + moduleRoot.getKey() + ":"); System.out.println(""); - System.out - .println("# ------------------------------------------------------------------------------"); + System.out.println("# ------------------------------------------------------------------------------"); System.out.println(""); System.out.println(toHumanReadableScript(evolutions)); System.out.println(""); - System.out - .println("# ------------------------------------------------------------------------------"); + System.out.println("# ------------------------------------------------------------------------------"); System.out.println(""); if (applyScript(dbName, true, moduleRoot.getKey(), moduleRoot.getValue())) { System.out.println("~"); - System.out.println("~ Evolutions script successfully applied for " + moduleRoot.getKey() - + "!"); + System.out.println("~ Evolutions script successfully applied for " + moduleRoot.getKey() + "!"); System.out.println("~"); return true; } else { @@ -237,18 +229,21 @@ private static boolean handleApplyAction(String dbName, Entry moduleRoot, List evolutions){ + private static boolean handleMarkAppliedAction(String dbName, Entry moduleRoot, List evolutions) { if (applyScript(dbName, false, moduleRoot.getKey(), moduleRoot.getValue())) { - System.out - .println("~ Evolutions script marked as applied for " + moduleRoot.getKey() + "!"); + System.out.println("~ Evolutions script marked as applied for " + moduleRoot.getKey() + "!"); System.out.println("~"); return true; } else { @@ -257,7 +252,6 @@ private static boolean handleMarkAppliedAction(String dbName, Entry moduleRoot : Play.modules.entrySet()) { - if(moduleRoot.getValue().child("db/evolutions").exists()) { - if(!isModuleEvolutionDisabled(moduleRoot.getKey())){ + if (!isModuleEvolutionDisabled()) { + for (Entry moduleRoot : Play.modules.entrySet()) { + if (moduleRoot.getValue().child("db/evolutions").exists()) { + if (!isModuleEvolutionDisabled(moduleRoot.getKey())) { modulesWithEvolutions.put(moduleRoot.getKey(), moduleRoot.getValue().child("db/evolutions")); } else { System.out.println("~ '" + moduleRoot.getKey() + "' module evolutions are disabled."); } } } - }else{ + } else { System.out.println("~ Module evolutions are disabled."); } @@ -319,18 +314,17 @@ private static void addMainProjectToModuleList() { } } - @Override public boolean rawInvocation(Request request, Response response) throws Exception { // Mark an evolution as resolved - if (Play.mode.isDev() && request.method.equals("POST") && request.url.matches("^/@evolutions/force/[a-zA-Z0-9]+/[0-9]+$")) { + if (Play.mode.isDev() && request.method.equals("POST") && request.url.matches("^/@evolutions/force/[a-zA-Z0-9]+/[0-9]+$")) { int index = request.url.lastIndexOf("/@evolutions/force/") + "/@evolutions/force/".length(); - + String dbName = DB.DEFAULT; String moduleKey = request.url.substring(index, request.url.lastIndexOf("/")); int revision = Integer.parseInt(request.url.substring(request.url.lastIndexOf("/") + 1)); - + resolve(dbName, moduleKey, revision); new Redirect("/").apply(request, response); return true; @@ -338,10 +332,10 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio // Apply the current evolution script if (Play.mode.isDev() && request.method.equals("POST") && request.url.equals("/@evolutions/apply")) { - - for(Entry moduleRoot : modulesWithEvolutions.entrySet()) { - applyScript(true, moduleRoot.getKey(), moduleRoot.getValue()); - } + + for (Entry moduleRoot : modulesWithEvolutions.entrySet()) { + applyScript(true, moduleRoot.getKey(), moduleRoot.getValue()); + } new Redirect("/").apply(request, response); return true; } @@ -350,7 +344,7 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio @Override public void beforeInvocation() { - if(isDisabled() || Play.mode.isProd()) { + if (isDisabled() || Play.mode.isProd()) { return; } try { @@ -359,7 +353,7 @@ public void beforeInvocation() { Set dbNames = Configuration.getDbNames(); for (String dbName : dbNames) { Configuration dbConfig = new Configuration(dbName); - + for (Entry moduleRoot : modulesWithEvolutions.entrySet()) { if ("mem".equals(dbConfig.getProperty("db")) && listDatabaseEvolutions(e.getDbName(), moduleRoot.getKey()).peek().revision == 0) { @@ -369,7 +363,7 @@ && listDatabaseEvolutions(e.getDbName(), moduleRoot.getKey()).peek().revision == } else { throw e; } - } + } } } } @@ -378,7 +372,7 @@ && listDatabaseEvolutions(e.getDbName(), moduleRoot.getKey()).peek().revision == public void onApplicationStart() { if (!isDisabled()) { populateModulesWithEvolutions(); - if ( Play.mode.isProd()) { + if (Play.mode.isProd()) { try { checkEvolutionsState(); } catch (InvalidDatabaseRevision e) { @@ -388,7 +382,7 @@ public void onApplicationStart() { throw e; } } - } + } } /** @@ -397,20 +391,19 @@ public void onApplicationStart() { private boolean isDisabled() { return "false".equals(Play.configuration.getProperty("evolutions.enabled", "true")); } - + private static boolean isModuleEvolutionDisabled() { - return "false".equals(Play.configuration.getProperty("modules.evolutions.enabled", "true")); + return "false".equals(Play.configuration.getProperty("modules.evolutions.enabled", "true")); } - + private static boolean isModuleEvolutionDisabled(String name) { - return "false".equals(Play.configuration.getProperty(name + ".evolutions.enabled", "true")); + return "false".equals(Play.configuration.getProperty(name + ".evolutions.enabled", "true")); } public static boolean autoCommit() { - return ! "false".equals(Play.configuration.getProperty("evolutions.autocommit", "true")); + return !"false".equals(Play.configuration.getProperty("evolutions.autocommit", "true")); } - - + public static synchronized void resolve(int revision) { try { EvolutionQuery.resolve(DB.DEFAULT, revision, Play.configuration.getProperty("application.name")); @@ -418,7 +411,7 @@ public static synchronized void resolve(int revision) { throw new UnexpectedException(e); } } - + public static synchronized void resolve(String dBName, int revision) { try { EvolutionQuery.resolve(dBName, revision, Play.configuration.getProperty("application.name")); @@ -427,7 +420,6 @@ public static synchronized void resolve(String dBName, int revision) { } } - public static synchronized void resolve(String dBName, String moduleKey, int revision) { try { EvolutionQuery.resolve(dBName, revision, moduleKey); @@ -436,16 +428,15 @@ public static synchronized void resolve(String dBName, String moduleKey, int rev } } - public static synchronized boolean applyScript(boolean runScript, String moduleKey, VirtualFile evolutionsDirectory) { + public static synchronized boolean applyScript(boolean runScript, String moduleKey, VirtualFile evolutionsDirectory) { // Look over all the DB Set dBNames = Configuration.getDbNames(); - for(String dbName: dBNames){ + for (String dbName : dBNames) { return applyScript(dbName, runScript, moduleKey, evolutionsDirectory); } return true; } - - + public static synchronized boolean applyScript(String dbName, boolean runScript, String moduleKey, VirtualFile evolutionsDirectory) { try { Connection connection = EvolutionQuery.getNewConnection(dbName, Evolutions.autoCommit()); @@ -453,31 +444,31 @@ public static synchronized boolean applyScript(String dbName, boolean runScript, try { List evolutions = getEvolutionScript(dbName, moduleKey, evolutionsDirectory); for (Evolution evolution : evolutions) { - applying = evolution.revision; - EvolutionQuery.apply(connection, runScript, evolution, moduleKey); + applying = evolution.revision; + EvolutionQuery.apply(connection, runScript, evolution, moduleKey); } - - if(!Evolutions.autoCommit()) { + + if (!Evolutions.autoCommit()) { connection.commit(); } - + return true; } catch (Exception e) { - Logger.error(e, "Can't apply evolution"); - if(Evolutions.autoCommit()) { - String message = e.getMessage(); - if (e instanceof SQLException) { - SQLException ex = (SQLException) e; - message += " [ERROR:" + ex.getErrorCode() + ", SQLSTATE:" + ex.getSQLState() + "]"; - } - - EvolutionQuery.setProblem(connection, applying, moduleKey, message); - } else { - connection.rollback(); - } + Logger.error(e, "Can't apply evolution"); + if (Evolutions.autoCommit()) { + String message = e.getMessage(); + if (e instanceof SQLException) { + SQLException ex = (SQLException) e; + message += " [ERROR:" + ex.getErrorCode() + ", SQLSTATE:" + ex.getSQLState() + "]"; + } + + EvolutionQuery.setProblem(connection, applying, moduleKey, message); + } else { + connection.rollback(); + } return false; } finally { - EvolutionQuery.closeConnection(connection); + EvolutionQuery.closeConnection(connection); } } catch (Exception e) { throw new UnexpectedException(e); @@ -492,54 +483,57 @@ public static String toHumanReadableScript(List evolutionScript) { if (!evolution.applyUp) { containsDown = true; } - sql.append("# --- Rev:").append(evolution.revision).append(",").append(evolution.applyUp ? "Ups" : "Downs").append(" - ").append(evolution.hash.substring(0, 7)).append("\n"); + sql.append("# --- Rev:").append(evolution.revision).append(",").append(evolution.applyUp ? "Ups" : "Downs").append(" - ") + .append(evolution.hash.substring(0, 7)).append("\n"); sql.append("\n"); sql.append(evolution.applyUp ? evolution.sql_up : evolution.sql_down); sql.append("\n\n"); } if (containsDown) { - sql.insert(0, "# !!! WARNING! This script contains DOWNS evolutions that are likely destructives\n\n"); + sql.insert(0, "# !!! WARNING! This script contains DOWNS evolutions that are likely destructive\n\n"); } return sql.toString().trim(); } - - public synchronized static void checkEvolutionsState() { + + public static synchronized void checkEvolutionsState() { // Look over all the DB Set dBNames = Configuration.getDbNames(); - for(String dbName: dBNames){ + for (String dbName : dBNames) { checkEvolutionsState(dbName); } } - - public synchronized static void checkEvolutionsState(String dbName) { - for(Entry moduleRoot : modulesWithEvolutions.entrySet()) { + public static synchronized void checkEvolutionsState(String dbName) { + for (Entry moduleRoot : modulesWithEvolutions.entrySet()) { if (DB.getDataSource(dbName) != null) { List evolutionScript = getEvolutionScript(dbName, moduleRoot.getKey(), moduleRoot.getValue()); Connection connection = null; + ResultSet resultSet = null; try { - connection = EvolutionQuery.getNewConnection(dbName); - ResultSet rs = EvolutionQuery.getEvolutionsToApply(connection, moduleRoot.getKey()); - if (rs.next()) { - int revision = rs.getInt("id"); - String state = rs.getString("state"); - String hash = rs.getString("hash").substring(0, 7); + connection = EvolutionQuery.getNewConnection(dbName); + resultSet = EvolutionQuery.getEvolutionsToApply(connection, moduleRoot.getKey()); + if (resultSet.next()) { + int revision = resultSet.getInt("id"); + String state = resultSet.getString("state"); + String hash = resultSet.getString("hash").substring(0, 7); String script = ""; if (EvolutionState.APPLYING_UP.getStateWord().equals(state)) { - script = rs.getString("apply_script"); + script = resultSet.getString("apply_script"); } else { - script = rs.getString("revert_script"); + script = resultSet.getString("revert_script"); } - script = "# --- Rev:" + revision + "," + (EvolutionState.APPLYING_UP.getStateWord().equals(state) ? "Ups" : "Downs") + " - " + hash + "\n\n" + script; - String error = rs.getString("last_problem"); + script = "# --- Rev:" + revision + "," + (EvolutionState.APPLYING_UP.getStateWord().equals(state) ? "Ups" : "Downs") + + " - " + hash + "\n\n" + script; + String error = resultSet.getString("last_problem"); throw new InconsistentDatabase(dbName, script, error, revision, moduleRoot.getKey()); } } catch (SQLException e) { throw new UnexpectedException(e); } finally { + EvolutionQuery.closeResultSet(resultSet); EvolutionQuery.closeConnection(connection); } @@ -550,11 +544,11 @@ public synchronized static void checkEvolutionsState(String dbName) { } } - public synchronized static List getEvolutionScript(String dbName, String moduleKey, VirtualFile evolutionsDirectory) { + public static synchronized List getEvolutionScript(String dbName, String moduleKey, VirtualFile evolutionsDirectory) { Stack app = listApplicationEvolutions(dbName, moduleKey, evolutionsDirectory); Stack db = listDatabaseEvolutions(dbName, moduleKey); - List downs = new ArrayList(); - List ups = new ArrayList(); + List downs = new ArrayList<>(); + List ups = new ArrayList<>(); // Apply non conflicting evolutions (ups and downs) while (db.peek().revision != app.peek().revision) { @@ -574,35 +568,37 @@ public synchronized static List getEvolutionScript(String dbName, Str // Ups need to be applied earlier first Collections.reverse(ups); - List script = new ArrayList(); + List script = new ArrayList<>(); script.addAll(downs); script.addAll(ups); return script; } - public synchronized static Stack listApplicationEvolutions(String dBName, String moduleKey, VirtualFile evolutionsDirectory) { - Stack evolutions = new Stack(); + public static synchronized Stack listApplicationEvolutions(String dBName, String moduleKey, + VirtualFile evolutionsDirectory) { + Stack evolutions = new Stack<>(); evolutions.add(new Evolution("", 0, "", "", true)); if (evolutionsDirectory.exists()) { for (File evolution : evolutionsDirectory.getRealFile().listFiles()) { - if (evolution.getName().matches("^"+ dBName + ".[0-9]+[.]sql$") || - (DB.DEFAULT.equals(dBName) && evolution.getName().matches("^[0-9]+[.]sql$") )) { + if (evolution.getName().matches("^" + dBName + ".[0-9]+[.]sql$") + || (DB.DEFAULT.equals(dBName) && evolution.getName().matches("^[0-9]+[.]sql$"))) { if (Logger.isTraceEnabled()) { Logger.trace("Loading evolution %s", evolution); } int version = 0; - if(evolution.getName().contains(dBName)){ - version = Integer.parseInt(evolution.getName().substring(evolution.getName().indexOf(".") + 1, evolution.getName().lastIndexOf("."))); + if (evolution.getName().contains(dBName)) { + version = Integer.parseInt( + evolution.getName().substring(evolution.getName().indexOf(".") + 1, evolution.getName().lastIndexOf("."))); } else { version = Integer.parseInt(evolution.getName().substring(0, evolution.getName().indexOf("."))); } - + String sql = IO.readContentAsString(evolution); - StringBuffer sql_up = new StringBuffer(); - StringBuffer sql_down = new StringBuffer(); - StringBuffer current = new StringBuffer(); + StringBuilder sql_up = new StringBuilder(); + StringBuilder sql_down = new StringBuilder(); + StringBuilder current = new StringBuilder(); for (String line : sql.split("\r?\n")) { if (line.trim().matches("^#.*[!]Ups")) { current = sql_up; @@ -621,47 +617,51 @@ public synchronized static Stack listApplicationEvolutions(String dBN } return evolutions; } - - private static boolean isEvolutionsTableExist(Connection connection){ + + private static boolean isEvolutionsTableExist(Connection connection) { String tableName = EVOLUTIONS_TABLE_NAME; + ResultSet resultSet = null; try { - ResultSet rs = connection.getMetaData().getTables(null, null, tableName, null); - if (!rs.next()) { + resultSet = connection.getMetaData().getTables(null, null, tableName, null); + if (!resultSet.next()) { // Table in lowercase does not exist // oracle gives table names in upper case tableName = tableName.toUpperCase(); Logger.trace("Checking " + tableName); - rs.close(); - rs = connection.getMetaData().getTables(null, null, tableName, null); + resultSet.close(); + resultSet = connection.getMetaData().getTables(null, null, tableName, null); // Does it exist? - if (!rs.next()) { + if (!resultSet.next()) { // did not find it in uppercase either return false; } } } catch (SQLException e) { Logger.error(e, "SQL error while checking if play evolutions exist"); + } finally { + EvolutionQuery.closeResultSet(resultSet); } - return true; + return true; } - public synchronized static Stack listDatabaseEvolutions(String dbName, String moduleKey) { - Stack evolutions = new Stack(); + public static synchronized Stack listDatabaseEvolutions(String dbName, String moduleKey) { + Stack evolutions = new Stack<>(); evolutions.add(new Evolution("", 0, "", "", false)); Connection connection = null; try { - connection = EvolutionQuery.getNewConnection(dbName); + connection = EvolutionQuery.getNewConnection(dbName); // Do we have a - if (isEvolutionsTableExist(connection) ) { - checkAndUpdateEvolutionsForMultiModuleSupport(dbName, connection); + if (isEvolutionsTableExist(connection)) { + checkAndUpdateEvolutionsForMultiModuleSupport(dbName, connection); ResultSet databaseEvolutions = EvolutionQuery.getEvolutions(connection, moduleKey); - + while (databaseEvolutions.next()) { - Evolution evolution = new Evolution(moduleKey, databaseEvolutions.getInt(1), databaseEvolutions.getString(3), databaseEvolutions.getString(4), false); + Evolution evolution = new Evolution(moduleKey, databaseEvolutions.getInt(1), databaseEvolutions.getString(3), + databaseEvolutions.getString(4), false); evolutions.add(evolution); } - + } else { EvolutionQuery.createTable(dbName); } @@ -673,7 +673,7 @@ public synchronized static Stack listDatabaseEvolutions(String dbName Collections.sort(evolutions); return evolutions; } - + private static void checkAndUpdateEvolutionsForMultiModuleSupport(String dbName, Connection connection) throws SQLException { ResultSet rs = connection.getMetaData().getColumns(null, null, "play_evolutions", "module_key"); if (!rs.next()) { @@ -681,5 +681,5 @@ private static void checkAndUpdateEvolutionsForMultiModuleSupport(String dbName, EvolutionQuery.alterForModuleSupport(dbName, connection); } } - + } diff --git a/framework/src/play/db/SQLSplitter.java b/framework/src/play/db/SQLSplitter.java index cb10c7f153..dd1117a71f 100644 --- a/framework/src/play/db/SQLSplitter.java +++ b/framework/src/play/db/SQLSplitter.java @@ -1,213 +1,238 @@ package play.db; -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; public class SQLSplitter implements Iterable { - /** - * Skips the index past the quote. - * - * @param s The string - * @param start The starting character of the quote. - * - * @return The index that skips past the quote starting at start. If the quote does not start at that point, it simply returns start. - */ - static int consumeQuote(final CharSequence s, final int start) { - if ( start >= s.length() ) return start; - char ender; - switch ( s.charAt(start) ) { - case '\'': - ender = '\''; - break; - case '"': - ender = '"'; - break; - case '[': - ender = ']'; - break; - - case '`': - ender = '`'; - break; - case '$': { - int quoteEnd = start + 1; - for ( ; s.charAt(quoteEnd) != '$'; ++quoteEnd ) - if ( quoteEnd >= s.length() ) - return quoteEnd; - int i = quoteEnd + 1; - while ( i < s.length() ) { - if ( s.charAt(i) == '$' ) { - boolean match = true; - for ( int j = start; j <= quoteEnd && i < s.length(); ++j, ++i ) { - if ( s.charAt(i) != s.charAt(j) ) { - match = false; - break; - } - } - if ( match ) - return i; - } else - ++i; - } - return i; - } - - default: - return start; - } - - boolean escaped = false; - - for ( int i = start + 1; i < s.length(); ++i ) { - if ( escaped ) { - escaped = false; - continue; - } - char c = s.charAt(i); - if ( c == '\\' ) - escaped = true; - else if ( c == ender ) - return i + 1; - } - return s.length(); - } - - static boolean isNewLine(char c) { - return c == '\n' || c == '\r'; - } - - /** - * Returns the index of the next line from a start location. - */ - static int consumeTillNextLine(final CharSequence s, int start) { - while ( start < s.length() && !isNewLine(s.charAt(start)) ) - ++start; - while ( start < s.length() && isNewLine(s.charAt(start)) ) - ++start; - return start; - } - - static boolean isNext(final CharSequence s, final int start, final char c) { - if ( start + 1 < s.length() ) - return s.charAt(start + 1) == c; - return false; - } - - /** - * Skips the index past the comment. - * - * @param s The string - * @param start The starting character of the comment - * - * @return The index that skips past the comment starting at start. If the comment does not start at that point, it simply returns start. - */ - static int consumeComment(final CharSequence s, int start) { - if ( start >= s.length() ) return start; - switch ( s.charAt(start) ) { - case '-': - if ( isNext(s, start, '-') ) - return consumeTillNextLine(s, start + 2); - else - return start; - - case '#': - return consumeTillNextLine(s, start); - - case '/': - if ( isNext(s, start, '*') ) { - start += 2; - while ( start < s.length() ) { - if ( s.charAt(start) == '*' ) { - ++start; - if ( start < s.length() && s.charAt(start) == '/' ) - return start + 1; - } else - ++start; - } - } - return start; - - case '{': - while ( start < s.length() && s.charAt(start) != '}' ) - ++start; - return start + 1; - - default: - return start; - } - } - - static int consumeParentheses(final CharSequence s, int start) { - if ( start >= s.length() ) return start; - switch ( s.charAt(start) ) { - case '(': - ++start; - while ( start < s.length() ) { - if ( s.charAt(start) == ')' ) - return start + 1; - start = nextChar(s, start); - } - break; - default: - break; - } - return start; - } - - static int nextChar(final CharSequence sql, final int start) { - int i = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, start))); - if ( i == start ) return Math.min(start + 1, sql.length()); - do { - final int j = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, i))); - if ( j == i ) - return i; - i = j; - } while ( true ); - } - - /** - * Splits the SQL "properly" based on semicolons. Respecting quotes and comments. - */ - public static ArrayList splitSQL(final CharSequence sql) { - final ArrayList ret = new ArrayList(); - for ( CharSequence c : new SQLSplitter(sql) ) - ret.add(c); - return ret; - } - - final CharSequence sql; - - public SQLSplitter(final CharSequence sql) { - this.sql = sql; - } - - public Iterator iterator() { - return new Iterator() { - int i = 0, prev = 0; - - public boolean hasNext() { - return prev < sql.length(); - } - - public CharSequence next() { - while ( i < sql.length() ) { - if ( sql.charAt(i) == ';' ) { - ++i; - CharSequence ret = sql.subSequence(prev, i); - prev = i; - return ret; - } - i = nextChar(sql, i); - } - if ( prev != i ) { - CharSequence ret = sql.subSequence(prev, i); - prev = i; - return ret; - } - throw new NoSuchElementException(); - } - - public void remove() { throw new UnsupportedOperationException(); } - }; - } + /** + * Skips the index past the quote. + * + * @param s + * The string + * @param start + * The starting character of the quote. + * @return The index that skips past the quote starting at start. If the quote does not start at that point, it + * simply returns start. + */ + static int consumeQuote(CharSequence s, int start) { + if (start >= s.length()) + return start; + char ender; + switch (s.charAt(start)) { + case '\'': + ender = '\''; + break; + case '"': + ender = '"'; + break; + case '[': + ender = ']'; + break; + + case '`': + ender = '`'; + break; + case '$': { + int quoteEnd = start + 1; + for (; s.charAt(quoteEnd) != '$'; ++quoteEnd) + if (quoteEnd >= s.length()) + return quoteEnd; + int i = quoteEnd + 1; + while (i < s.length()) { + if (s.charAt(i) == '$') { + boolean match = true; + for (int j = start; j <= quoteEnd && i < s.length(); ++j, ++i) { + if (s.charAt(i) != s.charAt(j)) { + match = false; + break; + } + } + if (match) + return i; + } else + ++i; + } + return i; + } + + default: + return start; + } + + boolean escaped = false; + + for (int i = start + 1; i < s.length(); ++i) { + if (escaped) { + escaped = false; + continue; + } + char c = s.charAt(i); + if (c == '\\') + escaped = true; + else if (c == ender) + return i + 1; + } + return s.length(); + } + + static boolean isNewLine(char c) { + return c == '\n' || c == '\r'; + } + + /** + * Returns the index of the next line from a start location. + */ + static int consumeTillNextLine(CharSequence s, int start) { + while (start < s.length() && !isNewLine(s.charAt(start))) + ++start; + while (start < s.length() && isNewLine(s.charAt(start))) + ++start; + return start; + } + + static boolean isNext(CharSequence s, int start, char c) { + if (start + 1 < s.length()) + return s.charAt(start + 1) == c; + return false; + } + + /** + * Skips the index past the comment. + * + * @param s + * The string + * @param start + * The starting character of the comment + * @return The index that skips past the comment starting at start. If the comment does not start at that point, it + * simply returns start. + */ + static int consumeComment(CharSequence s, int start) { + if (start >= s.length()) + return start; + switch (s.charAt(start)) { + case '-': + if (isNext(s, start, '-')) + return consumeTillNextLine(s, start + 2); + else + return start; + + case '#': + return consumeTillNextLine(s, start); + + case '/': + if (isNext(s, start, '*')) { + start += 2; + while (start < s.length()) { + if (s.charAt(start) == '*') { + ++start; + if (start < s.length() && s.charAt(start) == '/') + return start + 1; + } else + ++start; + } + } + return start; + + case '{': + while (start < s.length() && s.charAt(start) != '}') + ++start; + return start + 1; + + default: + return start; + } + } + + static int consumeParentheses(CharSequence s, int start) { + if (start >= s.length()) + return start; + switch (s.charAt(start)) { + case '(': + ++start; + while (start < s.length()) { + if (s.charAt(start) == ')') + return start + 1; + start = nextChar(s, start); + } + break; + default: + break; + } + return start; + } + + static int nextChar(CharSequence sql, int start) { + int i = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, start))); + if (i == start) + return Math.min(start + 1, sql.length()); + do { + int j = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, i))); + if (j == i) + return i; + i = j; + } while (true); + } + + /** + * Splits the SQL "properly" based on semicolons. Respecting quotes and comments. + * + * @param sql + * the SQL statement + * @return List of all SQL statements + */ + public static ArrayList splitSQL(CharSequence sql) { + ArrayList ret = new ArrayList<>(); + for (CharSequence c : new SQLSplitter(sql)) + ret.add(c); + return ret; + } + + final CharSequence sql; + + public SQLSplitter(CharSequence sql) { + this.sql = sql; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int i = 0, prev = 0; + + @Override + public boolean hasNext() { + return prev < sql.length(); + } + + @Override + public CharSequence next() { + while (i < sql.length()) { + if (sql.charAt(i) == ';') { + ++i; + // check "double semicolon" -> used to escape a semicolon and avoid splitting + if ((i < sql.length() && sql.charAt(i) == ';')) { + ++i; + } else { + CharSequence ret = sql.subSequence(prev, i).toString().replace(";;", ";"); + prev = i; + return ret; + } + } + i = nextChar(sql, i); + } + if (prev != i) { + CharSequence ret = sql.subSequence(prev, i).toString().replace(";;", ";"); + prev = i; + return ret; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } } diff --git a/framework/src/play/db/evolutions/Evolution.java b/framework/src/play/db/evolutions/Evolution.java index 1d87d4c394..c167c4ff10 100644 --- a/framework/src/play/db/evolutions/Evolution.java +++ b/framework/src/play/db/evolutions/Evolution.java @@ -12,28 +12,27 @@ public class Evolution implements Comparable { public String moduleKey; - public Evolution(String moduleKey, int revision, String sql_up, String sql_down, - boolean applyUp) { - this.moduleKey = moduleKey; - this.revision = revision; - this.sql_down = sql_down; - this.sql_up = sql_up; - this.hash = Codec.hexSHA1(sql_up + sql_down); - this.applyUp = applyUp; + public Evolution(String moduleKey, int revision, String sql_up, String sql_down, boolean applyUp) { + this.moduleKey = moduleKey; + this.revision = revision; + this.sql_down = sql_down; + this.sql_up = sql_up; + this.hash = Codec.hexSHA1(sql_up + sql_down); + this.applyUp = applyUp; } + @Override public int compareTo(Evolution o) { - return this.revision - o.revision; + return this.revision - o.revision; } @Override public boolean equals(Object obj) { - return (obj instanceof Evolution) - && ((Evolution) obj).revision == this.revision; + return (obj instanceof Evolution) && ((Evolution) obj).revision == this.revision; } @Override public int hashCode() { - return revision; + return revision; } } \ No newline at end of file diff --git a/framework/src/play/db/evolutions/EvolutionQuery.java b/framework/src/play/db/evolutions/EvolutionQuery.java index c361509ec9..20e89b263f 100644 --- a/framework/src/play/db/evolutions/EvolutionQuery.java +++ b/framework/src/play/db/evolutions/EvolutionQuery.java @@ -73,7 +73,7 @@ public static void alterForModuleSupport(String dbName, Connection connection) t } public static void resolve(String dbName, int revision, String moduleKey) throws SQLException { - Connection connection = getNewConnection(dbName); + Connection connection = getNewConnection(dbName); PreparedStatement ps = connection.prepareStatement("update play_evolutions set state = ?, last_problem = ? where state = ? and id = ? and module_key = ?" ); ps.setString(1, EvolutionState.APPLIED.getStateWord() ); ps.setString(2, ""); @@ -114,7 +114,7 @@ public static void apply(Connection connection, boolean runScript, Evolution evo // Execute script if (runScript) { for (CharSequence sql : new SQLSplitter((evolution.applyUp ? evolution.sql_up : evolution.sql_down))) { - final String s = sql.toString().trim(); + String s = sql.toString().trim(); if (StringUtils.isEmpty(s)) { continue; } @@ -138,13 +138,12 @@ public static void apply(Connection connection, boolean runScript, Evolution evo } public static void setProblem(Connection connection, int applying, - String moduleKey, String message) throws SQLException { + String moduleKey, String message) throws SQLException { PreparedStatement ps = connection.prepareStatement("update play_evolutions set last_problem = ? where id = ? and module_key = ?"); ps.setString(1, message); ps.setInt(2, applying); ps.setString(3, moduleKey); ps.execute(); - } @@ -252,14 +251,14 @@ public static void closeConnection(Connection connection) { } } - private synchronized static boolean isOracleDialectInUse(String dbName) { + private static synchronized boolean isOracleDialectInUse(String dbName) { boolean isOracle = false; Configuration dbConfig = new Configuration(dbName); String jpaDialect = JPAPlugin.getDefaultDialect(dbConfig.getProperty("db.driver")); if (jpaDialect != null) { try { Class dialectClass = Play.classloader.loadClass(jpaDialect); - + // Oracle 8i dialect is the base class for oracle dialects (at least for now) isOracle = org.hibernate.dialect.Oracle8iDialect.class.isAssignableFrom(dialectClass); } catch (ClassNotFoundException e) { diff --git a/framework/src/play/db/evolutions/EvolutionState.java b/framework/src/play/db/evolutions/EvolutionState.java index 9156d8abd7..5cebdef112 100644 --- a/framework/src/play/db/evolutions/EvolutionState.java +++ b/framework/src/play/db/evolutions/EvolutionState.java @@ -1,9 +1,9 @@ package play.db.evolutions; -public enum EvolutionState{ - APPLIED, APPLYING_UP, APPLYING_DOWN; - - public String getStateWord(){ - return this.name().toLowerCase(); - } - } +public enum EvolutionState { + APPLIED, APPLYING_UP, APPLYING_DOWN; + + public String getStateWord() { + return this.name().toLowerCase(); + } +} diff --git a/framework/src/play/db/helper/JdbcIterator.java b/framework/src/play/db/helper/JdbcIterator.java index 22ebf3dd02..6568472a92 100644 --- a/framework/src/play/db/helper/JdbcIterator.java +++ b/framework/src/play/db/helper/JdbcIterator.java @@ -17,7 +17,7 @@ public static JdbcIterator execute(SqlQuery query, Class resultClass) public static JdbcIterator execute(SqlQuery query, JdbcResultFactory factory) { try { - return new JdbcIterator(JdbcHelper.execute(query), factory); + return new JdbcIterator<>(JdbcHelper.execute(query), factory); } catch (SQLException ex) { throw new RuntimeException(ex); } @@ -40,7 +40,7 @@ public JdbcIterator(ResultSet result, Class resultClass) throws SQLException this(result, JdbcResultFactories.build(resultClass)); } - + @Override public void close() { if (result != null) { try { @@ -68,11 +68,13 @@ protected void load() { } } + @Override public boolean hasNext() { load(); return next != null; } + @Override public T next() { load(); T e = next; @@ -80,10 +82,12 @@ public T next() { return e; } + @Override public void remove() { throw new UnsupportedOperationException(); } + @Override public Iterator iterator() { return this; } diff --git a/framework/src/play/db/helper/JdbcResultFactories.java b/framework/src/play/db/helper/JdbcResultFactories.java index 65b285c39e..89c77fe684 100644 --- a/framework/src/play/db/helper/JdbcResultFactories.java +++ b/framework/src/play/db/helper/JdbcResultFactories.java @@ -31,8 +31,8 @@ public static JdbcResultFactory build(Class objectClass, List || objectClass == Long.class || objectClass == Float.class || objectClass == Double.class - ? new PrimitiveFactory(objectClass, fields) - : new ClassFactory(objectClass, fields); + ? new PrimitiveFactory<>(objectClass, fields) + : new ClassFactory<>(objectClass, fields); } @@ -42,11 +42,11 @@ public static JdbcResultFactory buildPrimitive(Class objectClass) { } public static JdbcResultFactory buildPrimitive(Class objectClass, int columnIndex) { - return new PrimitiveFactory(objectClass, columnIndex); + return new PrimitiveFactory<>(objectClass, columnIndex); } public static JdbcResultFactory buildPrimitive(Class objectClass, String field) { - return new PrimitiveFactory(objectClass, field); + return new PrimitiveFactory<>(objectClass, field); } @@ -60,7 +60,7 @@ public static JdbcResultFactory buildClass(Class objectClass, String . } public static JdbcResultFactory buildClass(Class objectClass, List fields) { - return new ClassFactory(objectClass, fields); + return new ClassFactory<>(objectClass, fields); } @@ -89,6 +89,7 @@ public PrimitiveFactory(Class objectClass, List fields) { this.columnIndex = 1; } + @Override public void init(ResultSet result) throws SQLException { if (field != null) { ResultSetMetaData meta = result.getMetaData(); @@ -104,6 +105,7 @@ public void init(ResultSet result) throws SQLException { } @SuppressWarnings("unchecked") + @Override public T create(ResultSet result) throws SQLException { Object value = result.getObject(columnIndex); if (value instanceof BigDecimal) value = new Long(((BigDecimal)value).longValue()); @@ -123,9 +125,10 @@ public ClassFactory(Class objectClass, List fields) { this.fields = fields; } + @Override public void init(ResultSet result) throws SQLException { if (fields == null) { - fields = new ArrayList(); + fields = new ArrayList<>(); ResultSetMetaData meta = result.getMetaData(); int count = meta.getColumnCount(); for (int i = 1; i <= count; i++) { @@ -135,6 +138,7 @@ public void init(ResultSet result) throws SQLException { } } + @Override public T create(ResultSet result) throws SQLException { try { T obj = objectClass.newInstance(); @@ -144,11 +148,7 @@ public T create(ResultSet result) throws SQLException { objectClass.getDeclaredField(field).set(obj, value); } return obj; - } catch (InstantiationException ex) { - throw new RuntimeException(ex); - } catch (NoSuchFieldException ex) { - throw new RuntimeException(ex); - } catch (IllegalAccessException ex) { + } catch (InstantiationException | NoSuchFieldException | IllegalAccessException ex) { throw new RuntimeException(ex); } } diff --git a/framework/src/play/db/helper/SqlQuery.java b/framework/src/play/db/helper/SqlQuery.java index e01246e0e2..97383ead8a 100644 --- a/framework/src/play/db/helper/SqlQuery.java +++ b/framework/src/play/db/helper/SqlQuery.java @@ -8,7 +8,7 @@ public abstract class SqlQuery { protected List params; protected SqlQuery() { - params = new ArrayList(); + params = new ArrayList<>(); } public SqlQuery param(Object obj) { params.add(obj); return this; } @@ -60,7 +60,7 @@ public Concat separator(String separator) { } public Concat append(Object obj) { - final String text; + String text; if (obj != null) { String objStr = obj.toString(); if (objStr.length() > 0) text = objStr; diff --git a/framework/src/play/db/jpa/Blob.java b/framework/src/play/db/jpa/Blob.java index aad4cc3655..f29c503245 100644 --- a/framework/src/play/db/jpa/Blob.java +++ b/framework/src/play/db/jpa/Blob.java @@ -11,6 +11,7 @@ import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.StringType; import org.hibernate.usertype.UserType; @@ -32,9 +33,17 @@ private Blob(String UUID, String type) { this.UUID = UUID; this.type = type; } - + + private Blob(String coded) { + if (coded != null && coded.length() > 0 && coded.contains("|")) { + this.UUID = coded.split("[|]")[0]; + this.type = coded.split("[|]")[1]; + } + } + + @Override public InputStream get() { - if(exists()) { + if (exists()) { try { return new FileInputStream(getFile()); } catch(Exception e) { @@ -43,21 +52,25 @@ public InputStream get() { } return null; } - + + @Override public void set(InputStream is, String type) { this.UUID = Codec.UUID(); this.type = type; IO.write(is, getFile()); } + @Override public long length() { return getFile().length(); } + @Override public String type() { return type; } + @Override public boolean exists() { return UUID != null && getFile().exists(); } @@ -73,12 +86,12 @@ public String getUUID() { return UUID; } - // - + @Override public int[] sqlTypes() { return new int[] {Types.VARCHAR}; } + @Override public Class returnedClass() { return Blob.class; } @@ -87,6 +100,7 @@ private static boolean equal(Object a, Object b) { return a == b || (a != null && a.equals(b)); } + @Override public boolean equals(Object o, Object o1) throws HibernateException { if(o instanceof Blob && o1 instanceof Blob) { return equal(((Blob)o).UUID, ((Blob)o1).UUID) && @@ -95,26 +109,31 @@ public boolean equals(Object o, Object o1) throws HibernateException { return equal(o, o1); } + @Override public int hashCode(Object o) throws HibernateException { return o.hashCode(); } - public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor sessionImplementor, Object o) throws HibernateException, SQLException { - String val = (String) StringType.INSTANCE.nullSafeGet(resultSet, names[0], sessionImplementor, o); - if(val == null || val.length() == 0 || !val.contains("|")) { - return new Blob(); - } - return new Blob(val.split("[|]")[0], val.split("[|]")[1]); + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException { + String val = (String) StringType.INSTANCE.nullSafeGet(rs, names[0], session, owner); + return new Blob(val); } - public void nullSafeSet(PreparedStatement ps, Object o, int i, SessionImplementor sessionImplementor) throws HibernateException, SQLException { - if(o != null) { - ps.setString(i, ((Blob)o).UUID + "|" + ((Blob)o).type); + @Override + public void nullSafeSet(PreparedStatement ps, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { + if (value != null) { + ps.setString(index, encode((Blob) value)); } else { - ps.setNull(i, Types.VARCHAR); + ps.setNull(index, Types.VARCHAR); } } + private String encode(Blob o) { + return o.UUID != null ? o.UUID + "|" + o.type : null; + } + + @Override public Object deepCopy(Object o) throws HibernateException { if(o == null) { return null; @@ -122,37 +141,39 @@ public Object deepCopy(Object o) throws HibernateException { return new Blob(((Blob)o).UUID, ((Blob)o).type); } + @Override public boolean isMutable() { return true; } + @Override public Serializable disassemble(Object o) throws HibernateException { - throw new UnsupportedOperationException("Not supported yet."); + if (o == null) return null; + return encode((Blob) o); } - public Object assemble(Serializable srlzbl, Object o) throws HibernateException { - throw new UnsupportedOperationException("Not supported yet."); + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + if (cached == null) return null; + return new Blob((String) cached); } - public Object replace(Object o, Object o1, Object o2) throws HibernateException { - throw new UnsupportedOperationException("Not supported yet."); + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return original; } - // - public static String getUUID(String dbValue) { return dbValue.split("[|]")[0]; } public static File getStore() { String name = Play.configuration.getProperty("attachments.path", "attachments"); - File store = null; - if(new File(name).isAbsolute()) { - store = new File(name); - } else { + File store = new File(name); + if (!store.isAbsolute()) { store = Play.getFile(name); } - if(!store.exists()) { + if (!store.exists()) { store.mkdirs(); } return store; diff --git a/framework/src/play/db/jpa/GenericModel.java b/framework/src/play/db/jpa/GenericModel.java index ac3d58912b..f9031f9d4f 100644 --- a/framework/src/play/db/jpa/GenericModel.java +++ b/framework/src/play/db/jpa/GenericModel.java @@ -1,10 +1,30 @@ package play.db.jpa; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; -import java.util.*; - -import javax.persistence.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.NoResultException; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.PostLoad; +import javax.persistence.PostPersist; +import javax.persistence.PostUpdate; +import javax.persistence.Query; import org.apache.commons.lang.StringUtils; @@ -17,27 +37,50 @@ import play.exceptions.UnexpectedException; import play.mvc.Scope.Params; -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; - /** - * A super class for JPA entities + * A super class for JPA entities */ @MappedSuperclass @SuppressWarnings("unchecked") public class GenericModel extends JPABase { /** - * This method is deprecated. Use this instead: - * - * public static T create(ParamNode rootParamNode, String name, Class type, Annotation[] annotations) + * Create a new model + * + * @param type + * the class of the object + * @param name + * name of the object + * @param params + * parameters used to create the new object + * @param annotations + * annotations on the model + * @param + * The entity class + * @return The created entity + * @deprecated use method {{@link #create(ParamNode, String, Class, Annotation[]) }} */ @Deprecated public static T create(Class type, String name, Map params, Annotation[] annotations) { ParamNode rootParamNode = ParamNode.convert(params); - return (T)create(rootParamNode, name, type, annotations); + return (T) create(rootParamNode, name, type, annotations); } + /** + * Create a new model + * + * @param rootParamNode + * parameters used to create the new object + * @param name + * name of the object + * @param type + * the class of the object + * @param annotations + * annotations on the model + * @param + * The entity class + * @return The created entity + */ public static T create(ParamNode rootParamNode, String name, Class type, Annotation[] annotations) { try { Constructor c = type.getDeclaredConstructor(); @@ -50,35 +93,76 @@ public static T create(ParamNode rootParamNode, String name, } /** - * This method is deprecated. Use this instead: - * - * public static T edit(ParamNode rootParamNode, String name, Object o, Annotation[] annotations) - * - * @see GenericModel#edit(ParamNode, String, Object, Annotation[]) + * Edit a model * + * @param o + * Object to edit + * @param name + * name of the object + * @param params + * list of parameters + * @param annotations + * annotations on the model + * @param + * class of the entity * @return the entity + * + * @see GenericModel#edit(ParamNode, String, Object, Annotation[]) + * @deprecated use method {{@link GenericModel#edit(ParamNode, String, Object, Annotation[]) }} */ @Deprecated public static T edit(Object o, String name, Map params, Annotation[] annotations) { ParamNode rootParamNode = ParamNode.convert(params); - return (T)edit( rootParamNode, name, o, annotations); + return (T) edit(rootParamNode, name, o, annotations); } + /** + * Edit a model + * + * @param rootParamNode + * parameters used to create the new object + * @param name + * name of the object + * @param o + * the entity to update + * @param annotations + * annotations on the model + * @param + * class of the entity + * @return the entity + */ public static T edit(ParamNode rootParamNode, String name, Object o, Annotation[] annotations) { return edit(JPA.DEFAULT, rootParamNode, name, o, annotations); } + /** + * Edit a model + * + * @param dbName + * the db name + * @param rootParamNode + * parameters used to create the new object + * @param name + * name of the object + * @param o + * the entity to update + * @param annotations + * annotations on the model + * @param + * class of the entity + * @return the entity + */ public static T edit(String dbName, ParamNode rootParamNode, String name, Object o, Annotation[] annotations) { // #1601 - If name is empty, we're dealing with "root" request parameters (without prefixes). // Must not call rootParamNode.getChild in that case, as it returns null. Use rootParamNode itself instead. ParamNode paramNode = StringUtils.isEmpty(name) ? rootParamNode : rootParamNode.getChild(name, true); // #1195 - Needs to keep track of whick keys we remove so that we can restore it before // returning from this method. - List removedNodesList = new ArrayList(); + List removedNodesList = new ArrayList<>(); try { BeanWrapper bw = new BeanWrapper(o.getClass()); // Start with relations - Set fields = new HashSet(); + Set fields = new HashSet<>(); Class clazz = o.getClass(); while (!clazz.equals(Object.class)) { Collections.addAll(fields, clazz.getDeclaredFields()); @@ -88,15 +172,16 @@ public static T edit(String dbName, ParamNode rootParamNode, boolean isEntity = false; String relation = null; boolean multiple = false; - - // First try the field + + // First try the field Annotation[] fieldAnnotations = field.getAnnotations(); // and check with the profiles annotations - final BindingAnnotations bindingAnnotations = new BindingAnnotations(fieldAnnotations, new BindingAnnotations(annotations).getProfiles()); + BindingAnnotations bindingAnnotations = new BindingAnnotations(fieldAnnotations, + new BindingAnnotations(annotations).getProfiles()); if (bindingAnnotations.checkNoBinding()) { continue; } - + if (field.isAnnotationPresent(OneToOne.class) || field.isAnnotationPresent(ManyToOne.class)) { isEntity = true; relation = field.getType().getName(); @@ -130,9 +215,10 @@ public static T edit(String dbName, ParamNode rootParamNode, if (_id == null || _id.equals("")) { continue; } - + Query q = JPA.em(dbName).createQuery("from " + relation + " where " + keyName + " = ?1"); - q.setParameter(1, Binder.directBind(rootParamNode.getOriginalKey(), annotations,_id, Model.Manager.factoryFor((Class) Play.classloader.loadClass(relation)).keyType(), null)); + q.setParameter(1, Binder.directBind(rootParamNode.getOriginalKey(), annotations, _id, + Model.Manager.factoryFor((Class) Play.classloader.loadClass(relation)).keyType(), null)); try { l.add(q.getSingleResult()); @@ -147,21 +233,22 @@ public static T edit(String dbName, ParamNode rootParamNode, if (ids != null && ids.length > 0 && !ids[0].equals("")) { Query q = JPA.em(dbName).createQuery("from " + relation + " where " + keyName + " = ?1"); - q.setParameter(1, Binder.directBind(rootParamNode.getOriginalKey(), annotations, ids[0], Model.Manager.factoryFor((Class) Play.classloader.loadClass(relation)).keyType(), null)); + q.setParameter(1, Binder.directBind(rootParamNode.getOriginalKey(), annotations, ids[0], + Model.Manager.factoryFor((Class) Play.classloader.loadClass(relation)).keyType(), null)); try { Object to = q.getSingleResult(); edit(paramNode, field.getName(), to, field.getAnnotations()); // Remove it to prevent us from finding it again later - paramNode.removeChild( field.getName(), removedNodesList); + paramNode.removeChild(field.getName(), removedNodesList); bw.set(field.getName(), o, to); } catch (NoResultException e) { Validation.addError(fieldParamNode.getOriginalKey(), "validation.notFound", ids[0]); // Remove only the key to prevent us from finding it again later // This how the old impl does it.. fieldParamNode.removeChild(keyName, removedNodesList); - if (fieldParamNode.getAllChildren().size()==0) { + if (fieldParamNode.getAllChildren().size() == 0) { // remove the whole node.. - paramNode.removeChild( field.getName(), removedNodesList); + paramNode.removeChild(field.getName(), removedNodesList); } } @@ -184,32 +271,68 @@ public static T edit(String dbName, ParamNode rootParamNode, throw new UnexpectedException(e); } finally { // restoring changes to paramNode - ParamNode.restoreRemovedChildren( removedNodesList ); + ParamNode.restoreRemovedChildren(removedNodesList); } } /** - * This method is deprecated. Use this instead: - * - * public T edit(ParamNode rootParamNode, String name) + * Edit a model + * + * @param name + * name of the entity + * @param params + * list of parameters + * @param + * class of the entity + * @return the entity + * + * @deprecated use method {{@link #edit(ParamNode, String)}} */ @Deprecated public T edit(String name, Map params) { ParamNode rootParamNode = ParamNode.convert(params); - return (T)edit(rootParamNode, name, this, null); + return (T) edit(rootParamNode, name, this, null); } + /** + * Edit a model + * + * @param rootParamNode + * parameters used to create the new object + * @param name + * name of the entity + * @param + * class of the entity + * @return the entity + */ public T edit(ParamNode rootParamNode, String name) { edit(rootParamNode, name, this, null); return (T) this; } + /** + * Edit a model + * + * @param dbName + * the db name + * @param rootParamNode + * parameters used to create the new object + * @param name + * name of the entity + * @param + * class of the entity + * @return the entity + */ public T edit(String dbName, ParamNode rootParamNode, String name) { edit(dbName, rootParamNode, name, this, null); return (T) this; } - - + + /** + * Validate and store the entity + * + * @return true if successful + */ public boolean validateAndSave() { if (Validation.current().valid(this).ok) { save(); @@ -218,6 +341,11 @@ public boolean validateAndSave() { return false; } + /** + * Validate and create the entity + * + * @return true if successful + */ public boolean validateAndCreate() { if (Validation.current().valid(this).ok) { return create(); @@ -227,6 +355,10 @@ public boolean validateAndCreate() { /** * store (ie insert) the entity. + * + * @param + * class of the entity + * @return true if successful */ public T save() { _save(); @@ -235,6 +367,8 @@ public T save() { /** * store (ie insert) the entity. + * + * @return true if successful */ public boolean create() { if (!em(JPA.getDBName(this.getClass())).contains(this)) { @@ -246,6 +380,10 @@ public boolean create() { /** * Refresh the entity state. + * + * @param + * class of the entity + * @return The given entity */ public T refresh() { em(JPA.getDBName(this.getClass())).refresh(this); @@ -253,7 +391,11 @@ public T refresh() { } /** - * Merge this object to obtain a managed entity (usefull when the object comes from the Cache). + * Merge this object to obtain a managed entity (useful when the object comes from the Cache). + * + * @param + * class of the entity + * @return The given entity */ public T merge() { return (T) em(JPA.getDBName(this.getClass())).merge(this); @@ -261,6 +403,9 @@ public T merge() { /** * Delete the entity. + * + * @param + * class of the entity * @return The deleted entity. */ public T delete() { @@ -268,12 +413,24 @@ public T delete() { return (T) this; } + /** + * Create the new entity + * + * @param name + * name of the model + * @param params + * list of parameters + * @param + * class of the entity + * @return The created entity. + */ public static T create(String name, Params params) { throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation."); } /** * Count entities + * * @return number of entities of this class */ public static long count() { @@ -281,10 +438,12 @@ public static long count() { } /** - * Count entities with a special query. - * Example : Long moderatedPosts = Post.count("moderated", true); - * @param query HQL query or shortcut - * @param params Params to bind to the query + * Count entities with a special query. Example : Long moderatedPosts = Post.count("moderated", true); + * + * @param query + * HQL query or shortcut + * @param params + * Params to bind to the query * @return A long */ public static long count(String query, Object... params) { @@ -293,6 +452,10 @@ public static long count(String query, Object... params) { /** * Find all entities of this type + * + * @param + * the type of the entity + * @return All entities of this type */ public static List findAll() { throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation."); @@ -300,7 +463,11 @@ public static List findAll() { /** * Find the entity with the corresponding id. - * @param id The entity id + * + * @param id + * The entity id + * @param + * the type of the entity * @return The entity */ public static T findById(Object id) { @@ -309,8 +476,11 @@ public static T findById(Object id) { /** * Prepare a query to find entities. - * @param query HQL query or shortcut - * @param params Params to bind to the query + * + * @param query + * HQL query or shortcut + * @param params + * Params to bind to the query * @return A JPAQuery */ public static JPAQuery find(String query, Object... params) { @@ -319,6 +489,7 @@ public static JPAQuery find(String query, Object... params) { /** * Prepare a query to find *all* entities. + * * @return A JPAQuery */ public static JPAQuery all() { @@ -327,8 +498,11 @@ public static JPAQuery all() { /** * Batch delete of entities - * @param query HQL query or shortcut - * @param params Params to bind to the query + * + * @param query + * HQL query or shortcut + * @param params + * Params to bind to the query * @return Number of entities deleted */ public static int delete(String query, Object... params) { @@ -337,6 +511,7 @@ public static int delete(String query, Object... params) { /** * Delete all entities + * * @return Number of entities deleted */ public static int deleteAll() { @@ -374,9 +549,15 @@ public T first() { } /** - * Bind a JPQL named parameter to the current query. - * Careful, this will also bind count results. This means that Integer get transformed into long - * so hibernate can do the right thing. Use the setParameter if you just want to set parameters. + * Bind a JPQL named parameter to the current query. Careful, this will also bind count results. This means that + * Integer get transformed into long so hibernate can do the right thing. Use the setParameter if you just want + * to set parameters. + * + * @param name + * name of the object + * @param param + * current query + * @return The query */ public JPAQuery bind(String name, Object param) { if (param.getClass().isArray()) { @@ -389,16 +570,25 @@ public JPAQuery bind(String name, Object param) { return this; } - /** - * Set a named parameter for this query. - **/ - public JPAQuery setParameter(String name, Object param) { - query.setParameter(name, param); - return this; - } + /** + * Set a named parameter for this query. + * + * @param name + * Parameter name + * @param param + * The given parameters + * @return The query + */ + public JPAQuery setParameter(String name, Object param) { + query.setParameter(name, param); + return this; + } /** * Retrieve all results of the query + * + * @param + * the type of the entity * @return A list of entities */ public List fetch() { @@ -411,7 +601,11 @@ public List fetch() { /** * Retrieve results of the query - * @param max Max results to fetch + * + * @param max + * Max results to fetch + * @param + * The entity class * @return A list of entities */ public List fetch(int max) { @@ -425,7 +619,11 @@ public List fetch(int max) { /** * Set the position to start - * @param position Position of the first element + * + * @param position + * Position of the first element + * @param + * The entity class * @return A new query */ public JPAQuery from(int position) { @@ -435,8 +633,13 @@ public JPAQuery from(int position) { /** * Retrieve a page of result - * @param page Page number (start at 1) - * @param length (page length) + * + * @param page + * Page number (start at 1) + * @param length + * (page length) + * @param + * The entity class * @return a list of entities */ public List fetch(int page, int length) { diff --git a/framework/src/play/db/jpa/HibernateInterceptor.java b/framework/src/play/db/jpa/HibernateInterceptor.java index 711c24ef88..dcd71f007f 100644 --- a/framework/src/play/db/jpa/HibernateInterceptor.java +++ b/framework/src/play/db/jpa/HibernateInterceptor.java @@ -74,7 +74,7 @@ public boolean onCollectionRemove(Object collection, Serializable key) throws Ca return super.onCollectionRemove(collection, key); } - protected ThreadLocal entities = new ThreadLocal(); + protected final ThreadLocal entities = new ThreadLocal<>(); @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { diff --git a/framework/src/play/db/jpa/JPA.java b/framework/src/play/db/jpa/JPA.java index 88ec511dff..a1fd986b58 100644 --- a/framework/src/play/db/jpa/JPA.java +++ b/framework/src/play/db/jpa/JPA.java @@ -1,21 +1,21 @@ package play.db.jpa; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.PersistenceException; import javax.persistence.PersistenceUnit; -import play.exceptions.JPAException; +import play.Invoker.InvocationContext; +import play.Invoker.Suspend; +import play.Logger; import play.Play; -import play.Invoker.*; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.Map; - -import javax.persistence.*; - import play.db.DB; -import play.Logger; +import play.exceptions.JPAException; import play.libs.F; /** @@ -23,11 +23,12 @@ */ public class JPA { - protected static Map emfs = new ConcurrentHashMap(); + protected static Map emfs = new ConcurrentHashMap<>(); public static final ThreadLocal> currentEntityManager = new ThreadLocal>() { - @Override protected Map initialValue() { - return new ConcurrentHashMap(); - } + @Override + protected Map initialValue() { + return new ConcurrentHashMap<>(); + } }; public static String DEFAULT = "default"; @@ -38,7 +39,7 @@ public static class JPAContext { public boolean autoCommit = false; } - public static boolean isInitialized(){ + public static boolean isInitialized() { return get(DEFAULT) != null; } @@ -51,6 +52,8 @@ static JPAContext get(String name) { } /** + * Clear a DB context + * * @deprecated Use clearContext instead * @since 1.3.0 * @see #clearContext(String) @@ -59,10 +62,12 @@ static JPAContext get(String name) { static void clearContext() { get().clear(); } - + /** + * Clear a DB context * - * @param name : the DB name + * @param name + * the DB name */ static void clearContext(String name) { get().remove(name); @@ -71,7 +76,7 @@ static void clearContext(String name) { static void createContext(EntityManager entityManager, boolean readonly) { createContext(JPA.DEFAULT, entityManager, readonly); } - + static void createContext(String dbName, EntityManager entityManager, boolean readonly) { if (isInitialized()) { try { @@ -81,33 +86,46 @@ static void createContext(String dbName, EntityManager entityManager, boolean re } clearContext(dbName); } - bindForCurrentThread(dbName, entityManager, readonly); + bindForCurrentThread(dbName, entityManager, readonly); } public static EntityManager newEntityManager(String key) { JPAPlugin jpaPlugin = Play.plugin(JPAPlugin.class); - if(jpaPlugin == null) { + if (jpaPlugin == null) { throw new JPAException("No JPA Plugin."); } EntityManager em = jpaPlugin.em(key); - if(em == null) { + if (em == null) { throw new JPAException("No JPA EntityManagerFactory configured for name [" + key + "]"); } return em; } + /** * Get the EntityManager for specified persistence unit for this thread. + * + * @param key + * The DB name + * + * @return The EntityManager */ public static EntityManager em(String key) { - JPAContext jpaContext = get(key); - if (jpaContext == null) - throw new JPAException("No active EntityManager for name [" + key + "], transaction not started?"); - return jpaContext.entityManager; + JPAContext jpaContext = get(key); + if (jpaContext == null) + throw new JPAException("No active EntityManager for name [" + key + "], transaction not started?"); + return jpaContext.entityManager; } - /** + /** * Bind an EntityManager to the current thread. + * + * @param name + * The DB name + * @param em + * The EntityManager + * @param readonly + * indicate if it is in read only mode */ public static void bindForCurrentThread(String name, EntityManager em, boolean readonly) { JPAContext context = new JPAContext(); @@ -136,11 +154,11 @@ public static EntityManager em() { * Tell to JPA do not commit the current transaction */ public static void setRollbackOnly() { - setRollbackOnly(DEFAULT); + setRollbackOnly(DEFAULT); } public static void setRollbackOnly(String em) { - get(em).entityManager.getTransaction().setRollbackOnly(); + get(em).entityManager.getTransaction().setRollbackOnly(); } /** @@ -156,6 +174,10 @@ public static boolean isEnabled(String em) { /** * Execute a JPQL query + * + * @param query + * The query to execute + * @return The result code */ public static int execute(String query) { return execute(DEFAULT, query); @@ -165,16 +187,15 @@ public static int execute(String em, String query) { return em(em).createQuery(query).executeUpdate(); } - - // * Build a new entityManager. - // * (In most case you want to use the local entityManager with em) - + // * Build a new entityManager. + // * (In most case you want to use the local entityManager with em) + public static EntityManager newEntityManager() { return createEntityManager(); } public static EntityManager createEntityManager() { - return createEntityManager(JPA.DEFAULT); + return createEntityManager(JPA.DEFAULT); } public static EntityManager createEntityManager(String name) { @@ -197,9 +218,9 @@ public static boolean isInsideTransaction(String name) { } public static T withinFilter(F.Function0 block) throws Throwable { - if(InvocationContext.current().getAnnotation(NoTransaction.class) != null ) { - //Called method or class is annotated with @NoTransaction telling us that - //we should not start a transaction + if (InvocationContext.current().getAnnotation(NoTransaction.class) != null) { + // Called method or class is annotated with @NoTransaction telling us that + // we should not start a transaction return block.apply(); } @@ -217,11 +238,10 @@ public static T withinFilter(F.Function0 block) throws Throwable { return withTransaction(name, readOnly, block); } - public static String getDBName(Class clazz) { String name = JPA.DEFAULT; - if(clazz != null){ - PersistenceUnit pu = (PersistenceUnit)clazz.getAnnotation(PersistenceUnit.class); + if (clazz != null) { + PersistenceUnit pu = clazz.getAnnotation(PersistenceUnit.class); if (pu != null) { name = pu.name(); } @@ -229,19 +249,26 @@ public static String getDBName(Class clazz) { return name; } - /** * Run a block of code in a JPA transaction. * - * @param dbName The persistence unit name - * @param readOnly Is the transaction read-only? - * @param block Block of code to execute. + * @param dbName + * The persistence unit name + * @param readOnly + * Is the transaction read-only? + * @param block + * Block of code to execute. + * @param + * The entity class + * @return The result + * @throws java.lang.Throwable + * Thrown in case of error */ public static T withTransaction(String dbName, boolean readOnly, F.Function0 block) throws Throwable { if (isEnabled()) { boolean closeEm = true; - // For each existing persisence unit - + // For each existing persistence unit + try { // we are starting a transaction for all known persistent unit // this is probably not the best, but there is no way we can know where to go from @@ -256,14 +283,15 @@ public static T withTransaction(String dbName, boolean readOnly, F.Function0 } T result = block.apply(); - + boolean rollbackAll = false; // Get back our entity managers // Because people might have mess up with the current entity managers for (JPAContext jpaContext : get().values()) { EntityManager m = jpaContext.entityManager; EntityTransaction localTx = m.getTransaction(); - // The resource transaction must be in progress in order to determine if it has been marked for rollback + // The resource transaction must be in progress in order to determine if it has been marked for + // rollback if (localTx.isActive() && localTx.getRollbackOnly()) { rollbackAll = true; } @@ -288,7 +316,7 @@ public static T withTransaction(String dbName, boolean readOnly, F.Function0 // Nothing, transaction is in progress closeEm = false; throw e; - } catch(Throwable t) { + } catch (Throwable t) { // Because people might have mess up with the current entity managers for (JPAContext jpaContext : get().values()) { EntityManager m = jpaContext.entityManager; @@ -298,7 +326,7 @@ public static T withTransaction(String dbName, boolean readOnly, F.Function0 if (localTx.isActive()) { localTx.rollback(); } - } catch(Throwable e) { + } catch (Throwable e) { } } @@ -315,18 +343,20 @@ public static T withTransaction(String dbName, boolean readOnly, F.Function0 for (String name : emfs.keySet()) { JPA.unbindForCurrentThread(name); } - } - } + } + } } else { return block.apply(); } } - /** + /** * initialize the JPA context and starts a JPA transaction * - * @param name The persistence unit name - * @param readOnly true for a readonly transaction + * @param name + * The persistence unit name + * @param readOnly + * true for a readonly transaction */ public static void startTx(String name, boolean readOnly) { EntityManager manager = createEntityManager(name); @@ -338,15 +368,16 @@ public static void startTx(String name, boolean readOnly) { public static void closeTx(String name) { if (JPA.isInsideTransaction(name)) { - EntityManager manager = em(name); - try { - // Be sure to set the connection is non-autoCommit mode as some driver will complain about COMMIT statement + EntityManager manager = em(name); + try { + // Be sure to set the connection is non-autoCommit mode as some driver will complain about COMMIT + // statement try { DB.getConnection(name).setAutoCommit(false); - } catch(Exception e) { + } catch (Exception e) { Logger.error(e, "Why the driver complains here?"); } - // Commit the transaction + // Commit the transaction if (manager.getTransaction().isActive()) { if (JPA.get().get(name).readonly || manager.getTransaction().getRollbackOnly()) { manager.getTransaction().rollback(); @@ -379,19 +410,20 @@ public static void closeTx(String name) { public static void rollbackTx(String name) { if (JPA.isInsideTransaction()) { - EntityManager manager = em(name); - try { - // Be sure to set the connection is non-autoCommit mode as some driver will complain about COMMIT statement + EntityManager manager = em(name); + try { + // Be sure to set the connection is non-autoCommit mode as some driver will complain about COMMIT + // statement try { DB.getConnection(name).setAutoCommit(false); - } catch(Exception e) { + } catch (Exception e) { Logger.error(e, "Why the driver complains here?"); } - // Commit the transaction + // Commit the transaction if (manager.getTransaction().isActive()) { try { - manager.getTransaction().rollback(); - } catch (Throwable e) { + manager.getTransaction().rollback(); + } catch (Throwable e) { for (int i = 0; i < 10; i++) { if (e instanceof PersistenceException && e.getCause() != null) { e = e.getCause(); @@ -405,7 +437,7 @@ public static void rollbackTx(String name) { throw new JPAException("Cannot commit", e); } } - + } finally { if (manager.isOpen()) { manager.close(); diff --git a/framework/src/play/db/jpa/JPABase.java b/framework/src/play/db/jpa/JPABase.java index a395931cd6..b430c20351 100644 --- a/framework/src/play/db/jpa/JPABase.java +++ b/framework/src/play/db/jpa/JPABase.java @@ -1,8 +1,29 @@ package play.db.jpa; -import org.hibernate.collection.spi.PersistentCollection; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.EntityManager; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.PersistenceException; + import org.hibernate.collection.internal.PersistentMap; -import org.hibernate.engine.spi.*; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.exception.GenericJDBCException; import org.hibernate.internal.SessionImpl; @@ -14,21 +35,13 @@ import play.PlayPlugin; import play.exceptions.UnexpectedException; -import javax.persistence.*; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.sql.SQLException; -import java.util.*; - /** * A super class for JPA entities */ @MappedSuperclass public class JPABase implements Serializable, play.db.Model { - + @Override public void _save() { String dbName = JPA.getDBName(this.getClass()); if (!em(dbName).contains(this)) { @@ -58,9 +71,10 @@ public void _save() { } } + @Override public void _delete() { String dbName = JPA.getDBName(this.getClass()); - + try { avoidCascadeSaveLoops.set(new HashSet()); try { @@ -92,13 +106,14 @@ public void _delete() { } } + @Override public Object _key() { return Model.Manager.factoryFor(this.getClass()).keyValue(this); } // ~~~ SAVING public transient boolean willBeSaved = false; - static transient ThreadLocal> avoidCascadeSaveLoops = new ThreadLocal>(); + static final transient ThreadLocal> avoidCascadeSaveLoops = new ThreadLocal<>(); private void saveAndCascade(boolean willBeSaved) { this.willBeSaved = willBeSaved; @@ -112,7 +127,7 @@ private void saveAndCascade(boolean willBeSaved) { } // Cascade save try { - Set fields = new HashSet(); + Set fields = new HashSet<>(); Class clazz = this.getClass(); while (!clazz.equals(JPABase.class)) { Collections.addAll(fields, clazz.getDeclaredFields()); @@ -186,7 +201,7 @@ private void saveAndCascade(boolean willBeSaved) { private void cascadeOrphans(JPABase base, PersistentCollection persistentCollection, boolean willBeSaved) { String dbName = JPA.getDBName(this.getClass()); - + SessionImpl session = ((SessionImpl) JPA.em(dbName).getDelegate()); PersistenceContext pc = session.getPersistenceContext(); CollectionEntry ce = pc.getCollectionEntry(persistentCollection); @@ -197,7 +212,7 @@ private void cascadeOrphans(JPABase base, PersistentCollection persistentCollect Type ct = cp.getElementType(); if (ct instanceof EntityType) { EntityEntry entry = pc.getEntry(base); - String entityName = entry.getEntityName(); + String entityName = entry.getEntityName(); entityName = ((EntityType) ct).getAssociatedEntityName(session.getFactory()); if (ce.getSnapshot() != null) { Collection orphans = ce.getOrphans(entityName, persistentCollection); @@ -227,6 +242,9 @@ private static boolean cascadeAll(CascadeType[] types) { /** * Retrieve the current entityManager + * + * @param name + * The DB name * * @return the current entityManager */ @@ -234,7 +252,7 @@ public static EntityManager em(String name) { return JPA.em(name); } - public static EntityManager em() { + public static EntityManager em() { return JPA.em(); } @@ -247,21 +265,23 @@ public boolean isPersistent() { } /** - * JPASupport instances a and b are equals if either a == b or a and b have same {@link #_key key} and class + * JPASupport instances a and b are equals if either a == b or a and b have same + * {@link #_key key} and class * * @param other + * The object to compare to * @return true if equality condition above is verified */ @Override public boolean equals(Object other) { - final Object key = this._key(); - if (other == null) { return false; } if (this == other) { return true; } + + Object key = this._key(); if (key == null) { return false; } @@ -273,7 +293,6 @@ public boolean equals(Object other) { return false; } - if (!this.getClass().isAssignableFrom(other.getClass())) { return false; } @@ -283,7 +302,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - final Object key = this._key(); + Object key = this._key(); if (key == null) { return 0; } @@ -295,7 +314,7 @@ public int hashCode() { @Override public String toString() { - final Object key = this._key(); + Object key = this._key(); String keyStr = ""; if (key != null && key.getClass().isArray()) { for (Object object : (Object[]) key) { diff --git a/framework/src/play/db/jpa/JPAEnhancer.java b/framework/src/play/db/jpa/JPAEnhancer.java index a215c2cba6..6f67502445 100644 --- a/framework/src/play/db/jpa/JPAEnhancer.java +++ b/framework/src/play/db/jpa/JPAEnhancer.java @@ -13,6 +13,7 @@ */ public class JPAEnhancer extends Enhancer { + @Override public void enhanceThisClass(ApplicationClass applicationClass) throws Exception { CtClass ctClass = makeClass(applicationClass); diff --git a/framework/src/play/db/jpa/JPAModelLoader.java b/framework/src/play/db/jpa/JPAModelLoader.java index 0727f8c513..dd81ecb6e1 100644 --- a/framework/src/play/db/jpa/JPAModelLoader.java +++ b/framework/src/play/db/jpa/JPAModelLoader.java @@ -49,6 +49,7 @@ public JPAModelLoader(Class clazz) { * Find object by ID * @param id : the id of the entity */ + @Override public Model findById(Object id) { try { if (id == null) { @@ -81,6 +82,7 @@ public Model findById(Object id) { * @return a list of results */ @SuppressWarnings("unchecked") + @Override public List fetch(int offset, int size, String orderBy, String order, List searchFields, String keywords, String where) { StringBuilder q = new StringBuilder("from ").append(this.clazz.getName()); @@ -113,9 +115,7 @@ public List fetch(int offset, int size, String orderBy, String order, Lis return query.getResultList(); } - /** - * - */ + @Override public Long count(List searchFields, String keywords, String where) { String q = "select count(*) from " + this.clazz.getName() + " e"; if (keywords != null && !keywords.equals("")) { @@ -134,9 +134,7 @@ public Long count(List searchFields, String keywords, String where) { return Long.decode(query.getSingleResult().toString()); } - /** - * - */ + @Override public void deleteAll() { JPA.em(this.dbName).createQuery("delete from " + this.clazz.getName()).executeUpdate(); } @@ -144,9 +142,10 @@ public void deleteAll() { /** * List of all properties */ + @Override public List listProperties() { - List properties = new ArrayList(); - Set fields = new LinkedHashSet(); + List properties = new ArrayList<>(); + Set fields = new LinkedHashSet<>(); Class tclazz = this.clazz; while (!tclazz.equals(Object.class)) { Collections.addAll(fields, tclazz.getDeclaredFields()); @@ -175,10 +174,12 @@ public List listProperties() { return properties; } + @Override public String keyName() { return keyField().getName(); } + @Override public Class keyType() { return keyField().getType(); } @@ -226,7 +227,7 @@ private void initProperties() { if (this.properties != null){ return; } - this.properties = new HashMap(); + this.properties = new HashMap<>(); Set fields = getModelFields(this.clazz); for (Field f : fields) { int mod = f.getModifiers(); @@ -291,9 +292,7 @@ private Object makeCompositeKey(Model model) throws Exception { return id; } - /** - * - */ + @Override public Object keyValue(Model m) { try { if (m == null) { @@ -306,11 +305,11 @@ public Object keyValue(Model m) { } // Is it a composite key? If yes we need to return the matching PK - final Field[] fields = keyFields(); - final Object[] values = new Object[fields.length]; + Field[] fields = keyFields(); + Object[] values = new Object[fields.length]; int i = 0; for (Field f : fields) { - final Object o = f.get(m); + Object o = f.get(m); if (o != null) { values[i++] = o; } @@ -327,13 +326,8 @@ public Object keyValue(Model m) { } } - /** - * - * @param clazz - * @return - */ public static Set getModelFields(Class clazz) { - Set fields = new LinkedHashSet(); + Set fields = new LinkedHashSet<>(); Class tclazz = clazz; while (!tclazz.equals(Object.class)) { // Only add fields for mapped types @@ -344,10 +338,6 @@ public static Set getModelFields(Class clazz) { return fields; } - /** - * - * @return - */ Field keyField() { Class c = this.clazz; try { @@ -361,19 +351,15 @@ Field keyField() { c = c.getSuperclass(); } } catch (Exception e) { - throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz); + throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz, e); } throw new UnexpectedException("Cannot get the object @Id for an object of type " + clazz); } - /** - * - * @return - */ Field[] keyFields() { Class c = this.clazz; try { - List fields = new ArrayList(); + List fields = new ArrayList<>(); while (!c.equals(Object.class)) { for (Field field : c.getDeclaredFields()) { if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class)) { @@ -383,21 +369,16 @@ Field[] keyFields() { } c = c.getSuperclass(); } - final Field[] f = fields.toArray(new Field[fields.size()]); + Field[] f = fields.toArray(new Field[fields.size()]); if (f.length == 0) { throw new UnexpectedException("Cannot get the object @Id for an object of type " + clazz); } return f; } catch (Exception e) { - throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz); + throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz, e); } } - /** - * - * @param searchFields - * @return - */ String getSearchQuery(List searchFields) { StringBuilder q = new StringBuilder(""); for (Model.Property property : this.listProperties()) { @@ -412,11 +393,6 @@ String getSearchQuery(List searchFields) { return q.toString(); } - /** - * - * @param field - * @return - */ Model.Property buildProperty(final Field field) { Model.Property modelProperty = new Model.Property(); modelProperty.type = field.getType(); @@ -430,6 +406,7 @@ Model.Property buildProperty(final Field field) { final String modelDbName = JPA.getDBName(modelProperty.relationType); modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") + @Override public List list() { return JPA.em(modelDbName).createQuery("from " + field.getType().getName()).getResultList(); } @@ -442,6 +419,7 @@ public List list() { final String modelDbName = JPA.getDBName(modelProperty.relationType); modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") + @Override public List list() { return JPA.em(modelDbName).createQuery("from " + field.getType().getName()).getResultList(); } @@ -458,6 +436,7 @@ public List list() { final String modelDbName = JPA.getDBName(modelProperty.relationType); modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") + @Override public List list() { return JPA.em(modelDbName).createQuery("from " + fieldType.getName()).getResultList(); } @@ -473,6 +452,7 @@ public List list() { modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") + @Override public List list() { return JPA.em(modelDbName).createQuery("from " + fieldType.getName()).getResultList(); } @@ -484,6 +464,7 @@ public List list() { modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") + @Override public List list() { return (List) Arrays.asList(field.getType().getEnumConstants()); } diff --git a/framework/src/play/db/jpa/JPAPlugin.java b/framework/src/play/db/jpa/JPAPlugin.java index 88f2dd5af0..0f9a343916 100644 --- a/framework/src/play/db/jpa/JPAPlugin.java +++ b/framework/src/play/db/jpa/JPAPlugin.java @@ -1,42 +1,34 @@ package play.db.jpa; -import org.apache.commons.beanutils.PropertyUtils; import org.apache.log4j.Level; -import org.hibernate.ejb.Ejb3Configuration; - +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; import play.Logger; import play.Play; import play.PlayPlugin; import play.classloading.ApplicationClasses.ApplicationClass; import play.data.binding.Binder; -import play.data.binding.NoBinding; import play.data.binding.ParamNode; import play.data.binding.RootParamNode; +import play.db.Configuration; import play.db.DB; import play.db.Model; -import play.db.Configuration; import play.exceptions.JPAException; import play.exceptions.UnexpectedException; import javax.persistence.*; +import javax.persistence.spi.PersistenceUnitInfo; -import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; import java.util.*; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; -/** - * JPA Plugin - */ public class JPAPlugin extends PlayPlugin { - - public static boolean autoTxs = true; - @Override public Object bind(RootParamNode rootParamNode, String name, Class clazz, java.lang.reflect.Type type, Annotation[] annotations) { @@ -116,98 +108,105 @@ public EntityManager em(String key) { */ @Override public void onApplicationStart() { - org.hibernate.ejb.HibernatePersistence persistence = new org.hibernate.ejb.HibernatePersistence(); org.apache.log4j.Logger.getLogger("org.hibernate.SQL").setLevel(Level.OFF); Set dBNames = Configuration.getDbNames(); for (String dbName : dBNames) { Configuration dbConfig = new Configuration(dbName); - Ejb3Configuration cfg = new Ejb3Configuration(); - List classes = Play.classloader.getAnnotatedClasses(Entity.class); - for (Class clazz : classes) { - if (clazz.isAnnotationPresent(Entity.class)) { - // Do we have a transactional annotation matching our dbname? - PersistenceUnit pu = clazz.getAnnotation(PersistenceUnit.class); - if (pu != null && pu.name().equals(dbName)) { - cfg.addAnnotatedClass(clazz); - Logger.debug("Add JPA Model : %s to db %s", clazz, dbName); - } else if (pu == null && JPA.DEFAULT.equals(dbName)) { - cfg.addAnnotatedClass(clazz); - Logger.debug("Add JPA Model : %s to db %s", clazz, dbName); - } - } - } - - // Add entities - String[] moreEntities = Play.configuration.getProperty("jpa.entities", "").split(", "); - for (String entity : moreEntities) { - if (entity.trim().equals("")) { - continue; - } - try { - Class clazz = Play.classloader.loadClass(entity); - // Do we have a transactional annotation matching our dbname? - PersistenceUnit pu = clazz.getAnnotation(PersistenceUnit.class); - if (pu != null && pu.name().equals(dbName)) { - cfg.addAnnotatedClass(clazz); - Logger.debug("Add JPA Model : %s to db %s", clazz, dbName); - } else if (pu == null && JPA.DEFAULT.equals(dbName)) { - cfg.addAnnotatedClass(clazz); - Logger.debug("Add JPA Model : %s to db %s", clazz, dbName); - } - } catch (Exception e) { - Logger.warn("JPA -> Entity not found: %s", entity); - } - } - - for (ApplicationClass applicationClass : Play.classes.all()) { - if (applicationClass.isClass() || applicationClass.javaPackage == null) { - continue; - } - Package p = applicationClass.javaPackage; - Logger.info("JPA -> Adding package: %s", p.getName()); - cfg.addPackage(p.getName()); + if (dbConfig.getProperty("jpa.debugSQL", "false").equals("true")) { + org.apache.log4j.Logger.getLogger("org.hibernate.SQL").setLevel(Level.ALL); } - String mappingFile = dbConfig.getProperty("jpa.mapping-file", ""); - if (mappingFile != null && mappingFile.length() > 0) { - cfg.addResource(mappingFile); - } + Thread thread = Thread.currentThread(); + ClassLoader contextClassLoader = thread.getContextClassLoader(); + thread.setContextClassLoader(Play.classloader); + try { - if (!dbConfig.getProperty("jpa.ddl", Play.mode.isDev() ? "update" : "none").equals("none")) { - cfg.setProperty("hibernate.hbm2ddl.auto", dbConfig.getProperty("jpa.ddl", "update")); + if (Logger.isTraceEnabled()) { + Logger.trace("Initializing JPA for %s...", dbName); + } + + JPA.emfs.put(dbName, newEntityManagerFactory(dbName, dbConfig)); + } finally { + thread.setContextClassLoader(contextClassLoader); } - - Map properties = dbConfig.getProperties(); - properties.put("javax.persistence.transaction", "RESOURCE_LOCAL"); - properties.put("javax.persistence.provider", "org.hibernate.ejb.HibernatePersistence"); - properties.put("hibernate.dialect", getDefaultDialect(dbConfig, dbConfig.getProperty("db.driver"))); - - if (dbConfig.getProperty("jpa.debugSQL", "false").equals("true")) { - org.apache.log4j.Logger.getLogger("org.hibernate.SQL").setLevel(Level.ALL); + } + JPQL.instance = new JPQL(); + } + + private List entityClasses(String dbName) { + List entityClasses = new ArrayList<>(); + + List classes = Play.classloader.getAnnotatedClasses(Entity.class); + for (Class clazz : classes) { + if (clazz.isAnnotationPresent(Entity.class)) { + // Do we have a transactional annotation matching our dbname? + PersistenceUnit pu = clazz.getAnnotation(PersistenceUnit.class); + if (pu != null && pu.name().equals(dbName)) { + entityClasses.add(clazz.getName()); + } else if (pu == null && JPA.DEFAULT.equals(dbName)) { + entityClasses.add(clazz.getName()); + } } + } - cfg.configure(properties); - cfg.setDataSource(DB.getDataSource(dbName)); - + // Add entities + String[] moreEntities = Play.configuration.getProperty("jpa.entities", "").split(", "); + for (String entity : moreEntities) { + if (entity.trim().equals("")) { + continue; + } try { - Field field = cfg.getClass().getDeclaredField("overridenClassLoader"); - field.setAccessible(true); - field.set(cfg, Play.classloader); + Class clazz = Play.classloader.loadClass(entity); + // Do we have a transactional annotation matching our dbname? + PersistenceUnit pu = clazz.getAnnotation(PersistenceUnit.class); + if (pu != null && pu.name().equals(dbName)) { + entityClasses.add(clazz.getName()); + } else if (pu == null && JPA.DEFAULT.equals(dbName)) { + entityClasses.add(clazz.getName()); + } } catch (Exception e) { - Logger.error(e, "Error trying to override the hibernate classLoader (new hibernate version ???)"); + Logger.warn(e, "JPA -> Entity not found: %s", entity); } - - cfg.setInterceptor(new HibernateInterceptor()); + } + return entityClasses; + } - if (Logger.isTraceEnabled()) { - Logger.trace("Initializing JPA for %s...", dbName); - } + protected EntityManagerFactory newEntityManagerFactory(String dbName, Configuration dbConfig) { + PersistenceUnitInfo persistenceUnitInfo = persistenceUnitInfo(dbName, dbConfig); + Map configuration = new HashMap<>(); + configuration.put(AvailableSettings.INTERCEPTOR, new HibernateInterceptor()); - JPA.emfs.put(dbName, cfg.buildEntityManagerFactory()); + return new EntityManagerFactoryBuilderImpl( + new PersistenceUnitInfoDescriptor(persistenceUnitInfo), configuration + ).build(); + } + + protected PersistenceUnitInfoImpl persistenceUnitInfo(String dbName, Configuration dbConfig) { + return new PersistenceUnitInfoImpl(dbName, + entityClasses(dbName), mappingFiles(dbConfig), properties(dbName, dbConfig)); + } + + private List mappingFiles(Configuration dbConfig) { + String mappingFile = dbConfig.getProperty("jpa.mapping-file", ""); + return mappingFile != null && mappingFile.length() > 0 ? singletonList(mappingFile) : emptyList(); + + } + + protected Properties properties(String dbName, Configuration dbConfig) { + Properties properties = new Properties(); + properties.putAll(dbConfig.getProperties()); + properties.put("javax.persistence.transaction", "RESOURCE_LOCAL"); + properties.put("javax.persistence.provider", "org.hibernate.ejb.HibernatePersistence"); + properties.put("hibernate.dialect", getDefaultDialect(dbConfig, dbConfig.getProperty("db.driver"))); + + if (!dbConfig.getProperty("jpa.ddl", Play.mode.isDev() ? "update" : "none").equals("none")) { + properties.setProperty("hibernate.hbm2ddl.auto", dbConfig.getProperty("jpa.ddl", "update")); } - JPQL.instance = new JPQL(); + + properties.put("hibernate.connection.datasource", DB.getDataSource(dbName)); + return properties; } public static String getDefaultDialect(String driver) { @@ -264,15 +263,18 @@ public static String getDefaultDialect(Configuration dbConfig, String driver) { @Override public void onApplicationStop() { - // Close all presistence units - for(EntityManagerFactory emf: JPA.emfs.values()) { - if(emf.isOpen()){ + closeAllPersistenceUnits(); + } + + private void closeAllPersistenceUnits() { + for (EntityManagerFactory emf : JPA.emfs.values()) { + if (emf.isOpen()) { emf.close(); } } - JPA.emfs.clear(); + JPA.emfs.clear(); } - + @Override public void afterFixtureLoad() { if (JPA.isEnabled()) { @@ -303,7 +305,7 @@ public Object withinFilter(play.libs.F.Function0 fct) throws Throwable { private TransactionalFilter txFilter = new TransactionalFilter("TransactionalFilter"); @Override - public Filter getFilter() { + public Filter getFilter() { return txFilter; } @@ -316,7 +318,6 @@ public static EntityManager createEntityManager() { * initialize the JPA context and starts a JPA transaction * * @param readonly true for a readonly transaction - * @param autoCommit true to automatically commit the DB transaction after each JPA statement * @deprecated see JPA startTx() method */ @Deprecated diff --git a/framework/src/play/db/jpa/JPASupport.java b/framework/src/play/db/jpa/JPASupport.java index 99b9e448ed..3bed5db972 100644 --- a/framework/src/play/db/jpa/JPASupport.java +++ b/framework/src/play/db/jpa/JPASupport.java @@ -3,13 +3,13 @@ import javax.persistence.Query; /** - * Use play.db.jpa.GenericModel insteads + * Use play.db.jpa.GenericModel instead */ @Deprecated public class JPASupport extends GenericModel { /** - * Use play.db.jpa.GenericModel.JPAQuery insteads + * Use play.db.jpa.GenericModel.JPAQuery instead */ @Deprecated public static class JPAQuery extends GenericModel.JPAQuery { diff --git a/framework/src/play/db/jpa/JPQL.java b/framework/src/play/db/jpa/JPQL.java index f69203e220..7f61b9ec30 100644 --- a/framework/src/play/db/jpa/JPQL.java +++ b/framework/src/play/db/jpa/JPQL.java @@ -41,11 +41,11 @@ public long count(String dbName, String entity, String query, Object[] params) { createCountQuery(dbName, entity, entity, query, params)), params).getSingleResult().toString()); } - public List findAll(String entity) { + public List findAll(String entity) { return findAll(JPA.DEFAULT, entity); } - public List findAll(String dbName, String entity) { + public List findAll(String dbName, String entity) { return em(dbName).createQuery("select e from " + entity + " e").getResultList(); } @@ -57,11 +57,11 @@ public JPABase findById(String dbName, String entity, Object id) throws Exceptio return (JPABase) em(dbName).find(Play.classloader.loadClass(entity), id); } - public List findBy(String entity, String query, Object[] params) { + public List findBy(String entity, String query, Object[] params) { return findBy(JPA.DEFAULT, entity, query, params); } - public List findBy(String dbName, String entity, String query, Object[] params) { + public List findBy(String dbName, String entity, String query, Object[] params) { Query q = em(dbName).createQuery( createFindByQuery(dbName, entity, entity, query, params)); return bindParameters(q, params).getResultList(); @@ -250,10 +250,10 @@ public String findByToJPQL(String dbName, String findBy) { StringBuilder jpql = new StringBuilder(); String subRequest; if (findBy.contains("OrderBy")) - subRequest = findBy.split("OrderBy")[0]; + subRequest = findBy.split("OrderBy")[0]; else subRequest = findBy; String[] parts = subRequest.split("And"); - int index = 1; + int index = 1; for (int i = 0; i < parts.length; i++) { String part = parts[i]; if (part.endsWith("NotEqual")) { diff --git a/framework/src/play/db/jpa/NoTransaction.java b/framework/src/play/db/jpa/NoTransaction.java index ce672286fb..29f7027bc8 100644 --- a/framework/src/play/db/jpa/NoTransaction.java +++ b/framework/src/play/db/jpa/NoTransaction.java @@ -1,6 +1,5 @@ package play.db.jpa; - import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -8,16 +7,16 @@ import java.lang.annotation.Target; /** - * Annotation to be used on methods telling JPA - * that it should not create a Transaction at all + * Annotation to be used on methods telling JPA that it should not create a Transaction at all */ @Retention(RetentionPolicy.RUNTIME) @Inherited -@Target(value={ElementType.METHOD,ElementType.TYPE}) +@Target(value = { ElementType.METHOD, ElementType.TYPE }) public @interface NoTransaction { - /** - * Db name we do not want transaction. - */ - String value() default "default"; + /** + * Db name we do not want transaction. + * + * @return The DB name + */ + String value() default "default"; } - diff --git a/framework/src/play/db/jpa/PersistenceUnitInfoImpl.java b/framework/src/play/db/jpa/PersistenceUnitInfoImpl.java new file mode 100644 index 0000000000..1394fe1b0d --- /dev/null +++ b/framework/src/play/db/jpa/PersistenceUnitInfoImpl.java @@ -0,0 +1,141 @@ +package play.db.jpa; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; +import javax.sql.DataSource; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +/** + * @author Vlad Mihalcea + * + * Taken from https://vladmihalcea.com/2015/11/26/how-to-bootstrap-hibernate-without-the-persistence-xml-file/ + */ +public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + + private final String persistenceUnitName; + + private PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + + private final List managedClassNames; + private final List mappingFileNames; + + private final Properties properties; + + private DataSource jtaDataSource; + + private DataSource nonJtaDataSource; + + public PersistenceUnitInfoImpl(String persistenceUnitName, List managedClassNames, List mappingFileNames, Properties properties) { + this.persistenceUnitName = persistenceUnitName; + this.managedClassNames = managedClassNames; + this.mappingFileNames = mappingFileNames; + this.properties = properties; + } + + @Override + public String getPersistenceUnitName() { + return persistenceUnitName; + } + + @Override + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return transactionType; + } + + @Override + public DataSource getJtaDataSource() { + return jtaDataSource; + } + + public PersistenceUnitInfoImpl setJtaDataSource(DataSource jtaDataSource) { + this.jtaDataSource = jtaDataSource; + this.nonJtaDataSource = null; + transactionType = PersistenceUnitTransactionType.JTA; + return this; + } + + @Override + public DataSource getNonJtaDataSource() { + return nonJtaDataSource; + } + + public PersistenceUnitInfoImpl setNonJtaDataSource(DataSource nonJtaDataSource) { + this.nonJtaDataSource = nonJtaDataSource; + this.jtaDataSource = null; + transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + return this; + } + + @Override + public List getMappingFileNames() { + return mappingFileNames; + } + + @Override + public List getJarFileUrls() { + return Collections.emptyList(); + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public List getManagedClassNames() { + return managedClassNames; + } + + @Override + public boolean excludeUnlistedClasses() { + return false; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return SharedCacheMode.UNSPECIFIED; + } + + @Override + public ValidationMode getValidationMode() { + return ValidationMode.AUTO; + } + + @Override + public Properties getProperties() { + return properties; + } + + @Override + public String getPersistenceXMLSchemaVersion() { + return "2.1"; + } + + @Override + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + @Override + public void addTransformer(ClassTransformer transformer) { + + } + + @Override + public ClassLoader getNewTempClassLoader() { + return null; + } +} \ No newline at end of file diff --git a/framework/src/play/db/jpa/Transactional.java b/framework/src/play/db/jpa/Transactional.java index 47bccc0c60..f940197684 100644 --- a/framework/src/play/db/jpa/Transactional.java +++ b/framework/src/play/db/jpa/Transactional.java @@ -6,12 +6,14 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD,ElementType.TYPE}) +@Target({ ElementType.METHOD, ElementType.TYPE }) public @interface Transactional { - /** - * Db name where to persist for multi db support - */ - String value() default "default"; - public boolean readOnly() default false; -} + /** + * Db name where to persist for multi db support + * + * @return The DB name + */ + String value() default "default"; + public boolean readOnly() default false; +} diff --git a/framework/src/play/deps/DependenciesManager.java b/framework/src/play/deps/DependenciesManager.java index cfa59b301a..33baf8f984 100644 --- a/framework/src/play/deps/DependenciesManager.java +++ b/framework/src/play/deps/DependenciesManager.java @@ -4,13 +4,13 @@ import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.commons.io.FileUtils; import org.apache.ivy.Ivy; +import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.core.report.ArtifactDownloadReport; import org.apache.ivy.core.report.ResolveReport; import org.apache.ivy.core.resolve.IvyNode; @@ -26,18 +26,38 @@ import play.libs.IO; public class DependenciesManager { - + public static void main(String[] args) throws Exception { + String applicationPath = System.getProperty("application.path"); + if (applicationPath == null) { + System.out.println("~ ERROR: cannot resolve \"application.path\""); + return; + } + String frameworkPath = System.getProperty("framework.path"); + if (frameworkPath == null) { + System.out.println("~ ERROR: cannot resolve \"framework.path\""); + return; + } + String userHomePath = System.getProperty("user.home"); + if (userHomePath == null) { + System.out.println("~ ERROR: cannot resolve \"user.home\""); + return; + } + String playVersion = System.getProperty("play.version"); + if (playVersion == null) { + System.out.println("~ ERROR: cannot resolve \"play.version\""); + return; + } // Paths - File application = new File(System.getProperty("application.path")); - File framework = new File(System.getProperty("framework.path")); - File userHome = new File(System.getProperty("user.home")); + File application = new File(applicationPath); + File framework = new File(frameworkPath); + File userHome = new File(userHomePath); DependenciesManager deps = new DependenciesManager(application, framework, userHome); ResolveReport report = deps.resolve(); - if(report != null) { + if (report != null) { deps.report(); List installed = deps.retrieve(report); deps.sync(installed); @@ -58,17 +78,17 @@ public static void main(String[] args) throws Exception { File framework; File userHome; HumanReadyLogger logger; - + final FileFilter dirsToTrim = new FileFilter() { - + + @Override public boolean accept(File file) { return file.isDirectory() && isDirToTrim(file.getName()); } - + private boolean isDirToTrim(String fileName) { - return "documentation".equals(fileName) || "src".equals(fileName) || - "tmp".equals(fileName) || fileName.contains("sample") || - fileName.contains("test"); + return "documentation".equals(fileName) || "src".equals(fileName) || "tmp".equals(fileName) || fileName.contains("sample") + || fileName.contains("test"); } }; @@ -102,12 +122,9 @@ public void report() { public void sync(List installed) { - List notSync = new ArrayList(); + List notSync = new ArrayList<>(); - File[] paths = new File[]{ - new File(application, "lib"), - new File(application, "modules") - }; + File[] paths = new File[] { new File(application, "lib"), new File(application, "modules") }; for (File path : paths) { if (path.exists()) { for (File f : path.listFiles()) { @@ -118,7 +135,7 @@ public void sync(List installed) { } } - boolean autoSync = System.getProperty("sync") != null; + boolean autoSync = System.getProperty("nosync") == null; if (autoSync && !notSync.isEmpty()) { System.out.println("~"); @@ -132,7 +149,8 @@ public void sync(List installed) { } else if (!notSync.isEmpty()) { System.out.println("~"); System.out.println("~ *****************************************************************************"); - System.out.println("~ WARNING: Your lib/ and modules/ directories are not synced with current dependencies (use --sync to automatically delete them)"); + System.out.println( + "~ WARNING: Your lib/ and modules/ directories are not synced with current dependencies (don't use --nosync to automatically delete them)"); System.out.println("~"); for (File f : notSync) { System.out.println("~ \tUnknown: " + f.getAbsolutePath()); @@ -146,7 +164,8 @@ public boolean problems() { if (!logger.notFound.isEmpty()) { System.out.println("~"); System.out.println("~ *****************************************************************************"); - System.out.println("~ WARNING: These dependencies are missing, your application may not work properly (use --verbose for details),"); + System.out.println( + "~ WARNING: These dependencies are missing, your application may not work properly (use --verbose for details),"); System.out.println("~"); for (String d : logger.notFound) { System.out.println("~\t" + d); @@ -160,21 +179,22 @@ public boolean problems() { // Retrieve the list of modules in the order they were defined in the dependencies.yml. public Set retrieveModules() throws Exception { - File ivyModule = new File(application, "conf/dependencies.yml"); - if(ivyModule == null || !ivyModule.exists()) { - return new LinkedHashSet(); + File ivyModule = new File(application, "conf/dependencies.yml"); + if (ivyModule == null || !ivyModule.exists()) { + return new LinkedHashSet<>(); } - return YamlParser.getOrderedModuleList(ivyModule); + return YamlParser.getOrderedModuleList(ivyModule); } - + public List retrieve(ResolveReport report) throws Exception { - // Track missing artifacts - List missing = new ArrayList(); + List missing = new ArrayList<>(); - List artifacts = new ArrayList(); - for (Iterator iter = report.getDependencies().iterator(); iter.hasNext();) { - IvyNode node = (IvyNode) iter.next(); + // Track deps with errors + List problems = new ArrayList<>(); + + List artifacts = new ArrayList<>(); + for (IvyNode node : ((List) report.getDependencies())) { if (node.isLoaded() && !node.isCompletelyEvicted()) { ArtifactDownloadReport[] adr = report.getArtifactsReports(node.getResolvedId()); for (ArtifactDownloadReport artifact : adr) { @@ -183,48 +203,63 @@ public List retrieve(ResolveReport report) throws Exception { } else { if (isPlayModule(artifact) || !isFrameworkLocal(artifact)) { artifacts.add(artifact); - + // Save the order of module - if(isPlayModule(artifact)){ + if (isPlayModule(artifact)) { String mName = artifact.getLocalFile().getName(); if (mName.endsWith(".jar") || mName.endsWith(".zip")) { - mName = mName.substring(0, mName.length() - 4); + mName = mName.substring(0, mName.length() - 4); } } } } } + } else if (node.hasProblem() && !node.isCompletelyEvicted()) { + problems.add(node); } } - + // Create directory if not exist File modulesDir = new File(application, "modules"); - if(!modulesDir.exists()){ + if (!modulesDir.exists()) { modulesDir.mkdir(); } - - - if (!missing.isEmpty()) { + + if (!missing.isEmpty() || !problems.isEmpty()) { System.out.println("~"); System.out.println("~ WARNING: Some dependencies could not be downloaded (use --verbose for details),"); System.out.println("~"); for (ArtifactDownloadReport d : missing) { - String msg = d.getArtifact().getModuleRevisionId().getOrganisation() + "->" + d.getArtifact().getModuleRevisionId().getName() + " " + d.getArtifact().getModuleRevisionId().getRevision() + ": " + d.getDownloadDetails(); - System.out.println("~\t" + msg); + StringBuilder msg = new StringBuilder(d.getArtifact().getModuleRevisionId().getOrganisation()).append(" -> ") + .append(d.getArtifact().getModuleRevisionId().getName()).append(' ') + .append(d.getArtifact().getModuleRevisionId().getRevision()).append(": ").append(d.getDownloadDetails()); + System.out.println("~\t" + msg.toString()); if (logger != null) { - logger.notFound.add(msg); + logger.notFound.add(msg.toString()); + } + } + if (!problems.isEmpty()) { + for (IvyNode node : problems) { + ModuleRevisionId moduleRevisionId = node.getId(); + + StringBuilder msg = new StringBuilder(moduleRevisionId.getOrganisation()).append("->") + .append(moduleRevisionId.getName()).append(' ').append(moduleRevisionId.getRevision()).append(": ") + .append(node.getProblemMessage()); + System.out.println("~\t" + msg.toString()); + if (logger != null) { + logger.notFound.add(msg.toString()); + } } } } - List installed = new ArrayList(); + List installed = new ArrayList<>(); // Install if (artifacts.isEmpty()) { System.out.println("~"); System.out.println("~ No dependencies to install"); } else { - System.out.println("~"); System.out.println("~ Installing resolved dependencies,"); System.out.println("~"); @@ -238,8 +273,10 @@ public List retrieve(ResolveReport report) throws Exception { } public File install(ArtifactDownloadReport artifact) throws Exception { - Boolean force = System.getProperty("play.forcedeps").equals("true"); - Boolean trim = System.getProperty("play.trimdeps").equals("true"); + boolean force = "true".equalsIgnoreCase(System.getProperty("play.forcedeps")); + boolean trim = "true".equalsIgnoreCase(System.getProperty("play.trimdeps")); + boolean shortModuleNames = "true".equalsIgnoreCase(System.getProperty("play.shortModuleNames")); + try { File from = artifact.getLocalFile(); @@ -263,10 +300,7 @@ public File install(ArtifactDownloadReport artifact) throws Exception { } else { // A module - String mName = from.getName(); - if (mName.endsWith(".jar") || mName.endsWith(".zip")) { - mName = mName.substring(0, mName.length() - 4); - } + String mName = moduleName(artifact, shortModuleNames); File to = new File(application, "modules" + File.separator + mName).getCanonicalFile(); new File(application, "modules").mkdir(); Files.delete(to); @@ -281,13 +315,13 @@ public File install(ArtifactDownloadReport artifact) throws Exception { Files.unzip(from, to); System.out.println("~ \tmodules/" + to.getName()); } - + if (trim) { for (File dirToTrim : to.listFiles(dirsToTrim)) { Files.deleteDirectory(dirToTrim); } } - + return to; } } catch (Exception e) { @@ -296,9 +330,22 @@ public File install(ArtifactDownloadReport artifact) throws Exception { } } + String moduleName(ArtifactDownloadReport artifact, boolean shortModuleNames) { + if (shortModuleNames) { + return artifact.getName(); + } + + String mName = artifact.getLocalFile().getName(); + if (mName.endsWith(".jar") || mName.endsWith(".zip")) { + mName = mName.substring(0, mName.length() - 4); + } + return mName; + } + private boolean isFrameworkLocal(ArtifactDownloadReport artifact) throws Exception { String artifactFileName = artifact.getLocalFile().getName(); - return new File(framework, "framework/lib/" + artifactFileName).exists() || new File(framework, "framework/" + artifactFileName).exists(); + return new File(framework, "framework/lib/" + artifactFileName).exists() + || new File(framework, "framework/" + artifactFileName).exists(); } private boolean isPlayModule(ArtifactDownloadReport artifact) throws Exception { @@ -318,75 +365,71 @@ private boolean isPlayModule(ArtifactDownloadReport artifact) throws Exception { } public ResolveReport resolve() throws Exception { - // Module ModuleDescriptorParserRegistry.getInstance().addParser(new YamlParser()); File ivyModule = new File(application, "conf/dependencies.yml"); - if(!ivyModule.exists()) { + if (!ivyModule.exists()) { System.out.println("~ !! " + ivyModule.getAbsolutePath() + " does not exist"); - System.exit(-1); + System.exit(-1); return null; } - // Variables System.setProperty("play.path", framework.getAbsolutePath()); - + // Ivy Ivy ivy = configure(); // Clear the cache boolean clearcache = System.getProperty("clearcache") != null; - if(clearcache){ - System.out.println("~ Clearing cache : " + ivy.getResolutionCacheManager().getResolutionCacheRoot() + ","); - System.out.println("~"); - try{ - FileUtils.deleteDirectory(ivy.getResolutionCacheManager().getResolutionCacheRoot()); - System.out.println("~ Clear"); - }catch(IOException e){ - System.out.println("~ Could not clear"); - System.out.println("~ "); - e.printStackTrace(); - } - - System.out.println("~"); - } - + if (clearcache) { + System.out.println("~ Clearing cache : " + ivy.getResolutionCacheManager().getResolutionCacheRoot() + ","); + System.out.println("~"); + try { + FileUtils.deleteDirectory(ivy.getResolutionCacheManager().getResolutionCacheRoot()); + System.out.println("~ Clear"); + } catch (IOException e) { + System.out.println("~ Could not clear"); + System.out.println("~ "); + e.printStackTrace(); + } + + System.out.println("~"); + } System.out.println("~ Resolving dependencies using " + ivyModule.getAbsolutePath() + ","); System.out.println("~"); - + // Resolve ResolveEngine resolveEngine = ivy.getResolveEngine(); ResolveOptions resolveOptions = new ResolveOptions(); - resolveOptions.setConfs(new String[]{"default"}); - resolveOptions.setArtifactFilter(FilterHelper.getArtifactTypeFilter(new String[]{"jar", "bundle", "source"})); + resolveOptions.setConfs(new String[] { "default" }); + resolveOptions.setArtifactFilter(FilterHelper.getArtifactTypeFilter(new String[] { "jar", "bundle", "source" })); return resolveEngine.resolve(ivyModule.toURI().toURL(), resolveOptions); } public Ivy configure() throws Exception { - boolean verbose = System.getProperty("verbose") != null; boolean debug = System.getProperty("debug") != null; - HumanReadyLogger humanReadyLogger = new HumanReadyLogger(); + this.logger = new HumanReadyLogger(); IvySettings ivySettings = new IvySettings(); - new SettingsParser(humanReadyLogger).parse(ivySettings, new File(framework, "framework/dependencies.yml")); - new SettingsParser(humanReadyLogger).parse(ivySettings, new File(application, "conf/dependencies.yml")); + new SettingsParser(this.logger).parse(ivySettings, new File(framework, "framework/dependencies.yml")); + new SettingsParser(this.logger).parse(ivySettings, new File(application, "conf/dependencies.yml")); ivySettings.setDefaultResolver("mavenCentral"); ivySettings.setDefaultUseOrigin(true); PlayConflictManager conflictManager = new PlayConflictManager(); ivySettings.addConflictManager("playConflicts", conflictManager); - ivySettings.addConflictManager("defaultConflicts", conflictManager.deleguate); + ivySettings.addConflictManager("defaultConflicts", conflictManager.delegate); ivySettings.setDefaultConflictManager(conflictManager); Ivy ivy = Ivy.newInstance(ivySettings); // Default ivy config see: http://play.lighthouseapp.com/projects/57987-play-framework/tickets/807 - if(userHome != null){ + if (userHome != null) { File ivyDefaultSettings = new File(userHome, ".ivy2/ivysettings.xml"); - if(ivyDefaultSettings != null && ivyDefaultSettings.exists()) { + if (ivyDefaultSettings != null && ivyDefaultSettings.exists()) { ivy.configure(ivyDefaultSettings); } } @@ -396,8 +439,7 @@ public Ivy configure() throws Exception { } else if (verbose) { ivy.getLoggerEngine().pushLogger(new DefaultMessageLogger(Message.MSG_INFO)); } else { - logger = humanReadyLogger; - ivy.getLoggerEngine().setDefaultLogger(logger); + ivy.getLoggerEngine().setDefaultLogger(this.logger); } ivy.pushContext(); diff --git a/framework/src/play/deps/HumanReadyLogger.java b/framework/src/play/deps/HumanReadyLogger.java index 6172ac8156..d7cfc9167a 100644 --- a/framework/src/play/deps/HumanReadyLogger.java +++ b/framework/src/play/deps/HumanReadyLogger.java @@ -13,9 +13,9 @@ public class HumanReadyLogger implements MessageLogger, TransferListener { - Set notFound = new HashSet(); - Set dynamics = new HashSet(); - Set evicteds = new HashSet(); + Set notFound = new HashSet<>(); + Set dynamics = new HashSet<>(); + Set evicteds = new HashSet<>(); Pattern dep = Pattern.compile("found ([^#]+)#([^;]+);([^\\s]+) in (.*)"); Pattern depNotFound = Pattern.compile("module not found: ([^#]+)#([^;]+);([^\\s]+)"); Pattern dynamic = Pattern.compile("\\[(.*)\\] ([^#]+)#([^;]+);([^\\s]+)"); @@ -40,7 +40,7 @@ public void niceLog(String msg, int level) { return; } - if (msg.startsWith("found ")) { // Depedency found + if (msg.startsWith("found ")) { // Dependency found if (msg.contains("playCore")) { return; } @@ -69,7 +69,7 @@ public void niceLog(String msg, int level) { Matcher m = evicted.matcher(msg); if (m.matches()) { - evicteds.add(m.group(2) + " " + m.group(3) + " is overriden by " + m.group(5) + " " + m.group(6)); + evicteds.add(m.group(2) + " " + m.group(3) + " is overridden by " + m.group(5) + " " + m.group(6)); return; } @@ -100,74 +100,93 @@ public void niceLog(String msg, int level) { } - // ~~~~~~ + @Override public void log(String string, int i) { niceLog(string, i); } + @Override public void rawlog(String string, int i) { niceLog(string, i); } + @Override public void debug(String string) { } + @Override public void verbose(String string) { } + @Override public void deprecated(String string) { } + @Override public void info(String string) { niceLog(string, Message.MSG_INFO); } + @Override public void rawinfo(String string) { niceLog(string, Message.MSG_INFO); } + @Override public void warn(String string) { niceLog(string, Message.MSG_WARN); } + @Override public void error(String string) { niceLog(string, Message.MSG_ERR); } - public List getProblems() { + @Override + public List getProblems() { return null; } - public List getWarns() { + @Override + public List getWarns() { return null; } - public List getErrors() { + @Override + public List getErrors() { return null; } + @Override public void clearProblems() { } + @Override public void sumupProblems() { } + @Override public void progress() { } + @Override public void endProgress() { } + @Override public void endProgress(String string) { } + @Override public boolean isShowProgress() { return false; } + @Override public void setShowProgress(boolean bln) { } + @Override public void transferProgress(TransferEvent te) { //System.out.println(te.getResource().getContentLength()); if (downloading != null) { diff --git a/framework/src/play/deps/PlayConflictManager.java b/framework/src/play/deps/PlayConflictManager.java index 8fa8b1c2c3..b70fcbc8ff 100644 --- a/framework/src/play/deps/PlayConflictManager.java +++ b/framework/src/play/deps/PlayConflictManager.java @@ -1,11 +1,5 @@ package play.deps; -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; import org.apache.ivy.core.module.descriptor.DependencyDescriptor; import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.core.resolve.IvyNode; @@ -13,20 +7,28 @@ import org.apache.ivy.plugins.conflict.LatestConflictManager; import org.apache.ivy.plugins.latest.LatestRevisionStrategy; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + public class PlayConflictManager extends AbstractConflictManager { - public LatestConflictManager deleguate = new LatestConflictManager(new LatestRevisionStrategy()); + public LatestConflictManager delegate = new LatestConflictManager(new LatestRevisionStrategy()); - public Collection resolveConflicts(IvyNode in, Collection conflicts) { + @Override + public Collection resolveConflicts(IvyNode in, Collection conflictsUntyped) { + Collection conflicts = conflictsUntyped; + // No conflict if (conflicts.size() < 2) { return conflicts; } // Force - for (Iterator iter = conflicts.iterator(); iter.hasNext();) { - IvyNode node = (IvyNode) iter.next(); + for (IvyNode node : conflicts) { DependencyDescriptor dd = node.getDependencyDescriptor(in); if (dd != null && dd.isForce() && in.getResolvedId().equals(dd.getParentRevisionId())) { return Collections.singleton(node); @@ -34,8 +36,7 @@ public Collection resolveConflicts(IvyNode in, Collection conflicts) { } boolean foundBuiltInDependency = false; - for (Iterator iter = conflicts.iterator(); iter.hasNext();) { - IvyNode node = (IvyNode) iter.next(); + for (IvyNode node : conflicts) { ModuleRevisionId modRev = node.getResolvedId(); File jar = new File(System.getProperty("play.path") + "/framework/lib/" + modRev.getName() + "-" + modRev.getRevision() + ".jar"); if(jar.exists()) { @@ -45,16 +46,15 @@ public Collection resolveConflicts(IvyNode in, Collection conflicts) { } if(!foundBuiltInDependency) { - return deleguate.resolveConflicts(in, conflicts); + return delegate.resolveConflicts(in, conflicts); } - /** + /* * Choose the artifact version provided in $PLAY/framework/lib * Evict other versions */ - List result = new ArrayList(); - for (Iterator iter = conflicts.iterator(); iter.hasNext();) { - IvyNode node = (IvyNode) iter.next(); + List result = new ArrayList<>(); + for (IvyNode node : conflicts) { ModuleRevisionId modRev = node.getResolvedId(); File jar = new File(System.getProperty("play.path") + "/framework/lib/" + modRev.getName() + "-" + modRev.getRevision() + ".jar"); if (jar.exists()) { diff --git a/framework/src/play/deps/SettingsParser.java b/framework/src/play/deps/SettingsParser.java index 3b2adce0a9..08c6b22e09 100644 --- a/framework/src/play/deps/SettingsParser.java +++ b/framework/src/play/deps/SettingsParser.java @@ -26,22 +26,22 @@ public Oops(String message) { super(message); } } - - HumanReadyLogger logger; + + private final HumanReadyLogger logger; public SettingsParser(HumanReadyLogger logger) { this.logger = logger; } public void parse(IvySettings settings, File desc) { - if(!desc.exists()) { + if (!desc.exists()) { System.out.println("~ !! " + desc.getAbsolutePath() + " does not exist"); return; } try { Yaml yaml = new Yaml(); - Object o = null; + Object o; // Try to parse the yaml try { @@ -55,7 +55,7 @@ public void parse(IvySettings settings, File desc) { throw new Oops("Unexpected format -> " + o); } - Map data = (Map) o; + Map data = (Map) o; parseIncludes(settings, data); @@ -63,8 +63,8 @@ public void parse(IvySettings settings, File desc) { if (data.get("repositories") instanceof List) { List repositories = (List) data.get("repositories"); - List> modules = new ArrayList>(); - for (Object dep: repositories) { + List> modules = new ArrayList<>(); + for (Object dep : repositories) { if (dep instanceof Map) { settings.addResolver(parseRepository((Map) dep, modules)); } else { @@ -72,8 +72,9 @@ public void parse(IvySettings settings, File desc) { } } - for (Map attributes: modules) { - settings.addModuleConfiguration(attributes, settings.getMatcher(PatternMatcher.EXACT_OR_REGEXP), (String) attributes.remove("resolver"), null, null, null); + for (Map attributes : modules) { + settings.addModuleConfiguration(attributes, settings.getMatcher(PatternMatcher.EXACT_OR_REGEXP), + (String) attributes.remove("resolver"), null, null, null); } } else { @@ -81,7 +82,6 @@ public void parse(IvySettings settings, File desc) { } } - } catch (Oops e) { System.out.println("~ Oops, malformed dependencies.yml descriptor:"); System.out.println("~"); @@ -94,10 +94,10 @@ public void parse(IvySettings settings, File desc) { /** * Look for an "include" property containing a list of yaml descriptors and load their repositories. */ - private void parseIncludes(IvySettings settings, Map data) throws Oops { + private void parseIncludes(IvySettings settings, Map data) throws Oops { if (data.containsKey("include") && data.get("include") != null) { if (data.get("include") instanceof List) { - List includes = (List)data.get("include"); + List includes = (List) data.get("include"); if (includes != null) { for (Object inc : includes) { File include = new File(substitute(inc.toString())); @@ -129,7 +129,7 @@ DependencyResolver parseRepository(Map repoDescriptor, List> if (type.equalsIgnoreCase("iBiblio")) { IBiblioResolver iBiblioResolver = new IBiblioResolver(); iBiblioResolver.setName(repName); - if(options.containsKey("root")) { + if (options.containsKey("root")) { iBiblioResolver.setRoot(get(options, "root", String.class)); } iBiblioResolver.setM2compatible(get(options, "m2compatible", boolean.class, true)); @@ -170,7 +170,7 @@ DependencyResolver parseRepository(Map repoDescriptor, List> chainResolver.setReturnFirst(true); for (Object o : get(options, "using", List.class, new ArrayList())) { DependencyResolver res = parseRepository((Map) o, modules); - if(res instanceof FileSystemResolver) { + if (res instanceof FileSystemResolver) { chainResolver.setCheckmodified(true); } chainResolver.add(res); @@ -191,15 +191,27 @@ DependencyResolver parseRepository(Map repoDescriptor, List> String revision = null; Matcher m = Pattern.compile("([^\\s]+)\\s*[-][>]\\s*([^\\s]+)\\s+([^\\s]+)").matcher(v); if (m.matches()) { - organisation = m.group(1); - module = m.group(2); - revision = m.group(3).replace("$version", System.getProperty("play.version")); - } else { - m = Pattern.compile("(([^\\s]+))\\s+([^\\s]+)").matcher(v); - if (m.matches()) { + if (m.groupCount() > 0) { organisation = m.group(1); + } + if (m.groupCount() > 1) { module = m.group(2); + } + if (m.groupCount() > 2) { revision = m.group(3).replace("$version", System.getProperty("play.version")); + } + } else { + m = Pattern.compile("(([^\\s]+))\\s+([^\\s]+)").matcher(v); + if (m.matches()) { + if (m.groupCount() > 0) { + organisation = m.group(1); + } + if (m.groupCount() > 1) { + module = m.group(2); + } + if (m.groupCount() > 2) { + revision = m.group(3).replace("$version", System.getProperty("play.version")); + } } else { m = Pattern.compile("([^\\s]+)\\s*[-][>]\\s*([^\\s]+)").matcher(v); if (m.matches()) { @@ -210,12 +222,12 @@ DependencyResolver parseRepository(Map repoDescriptor, List> if (m.matches()) { organisation = m.group(1); } else { - throw new Oops("Unknown depedency format -> " + o); + throw new Oops("Unknown dependency format -> " + o); } } } } - Map attributes = new HashMap(); + Map attributes = new HashMap<>(); attributes.put("organisation", organisation); if (module != null) { attributes.put("module", module); @@ -246,20 +258,21 @@ T get(Map data, String key, Class type) throws Oops { } /** - * Substitute environment variables found in a String with their value. - * This function search for ${variable} patterns and replace them with - * their value taken from current environment. + * Substitute environment variables found in a String with their value. This function search for + * ${variable} patterns and replace them with their value taken from current environment. * - * @param s String to substitute + * @param s + * String to substitute * @return The substituted String - * @throws Oops If an environment variable is not found + * @throws Oops + * If an environment variable is not found */ private String substitute(String s) throws Oops { - Matcher m = Pattern.compile("\\$\\{([^\\}]*)\\}").matcher((String)s); //search of ${something} group(1) => something + Matcher m = Pattern.compile("\\$\\{([^\\}]*)\\}").matcher(s); // search of ${something} group(1) => something while (m.find()) { String propertyValue = System.getProperty(m.group(1)); - if(propertyValue != null){ - s = s.replace("${" + m.group(1) + "}",propertyValue); + if (propertyValue != null) { + s = s.replace("${" + m.group(1) + "}", propertyValue); } else { throw new Oops("Unknown property " + m.group(1) + " in " + s); } diff --git a/framework/src/play/deps/YamlParser.java b/framework/src/play/deps/YamlParser.java index fddcd219f0..4031b08a0a 100644 --- a/framework/src/play/deps/YamlParser.java +++ b/framework/src/play/deps/YamlParser.java @@ -43,18 +43,19 @@ public class YamlParser extends AbstractModuleDescriptorParser { static class Oops extends Exception { - public Oops(String message) { super(message); } } + @Override public boolean accept(Resource rsrc) { return rsrc.exists() && rsrc.getName().endsWith(".yml"); } - - - + + + + @Override public ModuleDescriptor parseDescriptor(ParserSettings ps, URL url, Resource rsrc, boolean bln) throws ParseException, IOException { try { InputStream srcStream = rsrc.openStream(); @@ -112,7 +113,7 @@ public ModuleDescriptorParser getParser() { boolean transitiveDependencies = get(data, "transitiveDependencies", boolean.class, true); - List confs = new ArrayList(); + List confs = new ArrayList<>(); if (data.containsKey("configurations")) { if (data.get("configurations") instanceof List) { boolean allExcludes = true; @@ -175,6 +176,9 @@ public ModuleDescriptorParser getParser() { if(depName.matches("play\\s+->\\s+secure") || depName.equals("secure")) { depName = "play -> secure " + System.getProperty("play.version"); } + if(depName.matches("play\\s+->\\s+docviewer") || depName.equals("docviewer")) { + depName = "play -> docviewer " + System.getProperty("play.version"); + } // Pattern compile to match [organisation name] - > [artifact] [revision] [classifier] Matcher m = Pattern.compile("([^\\s]+)\\s*[-][>]\\s*([^\\s]+)\\s+([^\\s]+)(\\s+[^\\s]+)?.*").matcher(depName); @@ -188,11 +192,11 @@ public ModuleDescriptorParser getParser() { } } HashMap extraAttributesMap = null; - if(m.groupCount() == 4 && m.group(4) != null && !m.group(4).trim().isEmpty()){ - // dependency has a classifier - extraAttributesMap = new HashMap(); - extraAttributesMap.put("classifier", m.group(4).trim()); - } + if (m.groupCount() == 4 && m.group(4) != null && !m.group(4).trim().isEmpty()) { + // dependency has a classifier + extraAttributesMap = new HashMap(); + extraAttributesMap.put("classifier", m.group(4).trim()); + } ModuleRevisionId depId = ModuleRevisionId.newInstance(m.group(1), m.group(2), m.group(3), extraAttributesMap); @@ -267,8 +271,9 @@ public ModuleDescriptorParser getParser() { } } + @Override public void toIvyFile(InputStream in, Resource rsrc, File file, ModuleDescriptor md) throws ParseException, IOException { - ((DefaultModuleDescriptor)md).toIvyFile(file); + md.toIvyFile(file); } @SuppressWarnings("unchecked") @@ -294,13 +299,13 @@ T get(Map data, String key, Class type, T defaultValue) { return o; } - public static Set getOrderedModuleList(File file) throws FileNotFoundException, ParseException, IOException { - Set modules = new LinkedHashSet(); + public static Set getOrderedModuleList(File file) throws ParseException, IOException { + Set modules = new LinkedHashSet<>(); System.setProperty("application.path", Play.applicationPath.getAbsolutePath()); return getOrderedModuleList(modules, file); } - private static Set getOrderedModuleList(Set modules, File file) throws FileNotFoundException, ParseException, IOException { + private static Set getOrderedModuleList(Set modules, File file) throws ParseException, IOException { if (file == null || !file.exists()) { throw new FileNotFoundException("There was a problem to find the file"); } diff --git a/framework/src/play/exceptions/ActionNotFoundException.java b/framework/src/play/exceptions/ActionNotFoundException.java index 89763f84d6..353461a1b8 100644 --- a/framework/src/play/exceptions/ActionNotFoundException.java +++ b/framework/src/play/exceptions/ActionNotFoundException.java @@ -1,12 +1,9 @@ package play.exceptions; -/** - * Missing action - */ public class ActionNotFoundException extends PlayException { private String action; - + public ActionNotFoundException(String action, Throwable cause) { super(String.format("Action %s not found", action.startsWith("controllers.") ? action.substring(12) : action), cause); this.action = action.startsWith("controllers.") ? action.substring(12) : action; @@ -18,15 +15,15 @@ public String getAction() { @Override public String getErrorTitle() { - return String.format("Action not found"); + return "Action not found"; } @Override public String getErrorDescription() { return String.format( - "Action %s could not be found. Error raised is %s", - action, - getCause() instanceof ClassNotFoundException ? "ClassNotFound: "+getCause().getMessage() : getCause().getMessage() + "Action %s could not be found. Error raised is %s", + action, + getCause() instanceof ClassNotFoundException ? "ClassNotFound: " + getCause().getMessage() : getCause().getMessage() ); } } diff --git a/framework/src/play/exceptions/CacheException.java b/framework/src/play/exceptions/CacheException.java index 88681d5dd8..7529c4def9 100644 --- a/framework/src/play/exceptions/CacheException.java +++ b/framework/src/play/exceptions/CacheException.java @@ -1,8 +1,5 @@ package play.exceptions; -/** - * Cache related exception - */ public class CacheException extends PlayExceptionWithJavaSource { public CacheException(String message, Throwable cause) { diff --git a/framework/src/play/exceptions/CompilationException.java b/framework/src/play/exceptions/CompilationException.java index 4d9a9098d0..be74498742 100644 --- a/framework/src/play/exceptions/CompilationException.java +++ b/framework/src/play/exceptions/CompilationException.java @@ -31,12 +31,12 @@ public CompilationException(VirtualFile source, String problem, int line, int st @Override public String getErrorTitle() { - return String.format("Compilation error"); + return "Compilation error"; } @Override public String getErrorDescription() { - return String.format("The file %s could not be compiled.\nError raised is : %s", isSourceAvailable() ? source.relativePath() : "", problem.toString().replace("<", "<")); + return String.format("The file %s could not be compiled.\nError raised is : %s", isSourceAvailable() ? source.relativePath() : "", problem.replace("<", "<")); } @Override @@ -44,10 +44,11 @@ public String getMessage() { return problem; } + @Override public List getSource() { String sourceCode = source.contentAsString(); - if(start != -1 && end != -1) { - if(start.equals(end)) { + if (start != -1 && end != -1) { + if (start.equals(end)) { sourceCode = sourceCode.substring(0, start + 1) + "↓" + sourceCode.substring(end + 1); } else { sourceCode = sourceCode.substring(0, start) + "\000" + sourceCode.substring(start, end + 1) + "\001" + sourceCode.substring(end + 1); @@ -78,5 +79,4 @@ public Integer getSourceEnd() { public boolean isSourceAvailable() { return source != null && line != null; } - } diff --git a/framework/src/play/exceptions/ContinuationsException.java b/framework/src/play/exceptions/ContinuationsException.java index 62ffac8de8..abd61f6218 100644 --- a/framework/src/play/exceptions/ContinuationsException.java +++ b/framework/src/play/exceptions/ContinuationsException.java @@ -13,7 +13,7 @@ public String getErrorTitle() { @Override public String getErrorDescription() { - return String.format("A await/Continuations error occured : %s", getMessage()); + return String.format("A await/Continuations error occurred : %s", getMessage()); } } diff --git a/framework/src/play/exceptions/DatabaseException.java b/framework/src/play/exceptions/DatabaseException.java index 0201ef544d..7bbf717889 100644 --- a/framework/src/play/exceptions/DatabaseException.java +++ b/framework/src/play/exceptions/DatabaseException.java @@ -1,9 +1,6 @@ package play.exceptions; -/** - * Database error - */ public class DatabaseException extends PlayExceptionWithJavaSource { public DatabaseException(String message) { @@ -21,6 +18,6 @@ public String getErrorTitle() { @Override public String getErrorDescription() { - return String.format("A database error occured : %s", getMessage()); + return String.format("A database error occurred : %s", getMessage()); } } diff --git a/framework/src/play/exceptions/JPAException.java b/framework/src/play/exceptions/JPAException.java index e125b47d43..9fa0127f72 100644 --- a/framework/src/play/exceptions/JPAException.java +++ b/framework/src/play/exceptions/JPAException.java @@ -4,9 +4,6 @@ import java.util.List; import org.hibernate.exception.GenericJDBCException; -/** - * JPA exception - */ public class JPAException extends PlayException implements SourceAttachment { public JPAException(String message) { @@ -26,7 +23,8 @@ public String getErrorTitle() { public String getErrorDescription() { if(getCause() != null && getCause() instanceof GenericJDBCException) { String SQL = ((GenericJDBCException)getCause()).getSQL(); - return String.format("A JPA error occurred (%s): %s. This is likely because the batch has broken some referential integrity. Check your cascade delete, in case of ...", getMessage(), getCause() == null ? "" : getCause().getMessage(), SQL); + return String.format("A JPA error occurred (%s): %s. This is likely because the batch has broken some referential integrity. Check your cascade delete. SQL: (%s)", + getMessage(), getCause() == null ? "" : getCause().getMessage(), SQL); } return String.format("A JPA error occurred (%s): %s", getMessage(), getCause() == null ? "" : getCause().getMessage()); } @@ -41,9 +39,10 @@ public Integer getLineNumber() { return 1; } + @Override public List getSource() { - List sql = new ArrayList(); - if(getCause() != null && getCause() instanceof GenericJDBCException) { + List sql = new ArrayList<>(); + if (getCause() != null && getCause() instanceof GenericJDBCException) { sql.add(((GenericJDBCException)getCause()).getSQL()); } return sql; diff --git a/framework/src/play/exceptions/JavaException.java b/framework/src/play/exceptions/JavaException.java index 92bf1c3474..32bc7c395b 100644 --- a/framework/src/play/exceptions/JavaException.java +++ b/framework/src/play/exceptions/JavaException.java @@ -2,16 +2,9 @@ import play.classloading.ApplicationClasses.ApplicationClass; -/** - * A Java exception - */ -public abstract class JavaException extends PlayExceptionWithJavaSource { +abstract class JavaException extends PlayExceptionWithJavaSource { - public JavaException(ApplicationClass applicationClass, Integer line, String message) { - super(message, null, applicationClass, line); - } - - public JavaException(ApplicationClass applicationClass, Integer line, String message, Throwable cause) { + JavaException(ApplicationClass applicationClass, Integer line, String message, Throwable cause) { super(message, cause, applicationClass, line); } diff --git a/framework/src/play/exceptions/JavaExecutionException.java b/framework/src/play/exceptions/JavaExecutionException.java index c55d449004..a12030cd23 100644 --- a/framework/src/play/exceptions/JavaExecutionException.java +++ b/framework/src/play/exceptions/JavaExecutionException.java @@ -2,31 +2,24 @@ import play.classloading.ApplicationClasses.ApplicationClass; -/** - * An exception occured during Java execution - */ public class JavaExecutionException extends JavaException { public JavaExecutionException(ApplicationClass applicationClass, Integer lineNumber, Throwable e) { super(applicationClass, lineNumber, e.getMessage(), e); } - public JavaExecutionException(String action, Throwable e) { - super(null, null, e.getMessage(), e); - } - public JavaExecutionException(Throwable e) { super(null, null, e.getMessage(), e); } @Override public String getErrorTitle() { - return String.format("Execution exception"); + return "Execution exception"; } @Override public String getErrorDescription() { - return String.format("%s occured : %s", getCause().getClass().getSimpleName(), getMessage()); + return String.format("%s occurred : %s", getCause().getClass().getSimpleName(), getMessage()); } } diff --git a/framework/src/play/exceptions/MailException.java b/framework/src/play/exceptions/MailException.java index d47207ab15..7f7c12ab94 100644 --- a/framework/src/play/exceptions/MailException.java +++ b/framework/src/play/exceptions/MailException.java @@ -21,6 +21,6 @@ public String getErrorTitle() { @Override public String getErrorDescription() { - return String.format("A mail error occured : %s", getMessage()); + return String.format("A mail error occurred : %s", getMessage()); } } diff --git a/framework/src/play/exceptions/NoRouteFoundException.java b/framework/src/play/exceptions/NoRouteFoundException.java index ebe541d737..1a7a1370e9 100644 --- a/framework/src/play/exceptions/NoRouteFoundException.java +++ b/framework/src/play/exceptions/NoRouteFoundException.java @@ -89,6 +89,7 @@ public boolean isSourceAvailable() { return source != null; } + @Override public String getSourceFile() { return sourceFile; } @@ -98,10 +99,12 @@ public String getFile() { } + @Override public List getSource() { return source; } + @Override public Integer getLineNumber() { return line; } diff --git a/framework/src/play/exceptions/PlayException.java b/framework/src/play/exceptions/PlayException.java index 017e893c9c..65bb7ab638 100644 --- a/framework/src/play/exceptions/PlayException.java +++ b/framework/src/play/exceptions/PlayException.java @@ -9,8 +9,8 @@ */ public abstract class PlayException extends RuntimeException { - static AtomicLong atomicLong = new AtomicLong(System.currentTimeMillis()); - String id; + private static AtomicLong atomicLong = new AtomicLong(System.currentTimeMillis()); + private String id; public PlayException() { setId(); @@ -51,12 +51,6 @@ public String getId() { return id; } - /** - * Get the stack trace element - * @deprecated since 1.3.0 - * @param cause - * @return the stack trace element - */ @Deprecated public static StackTraceElement getInterestingStrackTraceElement(Throwable cause) { return getInterestingStackTraceElement(cause); diff --git a/framework/src/play/exceptions/PlayExceptionWithJavaSource.java b/framework/src/play/exceptions/PlayExceptionWithJavaSource.java index 2cff1a377c..0f78d9adb7 100644 --- a/framework/src/play/exceptions/PlayExceptionWithJavaSource.java +++ b/framework/src/play/exceptions/PlayExceptionWithJavaSource.java @@ -12,9 +12,6 @@ public abstract class PlayExceptionWithJavaSource extends PlayException implemen ApplicationClass applicationClass; Integer line; - protected PlayExceptionWithJavaSource() { - } - protected PlayExceptionWithJavaSource(String message) { super(message); } diff --git a/framework/src/play/exceptions/RestartNeededException.java b/framework/src/play/exceptions/RestartNeededException.java new file mode 100644 index 0000000000..7de86f6117 --- /dev/null +++ b/framework/src/play/exceptions/RestartNeededException.java @@ -0,0 +1,11 @@ +package play.exceptions; + +public class RestartNeededException extends Exception { + public RestartNeededException(String message) { + super(message); + } + + public RestartNeededException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/framework/src/play/exceptions/TagInternalException.java b/framework/src/play/exceptions/TagInternalException.java index 8461d2f4ac..d3e35bdc42 100644 --- a/framework/src/play/exceptions/TagInternalException.java +++ b/framework/src/play/exceptions/TagInternalException.java @@ -9,4 +9,8 @@ public TagInternalException(String message) { super(message); } + public TagInternalException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/framework/src/play/exceptions/TemplateCompilationException.java b/framework/src/play/exceptions/TemplateCompilationException.java index 8632ce4014..303a394550 100644 --- a/framework/src/play/exceptions/TemplateCompilationException.java +++ b/framework/src/play/exceptions/TemplateCompilationException.java @@ -13,7 +13,7 @@ public TemplateCompilationException(Template template, Integer lineNumber, Strin @Override public String getErrorTitle() { - return String.format("Template compilation error"); + return "Template compilation error"; } @Override diff --git a/framework/src/play/exceptions/TemplateException.java b/framework/src/play/exceptions/TemplateException.java index 4c6e7662ff..5c26db5285 100644 --- a/framework/src/play/exceptions/TemplateException.java +++ b/framework/src/play/exceptions/TemplateException.java @@ -28,14 +28,17 @@ public Template getTemplate() { return template; } + @Override public Integer getLineNumber() { return lineNumber; } + @Override public List getSource() { return Arrays.asList(template.source.split("\n")); } + @Override public String getSourceFile() { return template.name; } diff --git a/framework/src/play/exceptions/TemplateExecutionException.java b/framework/src/play/exceptions/TemplateExecutionException.java index 38a5ad791c..f3991e2495 100644 --- a/framework/src/play/exceptions/TemplateExecutionException.java +++ b/framework/src/play/exceptions/TemplateExecutionException.java @@ -13,12 +13,12 @@ public TemplateExecutionException(Template template, Integer lineNumber, String @Override public String getErrorTitle() { - return String.format("Template execution error"); + return "Template execution error"; } @Override public String getErrorDescription() { - return String.format("Execution error occured in template %s. Exception raised was %s : %s.", getSourceFile(), getCause().getClass().getSimpleName(), getMessage()); + return String.format("Execution error occurred in template %s. Exception raised was %s : %s.", getSourceFile(), getCause().getClass().getSimpleName(), getMessage()); } public static class DoBodyException extends RuntimeException { diff --git a/framework/src/play/exceptions/TemplateNotFoundException.java b/framework/src/play/exceptions/TemplateNotFoundException.java index b3c2668974..629244044b 100644 --- a/framework/src/play/exceptions/TemplateNotFoundException.java +++ b/framework/src/play/exceptions/TemplateNotFoundException.java @@ -6,9 +6,6 @@ import play.classloading.ApplicationClasses.ApplicationClass; import play.templates.Template; -/** - * A template is missing (tag, ...) - */ public class TemplateNotFoundException extends PlayException implements SourceAttachment { private String path; @@ -53,7 +50,7 @@ public String getPath() { @Override public String getErrorTitle() { - return String.format("Template not found"); + return "Template not found"; } @Override diff --git a/framework/src/play/exceptions/UnexpectedException.java b/framework/src/play/exceptions/UnexpectedException.java index 5af6d7f075..0478b10657 100644 --- a/framework/src/play/exceptions/UnexpectedException.java +++ b/framework/src/play/exceptions/UnexpectedException.java @@ -1,8 +1,5 @@ package play.exceptions; -/** - * An unexpected exception - */ public class UnexpectedException extends PlayException { public UnexpectedException(String message) { @@ -19,7 +16,7 @@ public UnexpectedException(String message, Throwable cause) { @Override public String getErrorTitle() { - if(getCause() == null) { + if (getCause() == null) { return "Unexpected error"; } return String.format("Oops: %s", getCause().getClass().getSimpleName()); @@ -27,9 +24,13 @@ public String getErrorTitle() { @Override public String getErrorDescription() { - if(getCause() != null && getCause().getClass() != null) - return String.format("An unexpected error occured caused by exception %s:
%s", getCause().getClass().getSimpleName(), getCause().getMessage()); - else return String.format("Unexpected error : %s", getMessage()); + if (getCause() != null && getCause().getClass() != null) { + return String.format("Unexpected error : %s, caused by exception %s:
%s", + getMessage(), getCause().getClass().getSimpleName(), getCause().getMessage()); + } + else { + return String.format("Unexpected error : %s", getMessage()); + } } } diff --git a/framework/src/play/exceptions/YAMLException.java b/framework/src/play/exceptions/YAMLException.java index cea2286b34..7179d8a98f 100644 --- a/framework/src/play/exceptions/YAMLException.java +++ b/framework/src/play/exceptions/YAMLException.java @@ -29,14 +29,17 @@ public String getErrorDescription() { return "Cannot parse the " + yaml.relativePath() + " file: " + e.getProblem(); } + @Override public Integer getLineNumber() { return e.getProblemMark().getLine() + 1; } + @Override public List getSource() { return Arrays.asList(yaml.contentAsString().split("\n")); } + @Override public String getSourceFile() { return yaml.relativePath(); } diff --git a/framework/src/play/i18n/Lang.java b/framework/src/play/i18n/Lang.java index 2b6bf77449..de07d7fcb1 100644 --- a/framework/src/play/i18n/Lang.java +++ b/framework/src/play/i18n/Lang.java @@ -1,10 +1,5 @@ package play.i18n; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; - import play.Logger; import play.Play; import play.mvc.Http; @@ -12,15 +7,20 @@ import play.mvc.Http.Response; import play.mvc.Scope; +import java.util.*; + /** * Language support */ public class Lang { - public static ThreadLocal current = new ThreadLocal(); + static final ThreadLocal current = new ThreadLocal<>(); + private static Map cache = new HashMap<>(); + /** * Retrieve the current language or null + * * @return The current language (fr, ja, it ...) or null */ public static String get() { @@ -28,9 +28,9 @@ public static String get() { if (locale == null) { // don't have current locale for this request - must try to resolve it Http.Request currentRequest = Http.Request.current(); - if (currentRequest!=null) { + if (currentRequest != null) { // we have a current request - lets try to resolve language from it - resolvefrom( currentRequest ); + resolveFrom(currentRequest); } else { // don't have current request - just use default setDefaultLocale(); @@ -42,9 +42,9 @@ public static String get() { } - /** * Force the current language + * * @param locale (fr, ja, it ...) * @return false if the language is not supported by the application */ @@ -68,18 +68,19 @@ public static void clear() { /** - * Change language for next requests + * Change language for next requests + * * @param locale (e.g. "fr", "ja", "it", "en_ca", "fr_be", ...) */ public static void change(String locale) { String closestLocale = findClosestMatch(Collections.singleton(locale)); - if ( closestLocale == null ) { + if (closestLocale == null) { // Give up - return ; + return; } - if ( set(closestLocale) ) { + if (set(closestLocale)) { Response response = Response.current(); - if ( response != null ) { + if (response != null) { // We have a current response in scope - set the language-cookie to store the selected language for the next requests response.setCookie(Play.configuration.getProperty("application.lang.cookie", "PLAY_LANG"), locale, null, "/", null, Scope.COOKIE_SECURE); } @@ -98,24 +99,24 @@ public static void change(String locale) { * @return the closest matching locale. If no closest match for a language/country is found, null is returned */ private static String findClosestMatch(Collection desiredLocales) { - ArrayList cleanLocales = new ArrayList(desiredLocales.size()); + ArrayList cleanLocales = new ArrayList<>(desiredLocales.size()); //look for an exact match - for (String a: desiredLocales) { + for (String a : desiredLocales) { a = a.replace("-", "_"); cleanLocales.add(a); - for (String locale: Play.langs) { + for (String locale : Play.langs) { if (locale.equalsIgnoreCase(a)) { return locale; } } } // Exact match not found, try language-only match. - for (String a: cleanLocales) { + for (String a : cleanLocales) { int splitPos = a.indexOf("_"); if (splitPos > 0) { a = a.substring(0, splitPos); } - for (String locale: Play.langs) { + for (String locale : Play.langs) { String langOnlyLocale; int localeSplitPos = locale.indexOf("_"); if (localeSplitPos > 0) { @@ -140,14 +141,15 @@ private static String findClosestMatch(Collection desiredLocales) { *
  • if Accept-Language header is set, use it only if the Play! application allows it.
    supported language may be defined in application configuration, eg : play.langs=fr,en,de)
  • *
  • otherwise, server's locale language is assumed * - * @param request + * + * @param request current request */ - private static void resolvefrom(Request request) { + private static void resolveFrom(Request request) { // Check a cookie String cn = Play.configuration.getProperty("application.lang.cookie", "PLAY_LANG"); if (request.cookies.containsKey(cn)) { String localeFromCookie = request.cookies.get(cn).value; - if (localeFromCookie != null && localeFromCookie.trim().length()>0) { + if (localeFromCookie != null && localeFromCookie.trim().length() > 0) { if (set(localeFromCookie)) { // we're using locale from cookie return; @@ -159,7 +161,7 @@ private static void resolvefrom(Request request) { } String closestLocaleMatch = findClosestMatch(request.acceptLanguage()); - if ( closestLocaleMatch != null ) { + if (closestLocaleMatch != null) { set(closestLocaleMatch); } else { // Did not find anything - use default @@ -177,7 +179,6 @@ public static void setDefaultLocale() { } /** - * * @return the default locale if the Locale cannot be found otherwise the locale * associated to the current Lang. */ @@ -193,24 +194,36 @@ public static Locale getLocaleOrDefault(String localeStr) { return Locale.getDefault(); } - public static Locale getLocale(String localeStr) { - if(localeStr == null) { - return null; + public static Locale getLocale(String localeStr) { + if (localeStr == null) { + return null; + } + + Locale result = cache.get(localeStr); + + if (result == null) { + result = findLocale(localeStr); + cache.put(localeStr, result); } - Locale langMatch = null; + + return result; + } + + private static Locale findLocale(String localeStr) { String lang = localeStr; int splitPos = lang.indexOf("_"); if (splitPos > 0) { - lang = lang.substring(0, splitPos); + lang = lang.substring(0, splitPos); } + + Locale result = null; for (Locale locale : Locale.getAvailableLocales()) { if (locale.toString().equalsIgnoreCase(localeStr)) { return locale; } else if (locale.getLanguage().equalsIgnoreCase(lang)) { - langMatch = locale; + result = locale; } } - return langMatch; + return result; } - } diff --git a/framework/src/play/i18n/Localized.java b/framework/src/play/i18n/Localized.java index 045e170bf7..960db2ae32 100644 --- a/framework/src/play/i18n/Localized.java +++ b/framework/src/play/i18n/Localized.java @@ -10,7 +10,7 @@ */ public class Localized { - private Map values = new HashMap(); + private Map values = new HashMap<>(); public void set(T value) { this.values.put(Lang.get(), value); @@ -30,7 +30,7 @@ public T get(String lang) { @SuppressWarnings("unchecked") public Set values() { - return new HashSet(values.values()); + return new HashSet<>(values.values()); } public Set lang() { diff --git a/framework/src/play/i18n/Messages.java b/framework/src/play/i18n/Messages.java index 9baaf91f14..6688deaf29 100644 --- a/framework/src/play/i18n/Messages.java +++ b/framework/src/play/i18n/Messages.java @@ -1,6 +1,11 @@ package play.i18n; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -10,35 +15,37 @@ /** * I18n Helper *

    - * translation are defined as properties in /conf/messages.locale files - * with locale being the i18n country code fr, en, fr_FR + * translation are defined as properties in /conf/messages.locale files with locale being the i18n country code + * fr, en, fr_FR * *

      * # /conf/messages.fr
      * hello=Bonjour, %s !
      * 
    + * * - * Messages.get( "hello", "World"); // => "Bonjour, World !" + * Messages.get( "hello", "World"); // => "Bonjour, World !" * * */ public class Messages { - private static final Object[] NO_ARGS = new Object[]{null}; + private static final Object[] NO_ARGS = new Object[] { null }; - static public Properties defaults; + public static Properties defaults = new Properties(); - static public Map locales = new HashMap(); + public static Map locales = new HashMap<>(); - static Pattern recursive = Pattern.compile("&\\{(.*?)\\}"); + private static final Pattern recursive = Pattern.compile("&\\{(.*?)\\}"); /** - * Given a message code, translate it using current locale. - * If there is no message in the current locale for the given key, the key - * is returned. + * Given a message code, translate it using current locale. If there is no message in the current locale for the + * given key, the key is returned. * - * @param key the message code - * @param args optional message format arguments + * @param key + * the message code + * @param args + * optional message format arguments * @return translated message */ public static String get(Object key, Object... args) { @@ -47,29 +54,33 @@ public static String get(Object key, Object... args) { /** * Return several messages for a locale - * @param locale the locale code, e.g. fr, fr_FR - * @param keys the keys to get messages from. Wildcards can be used at the end: {'title', 'login.*'} + * + * @param locale + * the locale code, e.g. fr, fr_FR + * @param keys + * the keys to get messages from. Wildcards can be used at the end: {'title', 'login.*'} * @return messages as a {@link java.util.Properties java.util.Properties} */ public static Properties find(String locale, Set keys) { Properties result = new Properties(); Properties all = all(locale); // Expand the set for wildcards - Set wildcards = new HashSet(); - for (String key: keys) { - if (key.endsWith("*")) wildcards.add(key); + Set wildcards = new HashSet<>(); + for (String key : keys) { + if (key.endsWith("*")) + wildcards.add(key); } - for (String key: wildcards) { + for (String key : wildcards) { keys.remove(key); String start = key.substring(0, key.length() - 1); - for (Object key2: all.keySet()) { - if (((String)key2).startsWith(start)) { - keys.add((String)key2); + for (Object key2 : all.keySet()) { + if (((String) key2).startsWith(start)) { + keys.add((String) key2); } } } // Build the result - for (Object key: all.keySet()) { + for (Object key : all.keySet()) { if (keys.contains(key)) { result.put(key, all.get(key)); } @@ -80,22 +91,23 @@ public static Properties find(String locale, Set keys) { public static String getMessage(String locale, Object key, Object... args) { // Check if there is a plugin that handles translation String message = Play.pluginCollection.getMessage(locale, key, args); - - if(message != null) { + if (message != null) { return message; } - - String value = null; - if( key == null ) { + + if (key == null) { return ""; } - if (locales.containsKey(locale)) { - value = locales.get(locale).getProperty(key.toString()); - } - if (value == null && locale != null && locale.length() == 5 && locales.containsKey(locale.substring(0, 2))) { - value = locales.get(locale.substring(0, 2)).getProperty(key.toString()); + String value = null; + if (locales != null) { + if (locales.containsKey(locale)) { + value = locales.get(locale).getProperty(key.toString()); + } + if (value == null && locale != null && locale.length() == 5 && locales.containsKey(locale.substring(0, 2))) { + value = locales.get(locale.substring(0, 2)).getProperty(key.toString()); + } } - if (value == null) { + if (value == null && defaults != null) { value = defaults.getProperty(key.toString()); } if (value == null) { @@ -114,7 +126,7 @@ public static String formatString(Locale locale, String value, Object... args) { Matcher matcher = recursive.matcher(message); StringBuffer sb = new StringBuffer(); - while(matcher.find()) { + while (matcher.find()) { matcher.appendReplacement(sb, get(matcher.group(1))); } matcher.appendTail(sb); @@ -125,43 +137,45 @@ public static String formatString(Locale locale, String value, Object... args) { @SuppressWarnings("unchecked") static Object[] coolStuff(String pattern, Object[] args) { - // when invoked with a null argument we get a null args instead of an array with a null value. + // when invoked with a null argument we get a null args instead of an + // array with a null value. - if(args == null) - return NO_ARGS; + if (args == null) + return NO_ARGS; Class[] conversions = new Class[args.length]; Matcher matcher = formatterPattern.matcher(pattern); int incrementalPosition = 1; - while(matcher.find()) { + while (matcher.find()) { String conversion = matcher.group(6); Integer position; - if(matcher.group(2) == null) { + if (matcher.group(2) == null) { position = incrementalPosition++; } else { position = Integer.parseInt(matcher.group(2)); } - if(conversion.equals("d") && position <= conversions.length) { - conversions[position-1] = Long.class; + if (conversion.equals("d") && position <= conversions.length) { + conversions[position - 1] = Long.class; } - if(conversion.equals("f") && position <= conversions.length) { - conversions[position-1] = Double.class; + if (conversion.equals("f") && position <= conversions.length) { + conversions[position - 1] = Double.class; } } Object[] result = new Object[args.length]; - for(int i=0; i < args.length; i++) { - if(args[i] == null) { + for (int i = 0; i < args.length; i++) { + if (args[i] == null) { continue; } - if(conversions[i] == null) { + if (conversions[i] == null) { result[i] = args[i]; } else { try { - // TODO: I think we need to type of direct bind -> primitive and object binder + // TODO: I think we need to type of direct bind -> primitive + // and object binder result[i] = Binder.directBind(null, args[i] + "", conversions[i], null); - } catch(Exception e) { + } catch (Exception e) { // Ignore result[i] = null; } @@ -172,18 +186,23 @@ static Object[] coolStuff(String pattern, Object[] args) { /** * return all messages for a locale - * @param locale the locale code eg. fr, fr_FR + * + * @param locale + * the locale code eg. fr, fr_FR * @return messages as a {@link java.util.Properties java.util.Properties} */ public static Properties all(String locale) { - if(locale == null || "".equals(locale)) + if (locale == null || "".equals(locale)) { return defaults; + } Properties mergedMessages = new Properties(); mergedMessages.putAll(defaults); - if (locale != null && locale.length() == 5 && locales.containsKey(locale.substring(0, 2))) { - mergedMessages.putAll(locales.get(locale.substring(0, 2))); + if (locales != null) { + if (locale.length() == 5 && locales.containsKey(locale.substring(0, 2))) { + mergedMessages.putAll(locales.get(locale.substring(0, 2))); + } + mergedMessages.putAll(locales.get(locale)); } - mergedMessages.putAll(locales.get(locale)); return mergedMessages; } diff --git a/framework/src/play/i18n/MessagesPlugin.java b/framework/src/play/i18n/MessagesPlugin.java index fd0dde8d94..e79ce8f28e 100644 --- a/framework/src/play/i18n/MessagesPlugin.java +++ b/framework/src/play/i18n/MessagesPlugin.java @@ -23,7 +23,7 @@ public class MessagesPlugin extends PlayPlugin { static Long lastLoading = 0L; - private static List includeMessageFilenames = new ArrayList(); + private static List includeMessageFilenames = new ArrayList<>(); @Override public void onApplicationStart() { @@ -33,7 +33,7 @@ public void onApplicationStart() { File message = new File(Play.frameworkPath, "resources/messages"); Messages.defaults.putAll(read(message)); } catch (Exception e) { - Logger.warn("Defaults messsages file missing"); + Logger.warn("Default messages file missing: %s", e); } for (VirtualFile module : Play.modules.values()) { VirtualFile messages = module.child("conf/messages"); @@ -85,7 +85,7 @@ static Properties read(File file) { propsFromFile = IO.readUtf8Properties(inStream); // Include - Map toInclude = new HashMap(16); + Map toInclude = new HashMap<>(16); for (Object key : propsFromFile.keySet()) { if (key.toString().startsWith("@include.")) { try { @@ -101,7 +101,7 @@ static Properties read(File file) { Logger.warn("Missing include: %s from file %s", filenameToInclude, file.getPath()); } } catch (Exception ex) { - Logger.warn("Missing include: %s", key); + Logger.warn("Missing include: %s, caused by: %s", key, ex); } } } diff --git a/framework/src/play/inject/Injector.java b/framework/src/play/inject/Injector.java index 0c07fa6b82..a9e34dfc1a 100644 --- a/framework/src/play/inject/Injector.java +++ b/framework/src/play/inject/Injector.java @@ -2,32 +2,38 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.List; + import javax.inject.Inject; + import play.Play; import play.classloading.enhancers.ControllersEnhancer.ControllerSupport; import play.jobs.Job; import play.mvc.Mailer; public class Injector { - + /** * For now, inject beans in controllers + * + * @param source + * the beanSource to inject */ public static void inject(BeanSource source) { - List classes = Play.classloader.getAssignableClasses(ControllerSupport.class); + List classes = new ArrayList<>(Play.classloader.getAssignableClasses(ControllerSupport.class)); classes.addAll(Play.classloader.getAssignableClasses(Mailer.class)); classes.addAll(Play.classloader.getAssignableClasses(Job.class)); - for(Class clazz : classes) { - for(Field field : clazz.getDeclaredFields()) { - if(Modifier.isStatic(field.getModifiers()) && field.isAnnotationPresent(Inject.class)) { + for (Class clazz : classes) { + for (Field field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) && field.isAnnotationPresent(Inject.class)) { Class type = field.getType(); field.setAccessible(true); try { field.set(null, source.getBeanOfType(type)); - } catch(RuntimeException e) { + } catch (RuntimeException e) { throw e; - } catch(Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/framework/src/play/jobs/Every.java b/framework/src/play/jobs/Every.java index 1ccf8ca24e..ad0b97685b 100644 --- a/framework/src/play/jobs/Every.java +++ b/framework/src/play/jobs/Every.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * Run a job at specified intervale + * Run a job at specified interval * Example, @Every("1h") */ @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/play/jobs/Job.java b/framework/src/play/jobs/Job.java index 478893f468..901ce062b8 100644 --- a/framework/src/play/jobs/Job.java +++ b/framework/src/play/jobs/Job.java @@ -6,24 +6,27 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import com.jamonapi.Monitor; +import com.jamonapi.MonitorFactory; + import play.Invoker; import play.Invoker.InvocationContext; import play.Logger; import play.Play; +import play.PlayPlugin; import play.exceptions.JavaExecutionException; import play.exceptions.PlayException; import play.exceptions.UnexpectedException; +import play.libs.F; import play.libs.F.Promise; import play.libs.Time; import play.mvc.Http; -import play.PlayPlugin; - -import com.jamonapi.Monitor; -import com.jamonapi.MonitorFactory; /** * A job is an asynchronously executed unit of work - * @param The job result type (if any) + * + * @param + * The job result type (if any) */ public class Job extends Invoker.Invocation implements Callable { @@ -43,12 +46,19 @@ public InvocationContext getInvocationContext() { /** * Here you do the job + * + * @throws Exception + * if problems occurred */ public void doJob() throws Exception { } /** * Here you do the job and return a result + * + * @return The job result + * @throws Exception + * if problems occurred */ public V doJobWithResult() throws Exception { doJob(); @@ -57,44 +67,47 @@ public V doJobWithResult() throws Exception { @Override public void execute() throws Exception { - + } /** * Start this job now (well ASAP) + * * @return the job completion */ public Promise now() { - final Promise smartFuture = new Promise(); + Promise smartFuture = new Promise<>(); JobsPlugin.executor.submit(getJobCallingCallable(smartFuture)); return smartFuture; } - /** - * If is called in a 'HttpRequest' invocation context, waits until request - * is served and schedules job then. - * - * Otherwise is the same as now(); - * - * If you want to schedule a job to run after some other job completes, wait till a promise redeems - * of just override first Job's call() to schedule the second one. - * - * @return the job completion - */ - public Promise afterRequest() { - InvocationContext current = Invoker.InvocationContext.current(); - if(current == null || !Http.invocationType.equals(current.getInvocationType())) { - return now(); - } - - final Promise smartFuture = new Promise(); - Callable callable = getJobCallingCallable(smartFuture); - JobsPlugin.addAfterRequestAction(callable); - return smartFuture; - } + /** + * If is called in a 'HttpRequest' invocation context, waits until request is served and schedules job then. + * + * Otherwise is the same as now(); + * + * If you want to schedule a job to run after some other job completes, wait till a promise redeems of just override + * first Job's call() to schedule the second one. + * + * @return the job completion + */ + public Promise afterRequest() { + InvocationContext current = Invoker.InvocationContext.current(); + if (current == null || !Http.invocationType.equals(current.getInvocationType())) { + return now(); + } + + Promise smartFuture = new Promise<>(); + Callable callable = getJobCallingCallable(smartFuture); + JobsPlugin.addAfterRequestAction(callable); + return smartFuture; + } /** * Start this job in several seconds + * + * @param delay + * time in seconds * @return the job completion */ public Promise in(String delay) { @@ -103,36 +116,42 @@ public Promise in(String delay) { /** * Start this job in several seconds + * + * @param seconds + * time in seconds * @return the job completion */ public Promise in(int seconds) { - final Promise smartFuture = new Promise(); + Promise smartFuture = new Promise<>(); JobsPlugin.executor.schedule(getJobCallingCallable(smartFuture), seconds, TimeUnit.SECONDS); return smartFuture; } private Callable getJobCallingCallable(final Promise smartFuture) { - return new Callable() { - public V call() throws Exception { - try { - V result = Job.this.call(); - if (smartFuture != null) { - smartFuture.invoke(result); + return new Callable() { + @Override + public V call() throws Exception { + try { + V result = Job.this.call(); + if (smartFuture != null) { + smartFuture.invoke(result); + } + return result; + } catch (Exception e) { + if (smartFuture != null) { + smartFuture.invokeWithException(e); + } + return null; + } } - return result; - } - catch (Exception e) { - if (smartFuture != null) { - smartFuture.invokeWithException(e); - } - return null; - } - } - }; + }; } /** * Run this job every n seconds + * + * @param delay + * time in seconds */ public void every(String delay) { every(Time.parseDuration(delay)); @@ -140,9 +159,13 @@ public void every(String delay) { /** * Run this job every n seconds + * + * @param seconds + * time in seconds */ public void every(int seconds) { JobsPlugin.executor.scheduleWithFixedDelay(this, seconds, seconds, TimeUnit.SECONDS); + JobsPlugin.scheduledJobs.add(this); } // Customize Invocation @@ -152,17 +175,17 @@ public void onException(Throwable e) { lastException = e; try { super.onException(e); - } catch(Throwable ex) { + } catch (Throwable ex) { Logger.error(ex, "Error during job execution (%s)", this); throw new UnexpectedException(unwrap(e)); } } private Throwable unwrap(Throwable e) { - while((e instanceof UnexpectedException || e instanceof PlayException) && e.getCause() != null) { - e = e.getCause(); - } - return e; + while ((e instanceof UnexpectedException || e instanceof PlayException) && e.getCause() != null) { + e = e.getCause(); + } + return e; } @Override @@ -171,14 +194,15 @@ public void run() { } private V withinFilter(play.libs.F.Function0 fct) throws Throwable { - for (PlayPlugin plugin : Play.pluginCollection.getEnabledPlugins() ){ - if (plugin.getFilter() != null) { - return (V)plugin.getFilter().withinFilter(fct); - } + F.Option> filters = Play.pluginCollection.composeFilters(); + if (!filters.isDefined()) { + return null; + } else { + return filters.get().withinFilter(fct); } - return null; } + @Override public V call() { Monitor monitor = null; try { @@ -189,22 +213,23 @@ public V call() { try { lastException = null; lastRun = System.currentTimeMillis(); - monitor = MonitorFactory.start(getClass().getName()+".doJob()"); - - // If we have a plugin, get him to execute the job within the filter. + monitor = MonitorFactory.start(this + ".doJob()"); + + // If we have a plugin, get him to execute the job within the filter. final AtomicBoolean executed = new AtomicBoolean(false); result = this.withinFilter(new play.libs.F.Function0() { + @Override public V apply() throws Throwable { executed.set(true); return doJobWithResult(); } }); - + // No filter function found => we need to execute anyway( as before the use of withinFilter ) if (!executed.get()) { result = doJobWithResult(); } - + monitor.stop(); monitor = null; wasError = false; @@ -213,7 +238,8 @@ public V apply() throws Throwable { } catch (Exception e) { StackTraceElement element = PlayException.getInterestingStackTraceElement(e); if (element != null) { - throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), e); + throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), + e); } throw e; } @@ -223,7 +249,7 @@ public V apply() throws Throwable { } catch (Throwable e) { onException(e); } finally { - if(monitor != null) { + if (monitor != null) { monitor.stop(); } _finally(); @@ -244,5 +270,4 @@ public String toString() { return this.getClass().getName(); } - } diff --git a/framework/src/play/jobs/JobsPlugin.java b/framework/src/play/jobs/JobsPlugin.java index 6b26d67fc7..12d07c4ca1 100644 --- a/framework/src/play/jobs/JobsPlugin.java +++ b/framework/src/play/jobs/JobsPlugin.java @@ -28,9 +28,9 @@ public class JobsPlugin extends PlayPlugin { - public static ScheduledThreadPoolExecutor executor = null; - public static List scheduledJobs = null; - private static ThreadLocal>> afterInvocationActions = new ThreadLocal>>(); + public static ScheduledThreadPoolExecutor executor; + public static List scheduledJobs = new ArrayList<>(); + private static final ThreadLocal>> afterInvocationActions = new ThreadLocal<>(); @Override public String getStatus() { @@ -54,7 +54,7 @@ public String getStatus() { out.println("Scheduled jobs (" + scheduledJobs.size() + "):"); out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~"); for (Job job : scheduledJobs) { - out.print(job.getClass().getName()); + out.print(job); if (job.getClass().isAnnotationPresent(OnApplicationStart.class) && !(job.getClass().isAnnotationPresent(On.class) || job.getClass().isAnnotationPresent(Every.class))) { OnApplicationStart appStartAnnotation = job.getClass().getAnnotation(OnApplicationStart.class); @@ -89,10 +89,9 @@ public String getStatus() { out.println(); out.println("Waiting jobs:"); out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - ScheduledFuture[] q = executor.getQueue().toArray(new ScheduledFuture[0]); + ScheduledFuture[] q = executor.getQueue().toArray(new ScheduledFuture[executor.getQueue().size()]); - for (int i = 0; i < q.length; i++) { - ScheduledFuture task = q[i]; + for (ScheduledFuture task : q) { out.println(Java.extractUnderlyingCallable((FutureTask) task) + " will run in " + task.getDelay(TimeUnit.SECONDS) + " seconds"); } @@ -102,14 +101,13 @@ public String getStatus() { @Override public void afterApplicationStart() { - List> jobs = new ArrayList>(); + List> jobs = new ArrayList<>(); for (Class clazz : Play.classloader.getAllClasses()) { if (Job.class.isAssignableFrom(clazz)) { jobs.add(clazz); } } - scheduledJobs = new ArrayList(); - for (final Class clazz : jobs) { + for (Class clazz : jobs) { // @OnApplicationStart if (clazz.isAnnotationPresent(OnApplicationStart.class)) { // check if we're going to run the job sync or async @@ -117,8 +115,7 @@ public void afterApplicationStart() { if (!appStartAnnotation.async()) { // run job sync try { - Job job = ((Job) clazz.newInstance()); - scheduledJobs.add(job); + Job job = createJob(clazz); job.run(); if (job.wasError) { if (job.lastException != null) { @@ -126,9 +123,7 @@ public void afterApplicationStart() { } throw new RuntimeException("@OnApplicationStart Job has failed"); } - } catch (InstantiationException e) { - throw new UnexpectedException("Job could not be instantiated", e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new UnexpectedException("Job could not be instantiated", e); } catch (Throwable ex) { if (ex instanceof PlayException) { @@ -139,16 +134,13 @@ public void afterApplicationStart() { } else { // run job async try { - Job job = ((Job) clazz.newInstance()); - scheduledJobs.add(job); + Job job = createJob(clazz); // start running job now in the background @SuppressWarnings("unchecked") Callable callable = (Callable) job; executor.submit(callable); - } catch (InstantiationException ex) { - throw new UnexpectedException("Cannot instanciate Job " + clazz.getName()); - } catch (IllegalAccessException ex) { - throw new UnexpectedException("Cannot instanciate Job " + clazz.getName()); + } catch (InstantiationException | IllegalAccessException ex) { + throw new UnexpectedException("Cannot instantiate Job " + clazz.getName(), ex); } } } @@ -156,20 +148,16 @@ public void afterApplicationStart() { // @On if (clazz.isAnnotationPresent(On.class)) { try { - Job job = ((Job) clazz.newInstance()); - scheduledJobs.add(job); + Job job = createJob(clazz); scheduleForCRON(job); - } catch (InstantiationException ex) { - throw new UnexpectedException("Cannot instanciate Job " + clazz.getName()); - } catch (IllegalAccessException ex) { - throw new UnexpectedException("Cannot instanciate Job " + clazz.getName()); + } catch (InstantiationException | IllegalAccessException ex) { + throw new UnexpectedException("Cannot instantiate Job " + clazz.getName(), ex); } } // @Every if (clazz.isAnnotationPresent(Every.class)) { try { - Job job = (Job) clazz.newInstance(); - scheduledJobs.add(job); + Job job = createJob(clazz); String value = job.getClass().getAnnotation(Every.class).value(); if (value.startsWith("cron.")) { value = Play.configuration.getProperty(value); @@ -178,19 +166,24 @@ public void afterApplicationStart() { if (!"never".equalsIgnoreCase(value)) { executor.scheduleWithFixedDelay(job, Time.parseDuration(value), Time.parseDuration(value), TimeUnit.SECONDS); } - } catch (InstantiationException ex) { - throw new UnexpectedException("Cannot instanciate Job " + clazz.getName()); - } catch (IllegalAccessException ex) { - throw new UnexpectedException("Cannot instanciate Job " + clazz.getName()); + } catch (InstantiationException | IllegalAccessException ex) { + throw new UnexpectedException("Cannot instantiate Job " + clazz.getName(), ex); } } } } + private Job createJob(Class clazz) throws InstantiationException, IllegalAccessException { + Job job = (Job) clazz.newInstance(); + scheduledJobs.add(job); + return job; + } + @Override public void onApplicationStart() { int core = Integer.parseInt(Play.configuration.getProperty("play.jobs.pool", "10")); executor = new ScheduledThreadPoolExecutor(core, new PThreadFactory("jobs"), new ThreadPoolExecutor.AbortPolicy()); + scheduledJobs.clear(); } public static void scheduleForCRON(Job job) { @@ -202,7 +195,7 @@ public static void scheduleForCRON(Job job) { cron = Play.configuration.getProperty(cron); } cron = Expression.evaluate(cron, cron).toString(); - if (cron == null || "".equals(cron) || "never".equalsIgnoreCase(cron)) { + if (cron == null || cron.isEmpty() || "never".equalsIgnoreCase(cron)) { Logger.info("Skipping job %s, cron expression is not defined", job.getClass().getName()); return; } @@ -212,8 +205,8 @@ public static void scheduleForCRON(Job job) { CronExpression cronExp = new CronExpression(cron); Date nextDate = cronExp.getNextValidTimeAfter(now); if (nextDate == null) { - Logger.warn("The cron expression for job %s doesn't have any match in the future, will never be executed", job.getClass() - .getName()); + Logger.warn("The cron expression for job %s doesn't have any match in the future, will never be executed", + job.getClass().getName()); return; } if (nextDate.equals(job.nextPlannedExecution)) { @@ -236,12 +229,11 @@ public void onApplicationStop() { List jobs = Play.classloader.getAssignableClasses(Job.class); - for (final Class clazz : jobs) { + for (Class clazz : jobs) { // @OnApplicationStop if (clazz.isAnnotationPresent(OnApplicationStop.class)) { try { - Job job = ((Job) clazz.newInstance()); - scheduledJobs.add(job); + Job job = createJob(clazz); job.run(); if (job.wasError) { if (job.lastException != null) { @@ -249,9 +241,7 @@ public void onApplicationStop() { } throw new RuntimeException("@OnApplicationStop Job has failed"); } - } catch (InstantiationException e) { - throw new UnexpectedException("Job could not be instantiated", e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new UnexpectedException("Job could not be instantiated", e); } catch (Throwable ex) { if (ex instanceof PlayException) { @@ -268,20 +258,20 @@ public void onApplicationStop() { @Override public void beforeInvocation() { - afterInvocationActions.set(new LinkedList>()); + afterInvocationActions.set(new LinkedList>()); } @Override public void afterInvocation() { - List> currentActions = afterInvocationActions.get(); + List> currentActions = afterInvocationActions.get(); afterInvocationActions.set(null); - for (Callable callable : currentActions) { - JobsPlugin.executor.submit(callable); + for (Callable callable : currentActions) { + executor.submit(callable); } } // default visibility, because we want to use this only from Job.java - static void addAfterRequestAction(Callable c) { + static void addAfterRequestAction(Callable c) { if (Request.current() == null) { throw new IllegalStateException("After request actions can be added only from threads that serve requests!"); } diff --git a/framework/src/play/libs/Codec.java b/framework/src/play/libs/Codec.java index 3ac425599c..8ee82e3e95 100644 --- a/framework/src/play/libs/Codec.java +++ b/framework/src/play/libs/Codec.java @@ -7,6 +7,7 @@ import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; + import play.exceptions.UnexpectedException; /** @@ -15,6 +16,8 @@ public class Codec { /** + * Generate an UUID String + * * @return an UUID String */ public static String UUID() { @@ -23,7 +26,9 @@ public static String UUID() { /** * Encode a String to base64 - * @param value The plain String + * + * @param value + * The plain String * @return The base64 encoded String */ public static String encodeBASE64(String value) { @@ -35,8 +40,10 @@ public static String encodeBASE64(String value) { } /** - * Encode binary data to base64 - * @param value The binary data + * Encode binary data to base64 + * + * @param value + * The binary data * @return The base64 encoded String */ public static String encodeBASE64(byte[] value) { @@ -45,7 +52,9 @@ public static String encodeBASE64(byte[] value) { /** * Decode a base64 value - * @param value The base64 encoded String + * + * @param value + * The base64 encoded String * @return decoded binary data */ public static byte[] decodeBASE64(String value) { @@ -58,7 +67,9 @@ public static byte[] decodeBASE64(String value) { /** * Build an hexadecimal MD5 hash for a String - * @param value The String to hash + * + * @param value + * The String to hash * @return An hexadecimal Hash */ public static String hexMD5(String value) { @@ -75,9 +86,11 @@ public static String hexMD5(String value) { /** * Build an hexadecimal SHA1 hash for a String - * @param value The String to hash + * + * @param value + * The String to hash * @return An hexadecimal Hash - */ + */ public static String hexSHA1(String value) { try { MessageDigest md; @@ -92,6 +105,10 @@ public static String hexSHA1(String value) { /** * Write a byte array as hexadecimal String. + * + * @param bytes + * byte array + * @return The hexadecimal String */ public static String byteToHexString(byte[] bytes) { return String.valueOf(Hex.encodeHex(bytes)); @@ -99,6 +116,10 @@ public static String byteToHexString(byte[] bytes) { /** * Transform an hexadecimal String to a byte array. + * + * @param hexString + * Hexadecimal string to transform + * @return The byte array */ public static byte[] hexStringToByte(String hexString) { try { diff --git a/framework/src/play/libs/CronExpression.java b/framework/src/play/libs/CronExpression.java index 55b3cbff5c..a31de92202 100644 --- a/framework/src/play/libs/CronExpression.java +++ b/framework/src/play/libs/CronExpression.java @@ -17,16 +17,16 @@ /** * Thanks to Quartz project, https://quartz.dev.java.net - * - * Provides a parser and evaluator for unix-like cron expressions. Cron - * expressions provide the ability to specify complex time combinations such as - * "At 8:00am every Monday through Friday" or "At 1:30am every - * last Friday of the month". - *

    - * Cron expressions are comprised of 6 required fields and one optional field - * separated by white space. The fields respectively are described as follows: - * - * + *

    + * Provides a parser and evaluator for unix-like cron expressions. Cron expressions provide the ability to specify + * complex time combinations such as "At 8:00am every Monday through Friday" or "At 1:30am every last + * Friday of the month". + *

    + *

    + * Cron expressions are comprised of 6 required fields and one optional field separated by white space. The fields + * respectively are described as follows: + *

    + *
    * * * @@ -36,137 +36,127 @@ * * * - * * - * * * * * - * * - * * * * * - * * - * * * * * - * * - * * * * * - * * - * * * * * - * * - * * * * * - * * - * * * *
    Field Name 
    Seconds  + *  0-59  + *  , - * /
    Minutes  + *  0-59  + *  , - * /
    Hours  + *  0-23  + *  , - * /
    Day-of-month  + *  1-31  + *  , - * ? / L W
    Month  + *  1-12 or JAN-DEC  + *  , - * /
    Day-of-Week  + *  1-7 or SUN-SAT  + *  , - * ? / L #
    Year (Optional)  + *  empty, 1970-2099  + *  , - * /
    - *

    - * The '*' character is used to specify all values. For example, "*" - * in the minute field means "every minute". - *

    - * The '?' character is allowed for the day-of-month and day-of-week fields. It - * is used to specify 'no specific value'. This is useful when you need to - * specify something in one of the two fileds, but not the other. - *

    - * The '-' character is used to specify ranges For example "10-12" in - * the hour field means "the hours 10, 11 and 12". - *

    - * The ',' character is used to specify additional values. For example - * "MON,WED,FRI" in the day-of-week field means "the days Monday, - * Wednesday, and Friday". - *

    - * The '/' character is used to specify increments. For example "0/15" - * in the seconds field means "the seconds 0, 15, 30, and 45". And - * "5/15" in the seconds field means "the seconds 5, 20, 35, and - * 50". Specifying '*' before the '/' is equivalent to specifying 0 is the - * value to start with. Essentially, for each field in the expression, there is - * a set of numbers that can be turned on or off. For seconds and minutes, the - * numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to 31, - * and for months 1 to 12. The "/" character simply helps you turn on - * every "nth" value in the given set. Thus "7/6" in the - * month field only turns on month "7", it does NOT mean every 6th - * month, please note that subtlety. - *

    - * The 'L' character is allowed for the day-of-month and day-of-week fields. - * This character is short-hand for "last", but it has different - * meaning in each of the two fields. For example, the value "L" in - * the day-of-month field means "the last day of the month" - day 31 - * for January, day 28 for February on non-leap years. If used in the - * day-of-week field by itself, it simply means "7" or - * "SAT". But if used in the day-of-week field after another value, it - * means "the last xxx day of the month" - for example "6L" - * means "the last friday of the month". When using the 'L' option, it - * is important not to specify lists, or ranges of values, as you'll get - * confusing results. - *

    - * The 'W' character is allowed for the day-of-month field. This character is - * used to specify the weekday (Monday-Friday) nearest the given day. As an - * example, if you were to specify "15W" as the value for the - * day-of-month field, the meaning is: "the nearest weekday to the 15th of - * the month". So if the 15th is a Saturday, the trigger will fire on - * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the - * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. - * However if you specify "1W" as the value for day-of-month, and the - * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not - * 'jump' over the boundary of a month's days. The 'W' character can only be - * specified when the day-of-month is a single day, not a range or list of days. - *

    - * The 'L' and 'W' characters can also be combined for the day-of-month - * expression to yield 'LW', which translates to "last weekday of the - * month". - *

    - * The '#' character is allowed for the day-of-week field. This character is - * used to specify "the nth" xxx day of the month. For example, the - * value of "6#3" in the day-of-week field means the third Friday of - * the month (day 6 = Friday and "#3" = the 3rd one in the month). - * Other examples: "2#1" = the first Monday of the month and - * "4#5" = the fifth Wednesday of the month. Note that if you specify - * "#5" and there is not 5 of the given day-of-week in the month, then - * no firing will occur that month. - *

    - * - *

    - * The legal characters and the names of months and days of the week are not - * case sensitive. - * *

    + * The '*' character is used to specify all values. For example, "*" in the minute field means "every + * minute". + *

    + *

    + * The '?' character is allowed for the day-of-month and day-of-week fields. It is used to specify 'no specific value'. + * This is useful when you need to specify something in one of the two fileds, but not the other. + *

    + *

    + * The '-' character is used to specify ranges For example "10-12" in the hour field means "the hours 10, + * 11 and 12". + *

    + *

    + * The ',' character is used to specify additional values. For example "MON,WED,FRI" in the day-of-week field + * means "the days Monday, Wednesday, and Friday". + *

    + *

    + * The '/' character is used to specify increments. For example "0/15" in the seconds field means "the + * seconds 0, 15, 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and + * 50". Specifying '*' before the '/' is equivalent to specifying 0 is the value to start with. Essentially, for + * each field in the expression, there is a set of numbers that can be turned on or off. For seconds and minutes, the + * numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to 31, and for months 1 to 12. The + * "/" character simply helps you turn on every "nth" value in the given set. Thus "7/6" + * in the month field only turns on month "7", it does NOT mean every 6th month, please note that subtlety. + *

    + *

    + * The 'L' character is allowed for the day-of-month and day-of-week fields. This character is short-hand for + * "last", but it has different meaning in each of the two fields. For example, the value "L" in the + * day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap + * years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in + * the day-of-week field after another value, it means "the last xxx day of the month" - for example + * "6L" means "the last friday of the month". When using the 'L' option, it is important not to + * specify lists, or ranges of values, as you'll get confusing results. + *

    + *

    + * The 'W' character is allowed for the day-of-month field. This character is used to specify the weekday + * (Monday-Friday) nearest the given day. As an example, if you were to specify "15W" as the value for the + * day-of-month field, the meaning is: "the nearest weekday to the 15th of the month". So if the 15th is a + * Saturday, the trigger will fire on Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the + * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the + * value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 'jump' + * over the boundary of a month's days. The 'W' character can only be specified when the day-of-month is a single day, + * not a range or list of days. + *

    + *

    + * The 'L' and 'W' characters can also be combined for the day-of-month expression to yield 'LW', which translates to + * "last weekday of the month". + *

    + *

    + * The '#' character is allowed for the day-of-week field. This character is used to specify "the nth" xxx day + * of the month. For example, the value of "6#3" in the day-of-week field means the third Friday of the month + * (day 6 = Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of + * the month and "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and there + * is not 5 of the given day-of-week in the month, then no firing will occur that month. + *

    + * + *

    + * The legal characters and the names of months and days of the week are not case sensitive. + *

    + * * NOTES: *
      - *
    • Support for specifying both a day-of-week and a day-of-month value is not - * complete (you'll need to use the '?' character in on of these fields).
    • + *
    • Support for specifying both a day-of-week and a day-of-month value is not complete (you'll need to use the '?' + * character in on of these fields).
    • *
    - *

    - * * * @author Sharada Jambula, James House * @author Contributions from Mads Henderson @@ -186,8 +176,8 @@ public class CronExpression implements Serializable, Cloneable { protected static final int NO_SPEC_INT = 98; // '?' protected static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT); protected static final Integer NO_SPEC = new Integer(NO_SPEC_INT); - protected static Map monthMap = new HashMap(20); - protected static Map dayMap = new HashMap(60); + protected static Map monthMap = new HashMap<>(20); + protected static Map dayMap = new HashMap<>(60); static { monthMap.put("JAN", new Integer(0)); @@ -227,15 +217,12 @@ public class CronExpression implements Serializable, Cloneable { protected transient boolean expressionParsed = false; /** - * Constructs a new CronExpression based on the specified - * parameter. + * Constructs a new CronExpression based on the specified parameter. * * @param cronExpression - * String representation of the cron expression the new object - * should represent + * String representation of the cron expression the new object should represent * @throws java.text.ParseException - * if the string expression cannot be parsed into a valid - * CronExpression + * if the string expression cannot be parsed into a valid CronExpression */ public CronExpression(String cronExpression) throws ParseException { if (cronExpression == null) { @@ -248,14 +235,12 @@ public CronExpression(String cronExpression) throws ParseException { } /** - * Indicates whether the given date satisfies the cron expression. Note that - * milliseconds are ignored, so two Dates falling on different milliseconds - * of the same second will always have the same result here. + * Indicates whether the given date satisfies the cron expression. Note that milliseconds are ignored, so two Dates + * falling on different milliseconds of the same second will always have the same result here. * * @param date * the date to evaluate - * @return a boolean indicating whether the given date satisfies the cron - * expression + * @return a boolean indicating whether the given date satisfies the cron expression */ public boolean isSatisfiedBy(Date date) { Calendar testDateCal = Calendar.getInstance(); @@ -271,12 +256,10 @@ public boolean isSatisfiedBy(Date date) { } /** - * Returns the next date/time after the given date/time which - * satisfies the cron expression. + * Returns the next date/time after the given date/time which satisfies the cron expression. * * @param date - * the date/time at which to begin the search for the next valid - * date/time + * the date/time at which to begin the search for the next valid date/time * @return the next valid date/time */ public Date getNextValidTimeAfter(Date date) { @@ -284,12 +267,10 @@ public Date getNextValidTimeAfter(Date date) { } /** - * Returns the next date/time after the given date/time which does - * not satisfy the expression + * Returns the next date/time after the given date/time which does not satisfy the expression * * @param date - * the date/time at which to begin the search for the next - * invalid date/time + * the date/time at which to begin the search for the next invalid date/time * @return the next valid date/time */ public Date getNextInvalidTimeAfter(Date date) { @@ -326,8 +307,7 @@ public Date getNextInvalidTimeAfter(Date date) { * * @param date * the date/time at which to begin the search - * @return the number of milliseconds between the next valid and the one - * after + * @return the number of milliseconds between the next valid and the one after */ public long getNextInterval(Date date) { Date nextValid = getNextValidTimeAfter(date); @@ -337,8 +317,9 @@ public long getNextInterval(Date date) { } /** - * Returns the time zone for which this CronExpression will be - * resolved. + * Returns the time zone for which this CronExpression will be resolved. + * + * @return Returns the time zone */ public TimeZone getTimeZone() { if (timeZone == null) { @@ -349,8 +330,10 @@ public TimeZone getTimeZone() { } /** - * Sets the time zone for which this CronExpression will be - * resolved. + * Sets the time zone for which this CronExpression will be resolved. + * + * @param timeZone + * The given time zone */ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; @@ -367,13 +350,11 @@ public String toString() { } /** - * Indicates whether the specified cron expression can be parsed into a - * valid cron expression + * Indicates whether the specified cron expression can be parsed into a valid cron expression * * @param cronExpression * the expression to evaluate - * @return a boolean indicating whether the given expression is a valid cron - * expression + * @return a boolean indicating whether the given expression is a valid cron expression */ public static boolean isValidExpression(String cronExpression) { @@ -397,25 +378,25 @@ protected void buildExpression(String expression) throws ParseException { try { if (seconds == null) { - seconds = new TreeSet(); + seconds = new TreeSet<>(); } if (minutes == null) { - minutes = new TreeSet(); + minutes = new TreeSet<>(); } if (hours == null) { - hours = new TreeSet(); + hours = new TreeSet<>(); } if (daysOfMonth == null) { - daysOfMonth = new TreeSet(); + daysOfMonth = new TreeSet<>(); } if (months == null) { - months = new TreeSet(); + months = new TreeSet<>(); } if (daysOfWeek == null) { - daysOfWeek = new TreeSet(); + daysOfWeek = new TreeSet<>(); } if (years == null) { - years = new TreeSet(); + years = new TreeSet<>(); } int exprOn = SECOND; @@ -745,7 +726,7 @@ public String getCronExpression() { } public String getExpressionSummary() { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); buf.append("seconds: "); buf.append(getExpressionSetSummary(seconds)); @@ -793,7 +774,7 @@ protected String getExpressionSetSummary(java.util.Set set) { return "*"; } - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); Iterator itr = set.iterator(); boolean first = true; @@ -819,7 +800,7 @@ protected String getExpressionSetSummary(java.util.ArrayList list) { return "*"; } - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); Iterator itr = list.iterator(); boolean first = true; @@ -1404,11 +1385,12 @@ protected Date getTimeAfter(Date afterTime) { } /** - * Advance the calendar to the particular hour paying particular attention - * to daylight saving problems. + * Advance the calendar to the particular hour paying particular attention to daylight saving problems. * * @param cal + * The given calendar * @param hour + * The hour to set */ protected void setCalendarHour(Calendar cal, int hour) { cal.set(java.util.Calendar.HOUR_OF_DAY, hour); @@ -1418,16 +1400,20 @@ protected void setCalendarHour(Calendar cal, int hour) { } /** - * NOT YET IMPLEMENTED: Returns the time before the given time that the - * CronExpression matches. + * NOT YET IMPLEMENTED: Returns the time before the given time that the CronExpression matches. + * + * @param endTime + * The given time + * @return Returns the time before the given time */ protected Date getTimeBefore(Date endTime) { throw new NotImplementedException(); } /** - * NOT YET IMPLEMENTED: Returns the final time that the - * CronExpression will match. + * NOT YET IMPLEMENTED: Returns the final time that the CronExpression will match. + * + * @return Returns the final time */ public Date getFinalFireTime() { throw new NotImplementedException(); @@ -1480,15 +1466,13 @@ private void readObject(java.io.ObjectInputStream stream) throws java.io.IOExcep @Override public Object clone() { - CronExpression copy = null; try { - copy = new CronExpression(getCronExpression()); + CronExpression copy = new CronExpression(getCronExpression()); copy.setTimeZone(getTimeZone()); - } catch (ParseException ex) { // never happens since the source is - // valid... - throw new IncompatibleClassChangeError("Not Cloneable."); + return copy; + } catch (ParseException ex) { // never happens since the source is valid + throw new RuntimeException("Failed to clone " + this, ex); } - return copy; } private static class ValueSet { diff --git a/framework/src/play/libs/Crypto.java b/framework/src/play/libs/Crypto.java index 940d131e09..f12a8cc482 100644 --- a/framework/src/play/libs/Crypto.java +++ b/framework/src/play/libs/Crypto.java @@ -3,12 +3,12 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.apache.commons.codec.binary.Base64; - import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.Base64; + import play.Play; import play.exceptions.UnexpectedException; @@ -21,13 +21,17 @@ public class Crypto { * Define a hash type enumeration for strong-typing */ public enum HashType { - MD5("MD5"), - SHA1("SHA-1"), - SHA256("SHA-256"), - SHA512("SHA-512"); + MD5("MD5"), SHA1("SHA-1"), SHA256("SHA-256"), SHA512("SHA-512"); private String algorithm; - HashType(String algorithm) { this.algorithm = algorithm; } - @Override public String toString() { return this.algorithm; } + + HashType(String algorithm) { + this.algorithm = algorithm; + } + + @Override + public String toString() { + return this.algorithm; + } } /** @@ -35,10 +39,14 @@ public enum HashType { */ private static final HashType DEFAULT_HASH_TYPE = HashType.MD5; - static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * Sign a message using the application secret key (HMAC-SHA1) + * + * @param message + * the message to sign + * @return The signed message */ public static String sign(String message) { return sign(message, Play.secretKey.getBytes()); @@ -46,10 +54,12 @@ public static String sign(String message) { /** * Sign a message with a key - * @param message The message to sign - * @param key The key to use + * + * @param message + * The message to sign + * @param key + * The key to use * @return The signed message (in hexadecimal) - * @throws java.lang.Exception */ public static String sign(String message, byte[] key) { @@ -66,7 +76,6 @@ public static String sign(String message, byte[] key) { int len = result.length; char[] hexChars = new char[len * 2]; - for (int charIndex = 0, startIndex = 0; charIndex < hexChars.length;) { int bite = result[startIndex++] & 0xff; hexChars[charIndex++] = HEX_CHARS[bite >> 4]; @@ -80,23 +89,26 @@ public static String sign(String message, byte[] key) { } /** - * Create a password hash using the default hashing algorithm - * @param input The password - * @return The password hash - */ - public static String passwordHash(String input) - { + * Create a password hash using the default hashing algorithm + * + * @param input + * The password + * @return The password hash + */ + public static String passwordHash(String input) { return passwordHash(input, DEFAULT_HASH_TYPE); } /** - * Create a password hash using specific hashing algorithm - * @param input The password - * @param hashType The hashing algorithm - * @return The password hash - */ - public static String passwordHash(String input, HashType hashType) - { + * Create a password hash using specific hashing algorithm + * + * @param input + * The password + * @param hashType + * The hashing algorithm + * @return The password hash + */ + public static String passwordHash(String input, HashType hashType) { try { MessageDigest m = MessageDigest.getInstance(hashType.toString()); byte[] out = m.digest(input.getBytes()); @@ -108,7 +120,9 @@ public static String passwordHash(String input, HashType hashType) /** * Encrypt a String with the AES encryption standard using the application secret - * @param value The String to encrypt + * + * @param value + * The String to encrypt * @return An hexadecimal encrypted string */ public static String encryptAES(String value) { @@ -117,8 +131,11 @@ public static String encryptAES(String value) { /** * Encrypt a String with the AES encryption standard. Private key must have a length of 16 bytes - * @param value The String to encrypt - * @param privateKey The key used to encrypt + * + * @param value + * The String to encrypt + * @param privateKey + * The key used to encrypt * @return An hexadecimal encrypted string */ public static String encryptAES(String value, String privateKey) { @@ -135,7 +152,9 @@ public static String encryptAES(String value, String privateKey) { /** * Decrypt a String with the AES encryption standard using the application secret - * @param value An hexadecimal encrypted string + * + * @param value + * An hexadecimal encrypted string * @return The decrypted String */ public static String decryptAES(String value) { @@ -144,8 +163,11 @@ public static String decryptAES(String value) { /** * Decrypt a String with the AES encryption standard. Private key must have a length of 16 bytes - * @param value An hexadecimal encrypted string - * @param privateKey The key used to encrypt + * + * @param value + * An hexadecimal encrypted string + * @param privateKey + * The key used to encrypt * @return The decrypted String */ public static String decryptAES(String value, String privateKey) { diff --git a/framework/src/play/libs/F.java b/framework/src/play/libs/F.java index 2ab98b4261..df4f18765a 100644 --- a/framework/src/play/libs/F.java +++ b/framework/src/play/libs/F.java @@ -36,14 +36,17 @@ public static class Promise implements Future, F.Action { protected final CountDownLatch taskLock = new CountDownLatch(1); protected boolean cancelled = false; + @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } + @Override public boolean isCancelled() { return false; } + @Override public boolean isDone() { return invoked; } @@ -52,6 +55,7 @@ public V getOrNull() { return result; } + @Override public V get() throws InterruptedException, ExecutionException { taskLock.await(); if (exception != null) { @@ -61,6 +65,7 @@ public V get() throws InterruptedException, ExecutionException { return result; } + @Override public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if(!taskLock.await(timeout, unit)) { throw new TimeoutException(String.format("Promise didn't redeem in %s %s", timeout, unit)); @@ -72,11 +77,12 @@ public V get(long timeout, TimeUnit unit) throws InterruptedException, Execution } return result; } - protected List>> callbacks = new ArrayList>>(); + protected List>> callbacks = new ArrayList<>(); protected boolean invoked = false; protected V result = null; protected Throwable exception = null; + @Override public void invoke(V result) { invokeWithResultOrException(result, null); } @@ -112,7 +118,7 @@ public void onRedeem(F.Action> callback) { } } - public static Promise> waitAll(final Promise... promises) { + public static Promise> waitAll(Promise... promises) { return waitAll(Arrays.asList(promises)); } @@ -150,7 +156,7 @@ public boolean isDone() { @Override public List get() throws InterruptedException, ExecutionException { waitAllLock.await(); - List r = new ArrayList(); + List r = new ArrayList<>(); for (Promise f : promises) { r.add(f.get()); } @@ -166,8 +172,9 @@ public List get(long timeout, TimeUnit unit) throws InterruptedException, Exe return get(); } }; - final F.Action> action = new F.Action>() { + F.Action> action = new F.Action>() { + @Override public void invoke(Promise completed) { waitAllLock.countDown(); if (waitAllLock.getCount() == 0) { @@ -189,10 +196,11 @@ public void invoke(Promise completed) { } public static Promise> wait2(Promise tA, Promise tB) { - final Promise> result = new Promise>(); - final Promise> t = waitAll(new Promise[]{tA, tB}); + final Promise> result = new Promise<>(); + Promise> t = waitAll(new Promise[]{tA, tB}); t.onRedeem(new F.Action>>() { + @Override public void invoke(Promise> completed) { List values = completed.getOrNull(); if(values != null) { @@ -207,10 +215,11 @@ public void invoke(Promise> completed) { } public static Promise> wait3(Promise tA, Promise tB, Promise tC) { - final Promise> result = new Promise>(); - final Promise> t = waitAll(new Promise[]{tA, tB, tC}); + final Promise> result = new Promise<>(); + Promise> t = waitAll(new Promise[]{tA, tB, tC}); t.onRedeem(new F.Action>>() { + @Override public void invoke(Promise> completed) { List values = completed.getOrNull(); if(values != null) { @@ -225,10 +234,11 @@ public void invoke(Promise> completed) { } public static Promise> wait4(Promise tA, Promise tB, Promise tC, Promise tD) { - final Promise> result = new Promise>(); - final Promise> t = waitAll(new Promise[]{tA, tB, tC, tD}); + final Promise> result = new Promise<>(); + Promise> t = waitAll(new Promise[]{tA, tB, tC, tD}); t.onRedeem(new F.Action>>() { + @Override public void invoke(Promise> completed) { List values = completed.getOrNull(); if(values != null) { @@ -243,10 +253,11 @@ public void invoke(Promise> completed) { } public static Promise> wait5(Promise tA, Promise tB, Promise tC, Promise tD, Promise tE) { - final Promise> result = new Promise>(); - final Promise> t = waitAll(new Promise[]{tA, tB, tC, tD, tE}); + final Promise> result = new Promise<>(); + Promise> t = waitAll(new Promise[]{tA, tB, tC, tD, tE}); t.onRedeem(new F.Action>>() { + @Override public void invoke(Promise> completed) { List values = completed.getOrNull(); if(values != null) { @@ -260,12 +271,13 @@ public void invoke(Promise> completed) { return result; } - private static Promise>> waitEitherInternal(final Promise... futures) { - final Promise>> result = new Promise>>(); + private static Promise>> waitEitherInternal(Promise... futures) { + final Promise>> result = new Promise<>(); for (int i = 0; i < futures.length; i++) { final int index = i + 1; ((Promise) futures[i]).onRedeem(new F.Action>() { + @Override public void invoke(Promise completed) { result.invoke(new F.Tuple(index, completed)); } @@ -274,12 +286,13 @@ public void invoke(Promise completed) { return result; } - public static Promise> waitEither(final Promise tA, final Promise tB) { - final Promise> result = new Promise>(); - final Promise>> t = waitEitherInternal(tA, tB); + public static Promise> waitEither(Promise tA, Promise tB) { + final Promise> result = new Promise<>(); + Promise>> t = waitEitherInternal(tA, tB); t.onRedeem(new F.Action>>>() { + @Override public void invoke(Promise>> completed) { F.Tuple> value = completed.getOrNull(); switch (value._1) { @@ -297,12 +310,13 @@ public void invoke(Promise>> completed) { return result; } - public static Promise> waitEither(final Promise tA, final Promise tB, final Promise tC) { - final Promise> result = new Promise>(); - final Promise>> t = waitEitherInternal(tA, tB, tC); + public static Promise> waitEither(Promise tA, Promise tB, Promise tC) { + final Promise> result = new Promise<>(); + Promise>> t = waitEitherInternal(tA, tB, tC); t.onRedeem(new F.Action>>>() { + @Override public void invoke(Promise>> completed) { F.Tuple> value = completed.getOrNull(); switch (value._1) { @@ -323,12 +337,13 @@ public void invoke(Promise>> completed) { return result; } - public static Promise> waitEither(final Promise tA, final Promise tB, final Promise tC, final Promise tD) { - final Promise> result = new Promise>(); - final Promise>> t = waitEitherInternal(tA, tB, tC, tD); + public static Promise> waitEither(Promise tA, Promise tB, Promise tC, Promise tD) { + final Promise> result = new Promise<>(); + Promise>> t = waitEitherInternal(tA, tB, tC, tD); t.onRedeem(new F.Action>>>() { + @Override public void invoke(Promise>> completed) { F.Tuple> value = completed.getOrNull(); switch (value._1) { @@ -352,12 +367,13 @@ public void invoke(Promise>> completed) { return result; } - public static Promise> waitEither(final Promise tA, final Promise tB, final Promise tC, final Promise tD, final Promise tE) { - final Promise> result = new Promise>(); - final Promise>> t = waitEitherInternal(tA, tB, tC, tD, tE); + public static Promise> waitEither(Promise tA, Promise tB, Promise tC, Promise tD, Promise tE) { + final Promise> result = new Promise<>(); + Promise>> t = waitEitherInternal(tA, tB, tC, tD, tE); t.onRedeem(new F.Action>>>() { + @Override public void invoke(Promise>> completed) { F.Tuple> value = completed.getOrNull(); switch (value._1) { @@ -385,11 +401,12 @@ public void invoke(Promise>> completed) { return result; } - public static Promise waitAny(final Promise... futures) { - final Promise result = new Promise(); + public static Promise waitAny(Promise... futures) { + final Promise result = new Promise<>(); - final F.Action> action = new F.Action>() { + F.Action> action = new F.Action>() { + @Override public void invoke(Promise completed) { synchronized (this) { if (result.isDone()) { @@ -417,8 +434,8 @@ public void invoke(Promise completed) { public static class Timeout extends Promise { static Timer timer = new Timer("F.Timeout", true); - final public String token; - final public long delay; + public final String token; + public final long delay; public Timeout(String delay) { this(Time.parseDuration(delay) * 1000); @@ -471,7 +488,7 @@ public static Timeout Timeout(String token, long delay) { public static class EventStream { final int bufferSize; - final ConcurrentLinkedQueue events = new ConcurrentLinkedQueue(); + final ConcurrentLinkedQueue events = new ConcurrentLinkedQueue<>(); final List> waiting = Collections.synchronizedList(new ArrayList>()); public EventStream() { @@ -493,7 +510,7 @@ public synchronized Promise nextEvent() { public synchronized void publish(T event) { if (events.size() > bufferSize) { - Logger.warn("Dropping message. If this is catastrophic to your app, use a BlockingEvenStream instead"); + Logger.warn("Dropping message. If this is catastrophic to your app, use a BlockingEvenStream instead"); events.poll(); } events.offer(event); @@ -547,12 +564,12 @@ public static class BlockingEventStream { public BlockingEventStream(ChannelHandlerContext ctx) { - this(100, ctx); + this(100, ctx); } public BlockingEventStream(int maxBufferSize, ChannelHandlerContext ctx) { - this.ctx = ctx; - events = new LinkedBlockingQueue(maxBufferSize+10); + this.ctx = ctx; + events = new LinkedBlockingQueue<>(maxBufferSize + 10); } public synchronized Promise nextEvent() { @@ -569,20 +586,20 @@ public synchronized Promise nextEvent() { //the socket reads completely(ie. stop reading from socket when queue is full) as in normal NIO operations if you stop reading //from the socket, the local nic buffer fills up, then the remote nic buffer fills(the client's nic), and so the client is informed //he can't write anymore just yet (or he blocks if he is synchronous). - //Then when someone pulls from the queue, the token would be set to enabled allowing to read from nic buffer again and it all propogates + //Then when someone pulls from the queue, the token would be set to enabled allowing to read from nic buffer again and it all propagates //This is normal flow control with NIO but since it is not done properly, this at least fixes the issue where websocket break down and //skip packets. They no longer skip packets anymore. public void publish(T event) { - try { - //This method blocks if the queue is full(read publish method documentation just above) - if (events.remainingCapacity() == 10) { - Logger.trace("events queue is full! Setting readable to false."); - ctx.getChannel().setReadable(false); - } - events.put(event); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + try { + // This method blocks if the queue is full(read publish method documentation just above) + if (events.remainingCapacity() == 10) { + Logger.trace("events queue is full! Setting readable to false."); + ctx.getChannel().setReadable(false); + } + events.put(event); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } notifyNewEvent(); } @@ -596,14 +613,14 @@ synchronized void notifyNewEvent() { class LazyTask extends Promise { - final ChannelHandlerContext ctx; - + final ChannelHandlerContext ctx; + public LazyTask(ChannelHandlerContext ctx) { - this.ctx = ctx; + this.ctx = ctx; } public LazyTask(T value, ChannelHandlerContext ctx) { - this.ctx = ctx; + this.ctx = ctx; invoke(value); } @@ -625,8 +642,9 @@ private void markAsRead(T value) { if (value != null) { events.remove(value); //Don't start back up until we get down to half the total capacity to prevent jittering: - if (events.remainingCapacity() > events.size()) - ctx.getChannel().setReadable(true); + if (events.remainingCapacity() > events.size()) { + ctx.getChannel().setReadable(true); + } } } } @@ -635,8 +653,8 @@ private void markAsRead(T value) { public static class IndexedEvent { private static final AtomicLong idGenerator = new AtomicLong(1); - final public M data; - final public Long id; + public final M data; + public final Long id; public IndexedEvent(M data) { this.data = data; @@ -656,16 +674,16 @@ public static void resetIdGenerator() { public static class ArchivedEventStream { final int archiveSize; - final ConcurrentLinkedQueue> events = new ConcurrentLinkedQueue>(); + final ConcurrentLinkedQueue> events = new ConcurrentLinkedQueue<>(); final List> waiting = Collections.synchronizedList(new ArrayList>()); - final List> pipedStreams = new ArrayList>(); + final List> pipedStreams = new ArrayList<>(); public ArchivedEventStream(int archiveSize) { this.archiveSize = archiveSize; } public synchronized EventStream eventStream() { - final EventStream stream = new EventStream(archiveSize); + EventStream stream = new EventStream<>(archiveSize); for (IndexedEvent event : events) { stream.publish(event.data); } @@ -674,14 +692,14 @@ public synchronized EventStream eventStream() { } public synchronized Promise>> nextEvents(long lastEventSeen) { - FilterTask filter = new FilterTask(lastEventSeen); + FilterTask filter = new FilterTask<>(lastEventSeen); waiting.add(filter); notifyNewEvent(); return filter; } public synchronized List availableEvents(long lastEventSeen) { - List result = new ArrayList(); + List result = new ArrayList<>(); for (IndexedEvent event : events) { if (event.id > lastEventSeen) { result.add(event); @@ -691,7 +709,7 @@ public synchronized List availableEvents(long lastEventSeen) { } public List archive() { - List result = new ArrayList(); + List result = new ArrayList<>(); for (IndexedEvent event : events) { result.add(event.data); } @@ -700,7 +718,7 @@ public List archive() { public synchronized void publish(T event) { if (events.size() >= archiveSize) { - Logger.warn("Dropping message. If this is catastrophic to your app, use a BlockingEvenStream instead"); + Logger.warn("Dropping message. If this is catastrophic to your app, use a BlockingEvenStream instead"); events.poll(); } events.offer(new IndexedEvent(event)); @@ -725,7 +743,7 @@ void notifyNewEvent() { static class FilterTask extends Promise>> { final Long lastEventSeen; - final List> newEvents = new ArrayList>(); + final List> newEvents = new ArrayList<>(); public FilterTask(Long lastEventSeen) { this.lastEventSeen = lastEventSeen; @@ -757,7 +775,7 @@ public static interface Action { void invoke(T result); } - public static abstract class Option implements Iterable { + public abstract static class Option implements Iterable { public abstract boolean isDefined(); @@ -768,7 +786,7 @@ public static None None() { } public static Some Some(T value) { - return new Some(value); + return new Some<>(value); } } @@ -788,6 +806,7 @@ public T get() { throw new IllegalStateException("No value"); } + @Override public Iterator iterator() { return Collections.emptyList().iterator(); } @@ -797,7 +816,7 @@ public String toString() { return "None"; } } - public static None None = new None(); + public static None None = new None<>(); public static class Some extends Option { @@ -817,6 +836,7 @@ public T get() { return value; } + @Override public Iterator iterator() { return Collections.singletonList(value).iterator(); } @@ -829,8 +849,8 @@ public String toString() { public static class Either { - final public Option _1; - final public Option _2; + public final Option _1; + public final Option _2; private Either(Option _1, Option _2) { this._1 = _1; @@ -860,9 +880,9 @@ private E2(Option _1, Option _2) { public static class E3 { - final public Option _1; - final public Option _2; - final public Option _3; + public final Option _1; + public final Option _2; + public final Option _3; private E3(Option _1, Option _2, Option _3) { this._1 = _1; @@ -890,10 +910,10 @@ public String toString() { public static class E4 { - final public Option _1; - final public Option _2; - final public Option _3; - final public Option _4; + public final Option _1; + public final Option _2; + public final Option _3; + public final Option _4; private E4(Option _1, Option _2, Option _3, Option _4) { this._1 = _1; @@ -926,11 +946,11 @@ public String toString() { public static class E5 { - final public Option _1; - final public Option _2; - final public Option _3; - final public Option _4; - final public Option _5; + public final Option _1; + public final Option _2; + public final Option _3; + public final Option _4; + public final Option _5; private E5(Option _1, Option _2, Option _3, Option _4, Option _5) { this._1 = _1; @@ -968,8 +988,8 @@ public String toString() { public static class Tuple { - final public A _1; - final public B _2; + public final A _1; + public final B _2; public Tuple(A _1, B _2) { this._1 = _1; @@ -999,9 +1019,9 @@ public static T2 T2(A a, B b) { public static class T3 { - final public A _1; - final public B _2; - final public C _3; + public final A _1; + public final B _2; + public final C _3; public T3(A _1, B _2, C _3) { this._1 = _1; @@ -1021,10 +1041,10 @@ public static T3 T3(A a, B b, C c) { public static class T4 { - final public A _1; - final public B _2; - final public C _3; - final public D _4; + public final A _1; + public final B _2; + public final C _3; + public final D _4; public T4(A _1, B _2, C _3, D _4) { this._1 = _1; @@ -1040,16 +1060,16 @@ public String toString() { } public static T4 T4(A a, B b, C c, D d) { - return new T4(a, b, c, d); + return new T4<>(a, b, c, d); } public static class T5 { - final public A _1; - final public B _2; - final public C _3; - final public D _4; - final public E _5; + public final A _1; + public final B _2; + public final C _3; + public final D _4; + public final E _5; public T5(A _1, B _2, C _3, D _4, E _5) { this._1 = _1; @@ -1066,10 +1086,10 @@ public String toString() { } public static T5 T5(A a, B b, C c, D d, E e) { - return new T5(a, b, c, d, e); + return new T5<>(a, b, c, d, e); } - public static abstract class Matcher { + public abstract static class Matcher { public abstract Option match(T o); diff --git a/framework/src/play/libs/Files.java b/framework/src/play/libs/Files.java index ada1410eae..76f76c9d8e 100644 --- a/framework/src/play/libs/Files.java +++ b/framework/src/play/libs/Files.java @@ -1,16 +1,24 @@ package play.libs; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import play.exceptions.UnexpectedException; +import static org.apache.commons.io.FileUtils.copyInputStreamToFile; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import static org.apache.commons.io.FileUtils.copyInputStreamToFile; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import play.Logger; +import play.exceptions.UnexpectedException; /** * Files utils @@ -20,18 +28,48 @@ public class Files { /** * Characters that are invalid in Windows OS file names (Unix only forbids '/' character) */ - public static final char[] ILLEGAL_FILENAME_CHARS = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; + public static final char[] ILLEGAL_FILENAME_CHARS = { 34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47 }; public static final char ILLEGAL_FILENAME_CHARS_REPLACE = '_'; + /** + * Indicate if two file refers to the same one + * + * @param a + * First file to compare + * @param b + * Second file to compare + * @return true is file are the same + */ + public static boolean isSameFile(File a, File b) { + if (a != null && b != null) { + Path aPath = null; + Path bPath = null; + try { + aPath = Paths.get(a.getCanonicalPath()); + bPath = Paths.get(b.getCanonicalPath()); + return java.nio.file.Files.isSameFile(aPath, bPath); + } catch (NoSuchFileException e) { + // As the file may not exist, we only compare path + return 0 == aPath.compareTo(bPath); + } catch (Exception e) { + Logger.error(e, "Cannot get canonical path from files"); + } + } + return false; + } + /** * Just copy a file + * * @param from + * source of the file * @param to + * destination file */ public static void copy(File from, File to) { - if (from.getAbsolutePath().equals(to.getAbsolutePath())) { + if (isSameFile(from, to)) { return; } @@ -44,7 +82,10 @@ public static void copy(File from, File to) { /** * Just delete a file. If the file is a directory, it's work. - * @param file The file to delete + * + * @param file + * The file to delete + * @return true if and only if the file is successfully deleted; false otherwise */ public static boolean delete(File file) { if (file.isDirectory()) { @@ -56,11 +97,15 @@ public static boolean delete(File file) { /** * Recursively delete a directory. + * + * @param path + * Path of the directory + * @return true if and only if the directory is successfully deleted; false otherwise */ public static boolean deleteDirectory(File path) { if (path.exists()) { File[] files = path.listFiles(); - for (File file: files) { + for (File file : files) { if (file.isDirectory()) { deleteDirectory(file); } else { @@ -83,8 +128,7 @@ public static boolean copyDir(File from, File to) { public static void unzip(File from, File to) { try { String outDir = to.getCanonicalPath(); - ZipFile zipFile = new ZipFile(from); - try { + try (ZipFile zipFile = new ZipFile(from)) { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); @@ -100,9 +144,6 @@ public static void unzip(File from, File to) { copyInputStreamToFile(zipFile.getInputStream(entry), f); } } - finally { - zipFile.close(); - } } catch (IOException e) { throw new RuntimeException(e); } @@ -110,18 +151,10 @@ public static void unzip(File from, File to) { public static void zip(File directory, File zipFile) { try { - FileOutputStream os = new FileOutputStream(zipFile); - try { - ZipOutputStream zos = new ZipOutputStream(os); - try { + try (FileOutputStream os = new FileOutputStream(zipFile)) { + try (ZipOutputStream zos = new ZipOutputStream(os)) { zipDirectory(directory, directory, zos); } - finally { - zos.close(); - } - } - finally { - os.close(); } } catch (Exception e) { throw new UnexpectedException(e); @@ -129,13 +162,14 @@ public static void zip(File directory, File zipFile) { } /** - * Replace all characters that are invalid in file names on Windows or Unix operating systems - * with {@link Files#ILLEGAL_FILENAME_CHARS_REPLACE} character. - *

    - * This method makes sure your file name can successfully be used to write new file to disk. - * Invalid characters are listed in {@link Files#ILLEGAL_FILENAME_CHARS} array. + * Replace all characters that are invalid in file names on Windows or Unix operating systems with + * {@link Files#ILLEGAL_FILENAME_CHARS_REPLACE} character. + *

    + * This method makes sure your file name can successfully be used to write new file to disk. Invalid characters are + * listed in {@link Files#ILLEGAL_FILENAME_CHARS} array. * - * @param fileName File name to sanitize + * @param fileName + * File name to sanitize * @return Sanitized file name (new String object) if found invalid characters or same string if not */ public static String sanitizeFileName(String fileName) { @@ -143,14 +177,16 @@ public static String sanitizeFileName(String fileName) { } /** - * Replace all characters that are invalid in file names on Windows or Unix operating systems - * with passed in character. - *

    - * This method makes sure your file name can successfully be used to write new file to disk. - * Invalid characters are listed in {@link Files#ILLEGAL_FILENAME_CHARS} array. + * Replace all characters that are invalid in file names on Windows or Unix operating systems with passed in + * character. + *

    + * This method makes sure your file name can successfully be used to write new file to disk. Invalid characters are + * listed in {@link Files#ILLEGAL_FILENAME_CHARS} array. * - * @param fileName File name to sanitize - * @param replacement character to use as replacement for invalid chars + * @param fileName + * File name to sanitize + * @param replacement + * character to use as replacement for invalid chars * @return Sanitized file name (new String object) if found invalid characters or same string if not */ public static String sanitizeFileName(String fileName, char replacement) { @@ -181,16 +217,12 @@ static void zipDirectory(File root, File directory, ZipOutputStream zos) throws if (item.isDirectory()) { zipDirectory(root, item, zos); } else { - FileInputStream fis = new FileInputStream(item); - try { + try (FileInputStream fis = new FileInputStream(item)) { String path = item.getAbsolutePath().substring(root.getAbsolutePath().length() + 1); ZipEntry anEntry = new ZipEntry(path); zos.putNextEntry(anEntry); IOUtils.copyLarge(fis, zos); } - finally { - fis.close(); - } } } } diff --git a/framework/src/play/libs/I18N.java b/framework/src/play/libs/I18N.java index 29b5b2b9f8..79c1fc3f3f 100644 --- a/framework/src/play/libs/I18N.java +++ b/framework/src/play/libs/I18N.java @@ -11,7 +11,7 @@ */ public class I18N { - static final Map symbols = new HashMap(); + static final Map symbols = new HashMap<>(); static { symbols.put("ALL", "Lek"); @@ -161,11 +161,11 @@ public static String getCurrencySymbol(String currency) { } public static String getDateFormat() { - final String localizedDateFormat = Play.configuration.getProperty("date.format." + Lang.get()); + String localizedDateFormat = Play.configuration.getProperty("date.format." + Lang.get()); if (!StringUtils.isEmpty(localizedDateFormat)) { return localizedDateFormat; } - final String globalDateFormat = Play.configuration.getProperty("date.format"); + String globalDateFormat = Play.configuration.getProperty("date.format"); if (!StringUtils.isEmpty(globalDateFormat)) { return globalDateFormat; } diff --git a/framework/src/play/libs/IO.java b/framework/src/play/libs/IO.java index 549d6303a8..cb09da0ff7 100644 --- a/framework/src/play/libs/IO.java +++ b/framework/src/play/libs/IO.java @@ -1,15 +1,21 @@ package play.libs; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import play.exceptions.UnexpectedException; -import play.utils.OrderSafeProperties; +import static org.apache.commons.io.IOUtils.closeQuietly; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.List; import java.util.Properties; -import static org.apache.commons.io.IOUtils.closeQuietly; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import play.exceptions.UnexpectedException; +import play.utils.OrderSafeProperties; /** * IO utils @@ -18,7 +24,9 @@ public class IO { /** * Read a properties file with the utf-8 encoding - * @param is Stream to properties file + * + * @param is + * Stream to properties file * @return The Properties object */ public static Properties readUtf8Properties(InputStream is) { @@ -28,15 +36,16 @@ public static Properties readUtf8Properties(InputStream is) { return properties; } catch (Exception e) { throw new RuntimeException(e); - } - finally { + } finally { closeQuietly(is); } } /** * Read the Stream content as a string (use utf-8) - * @param is The stream to read + * + * @param is + * The stream to read * @return The String content */ public static String readContentAsString(InputStream is) { @@ -45,7 +54,11 @@ public static String readContentAsString(InputStream is) { /** * Read the Stream content as a string - * @param is The stream to read + * + * @param is + * The stream to read + * @param encoding + * Encoding to used * @return The String content */ public static String readContentAsString(InputStream is, String encoding) { @@ -55,9 +68,12 @@ public static String readContentAsString(InputStream is, String encoding) { throw new RuntimeException(e); } } + /** * Read file content to a String (always use utf-8) - * @param file The file to read + * + * @param file + * The file to read * @return The String content */ public static String readContentAsString(File file) { @@ -66,7 +82,11 @@ public static String readContentAsString(File file) { /** * Read file content to a String - * @param file The file to read + * + * @param file + * The file to read + * @param encoding + * Encoding to used * @return The String content */ public static String readContentAsString(File file, String encoding) { @@ -101,7 +121,9 @@ public static List readLines(File file) { /** * Read binary content of a file (warning does not use on large file !) - * @param file The file te read + * + * @param file + * The file te read * @return The binary data */ public static byte[] readContent(File file) { @@ -114,7 +136,9 @@ public static byte[] readContent(File file) { /** * Read binary content of a stream (warning does not use on large file !) - * @param is The stream to read + * + * @param is + * The stream to read * @return The binary data */ public static byte[] readContent(InputStream is) { @@ -127,8 +151,11 @@ public static byte[] readContent(InputStream is) { /** * Write String content to a stream (always use utf-8) - * @param content The content to write - * @param os The stream to write + * + * @param content + * The content to write + * @param os + * The stream to write */ public static void writeContent(CharSequence content, OutputStream os) { writeContent(content, os, "utf-8"); @@ -136,8 +163,13 @@ public static void writeContent(CharSequence content, OutputStream os) { /** * Write String content to a stream (always use utf-8) - * @param content The content to write - * @param os The stream to write + * + * @param content + * The content to write + * @param os + * The stream to write + * @param encoding + * Encoding to used */ public static void writeContent(CharSequence content, OutputStream os, String encoding) { try { @@ -151,8 +183,11 @@ public static void writeContent(CharSequence content, OutputStream os, String en /** * Write String content to a file (always use utf-8) - * @param content The content to write - * @param file The file to write + * + * @param content + * The content to write + * @param file + * The file to write */ public static void writeContent(CharSequence content, File file) { writeContent(content, file, "utf-8"); @@ -160,8 +195,13 @@ public static void writeContent(CharSequence content, File file) { /** * Write String content to a file (always use utf-8) - * @param content The content to write - * @param file The file to write + * + * @param content + * The content to write + * @param file + * The file to write + * @param encoding + * Encoding to used */ public static void writeContent(CharSequence content, File file, String encoding) { try { @@ -172,39 +212,51 @@ public static void writeContent(CharSequence content, File file, String encoding } /** - * Write binay data to a file - * @param data The binary data to write - * @param file The file to write + * Write binary data to a file + * + * @param data + * The binary data to write + * @param file + * The file to write */ public static void write(byte[] data, File file) { try { FileUtils.writeByteArrayToFile(file, data); - } catch(IOException e) { + } catch (IOException e) { throw new UnexpectedException(e); } } /** * Copy an stream to another one. + * + * @param is + * The source stream + * @param os + * The destination stream */ public static void copy(InputStream is, OutputStream os) { try { IOUtils.copyLarge(is, os); - } catch(IOException e) { + } catch (IOException e) { throw new UnexpectedException(e); - } - finally { + } finally { closeQuietly(is); } } /** * Copy an stream to another one. + * + * @param is + * The source stream + * @param os + * The destination stream */ public static void write(InputStream is, OutputStream os) { try { IOUtils.copyLarge(is, os); - } catch(IOException e) { + } catch (IOException e) { throw new UnexpectedException(e); } finally { closeQuietly(is); @@ -212,16 +264,20 @@ public static void write(InputStream is, OutputStream os) { } } - /** + /** * Copy an stream to another one. + * + * @param is + * The source stream + * @param f + * The destination file */ public static void write(InputStream is, File f) { try { OutputStream os = new FileOutputStream(f); try { IOUtils.copyLarge(is, os); - } - finally { + } finally { closeQuietly(is); closeQuietly(os); } @@ -236,12 +292,12 @@ public static void copyDirectory(File source, File target) { if (!target.exists()) { target.mkdir(); } - for (String child: source.list()) { + for (String child : source.list()) { copyDirectory(new File(source, child), new File(target, child)); } } else { try { - write(new FileInputStream(source), new FileOutputStream(target)); + write(new FileInputStream(source), new FileOutputStream(target)); } catch (IOException e) { throw new UnexpectedException(e); } diff --git a/framework/src/play/libs/Images.java b/framework/src/play/libs/Images.java index fe130169d5..fa501f2255 100644 --- a/framework/src/play/libs/Images.java +++ b/framework/src/play/libs/Images.java @@ -61,14 +61,11 @@ public static void resize(File originalImage, File to, int w, int h) { * @param to * The destination file * @param w - * The new width (or -1 to proportionally resize) or the maxWidth - * if keepRatio is true + * The new width (or -1 to proportionally resize) or the maxWidth if keepRatio is true * @param h - * The new height (or -1 to proportionally resize) or the - * maxHeight if keepRatio is true + * The new height (or -1 to proportionally resize) or the maxHeight if keepRatio is true * @param keepRatio - * : if true, resize will keep the original image ratio and use w - * and h as max dimensions + * if true, resize will keep the original image ratio and use w and h as max dimensions */ public static void resize(File originalImage, File to, int w, int h, boolean keepRatio) { try { @@ -131,14 +128,11 @@ public static void resize(File originalImage, File to, int w, int h, boolean kee ImageWriter writer = ImageIO.getImageWritersByMIMEType(mimeType).next(); ImageWriteParam params = writer.getDefaultWriteParam(); - FileImageOutputStream toFs = new FileImageOutputStream(to); - try { + try (FileImageOutputStream toFs = new FileImageOutputStream(to)) { writer.setOutput(toFs); IIOImage image = new IIOImage(dest, null, null); writer.write(null, image, params); toFs.flush(); - } finally { - toFs.close(); } writer.dispose(); } catch (Exception e) { @@ -187,14 +181,11 @@ public static void crop(File originalImage, File to, int x1, int y1, int x2, int ImageWriter writer = ImageIO.getImageWritersByMIMEType(mimeType).next(); ImageWriteParam params = writer.getDefaultWriteParam(); - FileImageOutputStream toFs = new FileImageOutputStream(to); - try { + try (FileImageOutputStream toFs = new FileImageOutputStream(to)) { writer.setOutput(toFs); IIOImage image = new IIOImage(dest, null, null); writer.write(null, image, params); toFs.flush(); - } finally { - toFs.close(); } writer.dispose(); } catch (Exception e) { @@ -210,6 +201,7 @@ public static void crop(File originalImage, File to, int x1, int y1, int x2, int * The image file * @return The base64 encoded value * @throws java.io.IOException + * Thrown if the encoding encounters any problems. */ public static String toBase64(File image) throws IOException { return "data:" + MimeTypes.getMimeType(image.getName()) + ";base64," + Codec.encodeBASE64(IO.readContent(image)); @@ -217,6 +209,12 @@ public static String toBase64(File image) throws IOException { /** * Create a captche image + * + * @param width + * The width of the captche + * @param height + * The height of the captche + * @return The given captcha */ public static Captcha captcha(int width, int height) { return new Captcha(width, height); @@ -224,6 +222,8 @@ public static Captcha captcha(int width, int height) { /** * Create a 150x150 captcha image + * + * @return The given captcha */ public static Captcha captcha() { return captcha(150, 50); @@ -238,10 +238,12 @@ public static class Captcha extends InputStream { public BackgroundProducer background = new TransparentBackgroundProducer(); public GimpyRenderer gimpy = new RippleGimpyRenderer(); public Color textColor = Color.BLACK; - public List fonts = new ArrayList(2); + public List fonts = new ArrayList<>(2); public int w, h; public Color noise = null; + ByteArrayInputStream bais = null; + public Captcha(int w, int h) { this.w = w; this.h = h; @@ -251,14 +253,19 @@ public Captcha(int w, int h) { /** * Tell the captche to draw a text and retrieve it + * + * @return the given text */ public String getText() { return getText(5); } /** - * Tell the captche to draw a text using the specified color (ex. - * #000000) and retrieve it + * Tell the captche to draw a text using the specified color (ex. #000000) and retrieve it + * + * @param color + * a String that represents an opaque color as a 24-bit integer + * @return The text to draw */ public String getText(String color) { this.textColor = Color.decode(color); @@ -267,24 +274,42 @@ public String getText(String color) { /** * Tell the captche to draw a text of the specified size and retrieve it + * + * @param length + * the specified size of the text + * @return The text to draw */ public String getText(int length) { return getText(length, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"); } /** - * Tell the captche to draw a text of the specified size using the - * specified color (ex. #000000) and retrieve it + * Tell the captche to draw a text of the specified size using the specified color (ex. #000000) and retrieve it + * + * @param color + * a String that represents an opaque color as a 24-bit integer + * @param length + * the specified size of the text + * @return The text to draw */ public String getText(String color, int length) { this.textColor = Color.decode(color); return getText(length); } + /** + * Tell the captche to draw a text of the specified size using specials characters and retrieve it + * + * @param length + * the specified size of the text + * @param chars + * List of allowed characters + * @return The text to draw + */ public String getText(int length, String chars) { char[] charsArray = chars.toCharArray(); Random random = new Random(System.currentTimeMillis()); - StringBuffer sb = new StringBuffer(length); + StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append(charsArray[random.nextInt(charsArray.length)]); } @@ -292,6 +317,18 @@ public String getText(int length, String chars) { return text; } + /** + * Tell the captche to draw a text of the specified size using specials characters and a the specified color + * (ex. #000000)and retrieve it + * + * @param color + * a String that represents an opaque color as a 24-bit integer + * @param length + * the specified size of the text + * @param chars + * List of allowed characters + * @return The text to draw + */ public String getText(String color, int length, String chars) { this.textColor = Color.decode(color); return getText(length, chars); @@ -299,6 +336,8 @@ public String getText(String color, int length, String chars) { /** * Add noise to the captcha. + * + * @return The given captcha */ public Captcha addNoise() { noise = Color.BLACK; @@ -307,6 +346,10 @@ public Captcha addNoise() { /** * Add noise to the captcha. + * + * @param color + * a String that represents an opaque color as a 24-bit integer + * @return The given captcha */ public Captcha addNoise(String color) { noise = Color.decode(color); @@ -315,6 +358,12 @@ public Captcha addNoise(String color) { /** * Set a gradient background. + * + * @param from + * a String that represents an opaque color use to start the gradient + * @param to + * a String that represents an opaque color use to end the gradient + * @return The given captcha */ public Captcha setBackground(String from, String to) { GradiatedBackgroundProducer bg = new GradiatedBackgroundProducer(); @@ -326,6 +375,10 @@ public Captcha setBackground(String from, String to) { /** * Set a solid background. + * + * @param color + * a String that represents an opaque color as a 24-bit integer + * @return The given captcha */ public Captcha setBackground(String color) { background = new FlatColorBackgroundProducer(Color.decode(color)); @@ -334,14 +387,14 @@ public Captcha setBackground(String color) { /** * Set a squiggles background + * + * @return The given captcha */ public Captcha setSquigglesBackground() { background = new SquigglesBackgroundProducer(); return this; } // ~~ rendering - ByteArrayInputStream bais = null; - @Override public int read() throws IOException { check(); diff --git a/framework/src/play/libs/Mail.java b/framework/src/play/libs/Mail.java index 2a3726b995..4f46cfcb2b 100644 --- a/framework/src/play/libs/Mail.java +++ b/framework/src/play/libs/Mail.java @@ -1,5 +1,18 @@ package play.libs; +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import javax.mail.Authenticator; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; + import org.apache.commons.lang.StringUtils; import org.apache.commons.mail.Email; import org.apache.commons.mail.EmailException; @@ -7,29 +20,21 @@ import play.Logger; import play.Play; import play.exceptions.MailException; -import play.libs.mail.*; +import play.libs.mail.AbstractMailSystemFactory; +import play.libs.mail.MailSystem; import play.libs.mail.test.LegacyMockMailSystem; -import play.utils.Utils; - -import javax.mail.*; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.*; +import play.utils.Utils.Maps; /** * Mail utils */ public class Mail { - private static class StaticMailSystemFactory extends - AbstractMailSystemFactory { + private static class StaticMailSystemFactory extends AbstractMailSystemFactory { private final MailSystem mailSystem; - public StaticMailSystemFactory(MailSystem mailSystem) { + private StaticMailSystemFactory(MailSystem mailSystem) { this.mailSystem = mailSystem; } @@ -40,12 +45,16 @@ public MailSystem currentMailSystem() { } - public static Session session; - public static boolean asynchronousSend = true; + public static Session session; + public static boolean asynchronousSend = true; protected static AbstractMailSystemFactory mailSystemFactory = AbstractMailSystemFactory.DEFAULT; /** * Send an email + * + * @param email + * An Email message + * @return true if email successfully send */ public static Future send(Email email) { try { @@ -62,11 +71,13 @@ protected static MailSystem currentMailSystem() { } /** - * Through this method you can substitute the current MailSystem. This is - * especially helpful for testing purposes like using mock libraries. + * Through this method you can substitute the current MailSystem. This is especially helpful for testing purposes + * like using mock libraries. * - * @author Andreas Simon - * @see MailSystem + * @author Andreas Simon <a.simon@quagilis.de> + * @param mailSystem + * The mailSystem to use + * @see MailSystem */ public static void useMailSystem(MailSystem mailSystem) { mailSystemFactory = new StaticMailSystemFactory(mailSystem); @@ -77,23 +88,21 @@ public static void resetMailSystem() { } public static Email buildMessage(Email email) throws EmailException { - String from = Play.configuration.getProperty("mail.smtp.from"); if (email.getFromAddress() == null && !StringUtils.isEmpty(from)) { email.setFrom(from); } else if (email.getFromAddress() == null) { throw new MailException("Please define a 'from' email address", new NullPointerException()); } - if ((email.getToAddresses() == null || email.getToAddresses().size() == 0) && - (email.getCcAddresses() == null || email.getCcAddresses().size() == 0) && - (email.getBccAddresses() == null || email.getBccAddresses().size() == 0)) - { + if ((email.getToAddresses() == null || email.getToAddresses().isEmpty()) + && (email.getCcAddresses() == null || email.getCcAddresses().isEmpty()) + && (email.getBccAddresses() == null || email.getBccAddresses().isEmpty())) { throw new MailException("Please define a recipient email address", new NullPointerException()); } if (email.getSubject() == null) { throw new MailException("Please define a subject", new NullPointerException()); } - if (email.getReplyToAddresses() == null || email.getReplyToAddresses().size() == 0) { + if (email.getReplyToAddresses() == null || email.getReplyToAddresses().isEmpty()) { email.addReplyTo(email.getFromAddress().getAddress()); } @@ -107,22 +116,24 @@ public static Session getSession() { props.put("mail.smtp.host", Play.configuration.getProperty("mail.smtp.host", "localhost")); String channelEncryption; - if (Play.configuration.containsKey("mail.smtp.protocol") && Play.configuration.getProperty("mail.smtp.protocol", "smtp").equals("smtps")) { + if (Play.configuration.containsKey("mail.smtp.protocol") + && "smtps".equals(Play.configuration.getProperty("mail.smtp.protocol", "smtp"))) { // Backward compatibility before stable5 channelEncryption = "starttls"; } else { channelEncryption = Play.configuration.getProperty("mail.smtp.channel", "clear"); } - if (channelEncryption.equals("clear")) { + if ("clear".equals(channelEncryption)) { props.put("mail.smtp.port", "25"); - } else if (channelEncryption.equals("ssl")) { - // port 465 + setup yes ssl socket factory (won't verify that the server certificate is signed with a root ca.) + } else if ("ssl".equals(channelEncryption)) { + // port 465 + setup yes ssl socket factory (won't verify that the server certificate is signed with a + // root ca.) props.put("mail.smtp.port", "465"); props.put("mail.smtp.socketFactory.port", "465"); props.put("mail.smtp.socketFactory.class", "play.utils.YesSSLSocketFactory"); props.put("mail.smtp.socketFactory.fallback", "false"); - } else if (channelEncryption.equals("starttls")) { + } else if ("starttls".equals(channelEncryption)) { // port 25 + enable starttls + ssl socket factory props.put("mail.smtp.port", "25"); props.put("mail.smtp.starttls.enable", "true"); @@ -130,16 +141,15 @@ public static Session getSession() { // story to be continued in javamail 1.4.2 : https://glassfish.dev.java.net/issues/show_bug.cgi?id=5189 } - // Inject additional mail.* settings declared in Play! configuration - Map additionalSettings = new HashMap(); - additionalSettings = Utils.Maps.filterMap(Play.configuration, "^mail\\..*"); - if(additionalSettings.size() > 0){ + // Inject additional mail.* settings declared in Play! configuration + Map additionalSettings = Maps.filterMap(Play.configuration, "^mail\\..*"); + if (!additionalSettings.isEmpty()) { // Remove "password" fields additionalSettings.remove("mail.smtp.pass"); - additionalSettings.remove("mail.smtp.password"); + additionalSettings.remove("mail.smtp.password"); props.putAll(additionalSettings); } - + String user = Play.configuration.getProperty("mail.smtp.user"); String password = Play.configuration.getProperty("mail.smtp.pass"); if (password == null) { @@ -154,7 +164,7 @@ public static Session getSession() { try { session = Session.getInstance(props, (Authenticator) Play.classloader.loadClass(authenticator).newInstance()); } catch (Exception e) { - Logger.error(e, "Cannot instanciate custom SMTP authenticator (%s)", authenticator); + Logger.error(e, "Cannot instantiate custom SMTP authenticator (%s)", authenticator); } } @@ -178,12 +188,15 @@ public static Session getSession() { /** * Send a JavaMail message * - * @param msg An Email message + * @param msg + * An Email message + * @return true if email successfully send */ public static Future sendMessage(final Email msg) { if (asynchronousSend) { return executor.submit(new Callable() { + @Override public Boolean call() { try { msg.setSentDate(new Date()); @@ -197,7 +210,7 @@ public Boolean call() { } }); } else { - final StringBuffer result = new StringBuffer(); + final StringBuilder result = new StringBuilder(); try { msg.setSentDate(new Date()); msg.send(); @@ -207,24 +220,28 @@ public Boolean call() { result.append("oops"); } return new Future() { - + @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } + @Override public boolean isCancelled() { return false; } + @Override public boolean isDone() { return true; } - public Boolean get() throws InterruptedException, ExecutionException { + @Override + public Boolean get() { return result.length() == 0; } - public Boolean get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + @Override + public Boolean get(long timeout, TimeUnit unit) { return result.length() == 0; } }; @@ -252,8 +269,8 @@ protected PasswordAuthentication getPasswordAuthentication() { /** * Just kept for compatibility reasons, use test double substitution mechanism instead. * - * @see Mail#useMailSystem(MailSystem) - * @author Andreas Simon + * @see Mail#useMailSystem(MailSystem) + * @author Andreas Simon <a.simon@quagilis.de> */ public static LegacyMockMailSystem Mock = new LegacyMockMailSystem(); } diff --git a/framework/src/play/libs/MimeTypes.java b/framework/src/play/libs/MimeTypes.java index 3e03cb42b5..3670356c45 100644 --- a/framework/src/play/libs/MimeTypes.java +++ b/framework/src/play/libs/MimeTypes.java @@ -1,8 +1,5 @@ package play.libs; -import play.*; -import play.mvc.Http; - import java.io.InputStream; import java.util.Enumeration; import java.util.Map; @@ -10,6 +7,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import play.Logger; +import play.Play; +import play.PlayPlugin; +import play.mvc.Http; + /** * MimeTypes utils */ @@ -24,7 +26,9 @@ public class MimeTypes { /** * return the mimetype from a file name - * @param filename the file name + * + * @param filename + * the file name * @return the mimetype or the empty string if not found */ public static String getMimeType(String filename) { @@ -32,9 +36,12 @@ public static String getMimeType(String filename) { } /** - * return the mimetype from a file name.
    - * @param filename the file name - * @param defaultMimeType the default mime type to return when no matching mimetype is found + * return the mimetype from a file name.
    + * + * @param filename + * the file name + * @param defaultMimeType + * the default mime type to return when no matching mimetype is found * @return the mimetype */ public static String getMimeType(String filename, String defaultMimeType) { @@ -54,36 +61,44 @@ public static String getMimeType(String filename, String defaultMimeType) { } /** - * return the content-type from a file name. If none is found returning application/octet-stream
    + * return the content-type from a file name. If none is found returning application/octet-stream
    * For a text-based content-type, also return the encoding suffix eg. "text/plain; charset=utf-8" - * @param filename the file name + * + * @param filename + * the file name * @return the content-type deduced from the file extension. */ - public static String getContentType(String filename){ + public static String getContentType(String filename) { return getContentType(filename, "application/octet-stream"); } /** - * return the content-type from a file name.
    + * return the content-type from a file name.
    * For a text-based content-type, also return the encoding suffix eg. "text/plain; charset=utf-8" - * @param filename the file name - * @param defaultContentType the default content-type to return when no matching content-type is found + * + * @param filename + * the file name + * @param defaultContentType + * the default content-type to return when no matching content-type is found * @return the content-type deduced from the file extension. */ - public static String getContentType(String filename, String defaultContentType){ + public static String getContentType(String filename, String defaultContentType) { String contentType = getMimeType(filename, null); - if (contentType == null){ - contentType = defaultContentType; + if (contentType == null) { + contentType = defaultContentType; } - if (contentType != null && contentType.startsWith("text/")){ + if (contentType != null && contentType.startsWith("text/")) { return contentType + "; charset=" + getCurrentCharset(); } return contentType; } /** - * check the mimetype is referenced in the mimetypes database - * @param mimeType the mimeType to verify + * Check the mimetype is referenced in the mimetypes database + * + * @param mimeType + * the mimeType to verify + * @return true if the mimetype is referenced, false otherwise */ public static boolean isValidMimeType(String mimeType) { if (mimeType == null) { @@ -101,8 +116,7 @@ private static String getCurrentCharset() { if (currentResponse != null) { charset = currentResponse.encoding; - } - else { + } else { charset = Play.defaultWebEncoding; } @@ -110,7 +124,8 @@ private static String getCurrentCharset() { } private static synchronized void initMimetypes() { - if (mimetypes != null) return; + if (mimetypes != null) + return; // Load default mimetypes from the framework try { InputStream is = MimeTypes.class.getClassLoader().getResourceAsStream("play/libs/mime-types.properties"); @@ -120,19 +135,19 @@ private static synchronized void initMimetypes() { Logger.warn(ex.getMessage()); } // Load mimetypes from plugins - for (PlayPlugin plugin: Play.pluginCollection.getEnabledPlugins()) { + for (PlayPlugin plugin : Play.pluginCollection.getEnabledPlugins()) { Map pluginTypes = plugin.addMimeTypes(); - for (String type: pluginTypes.keySet()) { + for (String type : pluginTypes.keySet()) { mimetypes.setProperty(type, pluginTypes.get(type)); } } // Load custom mimetypes from the application configuration Enumeration confenum = Play.configuration.keys(); while (confenum.hasMoreElements()) { - String key = (String)confenum.nextElement(); + String key = (String) confenum.nextElement(); if (key.startsWith("mimetype.")) { String type = key.substring(key.indexOf('.') + 1).toLowerCase(); - String value = (String)Play.configuration.get(key); + String value = (String) Play.configuration.get(key); mimetypes.setProperty(type, value); } } diff --git a/framework/src/play/libs/OAuth.java b/framework/src/play/libs/OAuth.java index 7c24b22d34..59444626f7 100644 --- a/framework/src/play/libs/OAuth.java +++ b/framework/src/play/libs/OAuth.java @@ -13,7 +13,7 @@ import play.mvc.Scope.Params; /** - * Library to access ressources protected by OAuth 1.0a. For OAuth 2.0, see play.libs.OAuth2. + * Library to access resources protected by OAuth 1.0a. For OAuth 2.0, see play.libs.OAuth2. * */ public class OAuth { @@ -29,7 +29,9 @@ private OAuth(ServiceInfo info) { /** * Create an OAuth object for the service described in info - * @param info must contain all informations related to the service + * + * @param info + * must contain all information related to the service * @return the OAuth object */ public static OAuth service(ServiceInfo info) { @@ -42,6 +44,7 @@ public static boolean isVerifierResponse() { /** * Request the request token and secret. + * * @return a Response object holding either the result in case of a success or the error */ public Response retrieveRequestToken() { @@ -50,7 +53,9 @@ public Response retrieveRequestToken() { /** * Request the request token and secret. - * @param callbackURL the URL where the provider should redirect to + * + * @param callbackURL + * the URL where the provider should redirect to * @return a Response object holding either the result in case of a success or the error */ public Response retrieveRequestToken(String callbackURL) { @@ -65,7 +70,9 @@ public Response retrieveRequestToken(String callbackURL) { /** * Exchange a request token for an access token. - * @param requestTokenResponse a successful response obtained from retrieveRequestToken + * + * @param requestTokenResponse + * a successful response obtained from retrieveRequestToken * @return a Response object holding either the result in case of a success or the error */ public Response retrieveAccessToken(Response requestTokenResponse) { @@ -74,12 +81,15 @@ public Response retrieveAccessToken(Response requestTokenResponse) { /** * Exchange a request token for an access token. - * @param token the token obtained from a previous call - * @param secret your application secret + * + * @param token + * the token obtained from a previous call + * @param secret + * your application secret * @return a Response object holding either the result in case of a success or the error */ public Response retrieveAccessToken(String token, String secret) { - OAuthConsumer consumer = new DefaultOAuthConsumer(info.consumerKey, info.consumerSecret); + OAuthConsumer consumer = new DefaultOAuthConsumer(info.consumerKey, info.consumerSecret); consumer.setTokenWithSecret(token, secret); String verifier = Params.current().get("oauth_verifier"); try { @@ -92,6 +102,7 @@ public Response retrieveAccessToken(String token, String secret) { /** * Request the unauthorized token and secret. They can then be read with getTokens() + * * @return the url to redirect the user to get the verifier and continue the process * @deprecated use retrieveRequestToken() instead */ @@ -102,6 +113,9 @@ public TokenPair requestUnauthorizedToken() { } /** + * @param tokenPair + * The token / secret pair + * @return the url * @deprecated use retrieveAccessToken() instead */ @Deprecated @@ -111,14 +125,13 @@ public TokenPair requestAccessToken(TokenPair tokenPair) { } public String redirectUrl(String token) { - return oauth.signpost.OAuth.addQueryParameters(provider.getAuthorizationWebsiteUrl(), - oauth.signpost.OAuth.OAUTH_TOKEN, token); + return oauth.signpost.OAuth.addQueryParameters(provider.getAuthorizationWebsiteUrl(), oauth.signpost.OAuth.OAUTH_TOKEN, token); } @Deprecated public String redirectUrl(TokenPair tokenPair) { - return oauth.signpost.OAuth.addQueryParameters(provider.getAuthorizationWebsiteUrl(), - oauth.signpost.OAuth.OAUTH_TOKEN, tokenPair.token); + return oauth.signpost.OAuth.addQueryParameters(provider.getAuthorizationWebsiteUrl(), oauth.signpost.OAuth.OAUTH_TOKEN, + tokenPair.token); } /** @@ -131,11 +144,9 @@ public static class ServiceInfo { public String authorizationURL; public String consumerKey; public String consumerSecret; - public ServiceInfo(String requestTokenURL, - String accessTokenURL, - String authorizationURL, - String consumerKey, - String consumerSecret) { + + public ServiceInfo(String requestTokenURL, String accessTokenURL, String authorizationURL, String consumerKey, + String consumerSecret) { this.requestTokenURL = requestTokenURL; this.accessTokenURL = accessTokenURL; this.authorizationURL = authorizationURL; @@ -145,34 +156,39 @@ public ServiceInfo(String requestTokenURL, } /** - * Response to an OAuth 1.0 request. - * If success token and secret are non null, and error is null. - * If error token and secret are null, and error is non null. + * Response to an OAuth 1.0 request. If success token and secret are non null, and error is null. If error token and + * secret are null, and error is non null. * */ public static class Response { public final String token; public final String secret; public final Error error; + private Response(String token, String secret, Error error) { this.token = token; this.secret = secret; this.error = error; } + /** * Create a new success response - * @param pair the TokenPair returned by the provider + * + * @param pair + * the TokenPair returned by the provider * @return a new Response object holding the token pair */ private static Response success(String token, String secret) { return new Response(token, secret, null); } + private static Response error(Error error) { return new Response(null, null, error); } - @Override public String toString() { - return (error != null) ? ("Error: " + error) - : ("Success: " + token + " - " + secret); + + @Override + public String toString() { + return (error != null) ? ("Error: " + error) : ("Success: " + token + " - " + secret); } } @@ -180,13 +196,11 @@ public static class Error { public final OAuthException exception; public final Type type; public final String details; + public enum Type { - MESSAGE_SIGNER, - NOT_AUTHORIZED, - EXPECTATION_FAILED, - COMMUNICATION, - OTHER + MESSAGE_SIGNER, NOT_AUTHORIZED, EXPECTATION_FAILED, COMMUNICATION, OTHER } + private Error(OAuthException exception) { this.exception = exception; if (this.exception instanceof OAuthMessageSignerException) { @@ -202,8 +216,13 @@ private Error(OAuthException exception) { } this.details = exception.getMessage(); } - public String details() { return details; } - @Override public String toString() { + + public String details() { + return details; + } + + @Override + public String toString() { return "OAuth.Error: " + type + " - " + details; } } @@ -212,10 +231,12 @@ private Error(OAuthException exception) { public static class TokenPair { public String token; public String secret; + public TokenPair(String token, String secret) { this.token = token; this.secret = secret; } + @Override public String toString() { return token + " - " + secret; diff --git a/framework/src/play/libs/OAuth2.java b/framework/src/play/libs/OAuth2.java index 0623e9e996..85944c9028 100644 --- a/framework/src/play/libs/OAuth2.java +++ b/framework/src/play/libs/OAuth2.java @@ -4,33 +4,28 @@ import java.util.Map; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import play.libs.WS.HttpResponse; import play.mvc.Http.Request; import play.mvc.Scope.Params; import play.mvc.results.Redirect; -import play.libs.WS.HttpResponse; - -import com.google.gson.JsonObject; - /** - * Library to access ressources protected by OAuth 2.0. For OAuth 1.0a, see play.libs.OAuth. - * See the facebook-oauth2 example for usage. - * + * Library to access resources protected by OAuth 2.0. For OAuth 1.0a, see play.libs.OAuth. See the facebook-oauth2 + * example for usage. */ public class OAuth2 { - private static final String CLIENT_ID_NAME = "client_id"; - private static final String REDIRECT_URI = "redirect_uri"; - + private static final String CLIENT_ID_NAME = "client_id"; + private static final String REDIRECT_URI = "redirect_uri"; + public String authorizationURL; public String accessTokenURL; public String clientid; public String secret; - public OAuth2(String authorizationURL, - String accessTokenURL, - String clientid, - String secret) { + public OAuth2(String authorizationURL, String accessTokenURL, String clientid, String secret) { this.accessTokenURL = accessTokenURL; this.authorizationURL = authorizationURL; this.clientid = clientid; @@ -41,53 +36,56 @@ public static boolean isCodeResponse() { return Params.current().get("code") != null; } - /** - * First step of the OAuth2 process: redirects the user to the authorisation page - * - * @param callbackURL - */ - public void retrieveVerificationCode(String callbackURL) { - retrieveVerificationCode(callbackURL, new HashMap()); - } - - /** - * First step of the oAuth2 process. This redirects the user to the authorization page on the oAuth2 provider. This is a helper method that only takes one parameter name,value pair and then - * converts them into a map to be used by {@link #retrieveVerificationCode(String, Map)} - * - * @param callbackURL - * The URL to redirect the user to after authorization - * @param parameterName - * An additional parameter name - * @param parameterValue - * An additional parameter value - */ - public void retrieveVerificationCode(String callbackURL, String parameterName, String parameterValue) { - Map parameters = new HashMap(); - parameters.put(parameterName, parameterValue); - retrieveVerificationCode(callbackURL, parameters); - } - - /** - * First step of the oAuth2 process. This redirects the user to the authorisation page on the oAuth2 provider. - * - * @param callbackURL - * The URL to redirect the user to after authorisation - * @param parameters - * Any additional parameters that weren't included in the constructor. For example you might need to add a response_type. - */ - public void retrieveVerificationCode(String callbackURL, Map parameters) { - parameters.put(CLIENT_ID_NAME, clientid); - parameters.put(REDIRECT_URI, callbackURL); - throw new Redirect(authorizationURL, parameters); - } - + /** + * First step of the OAuth2 process: redirects the user to the authorisation page + * + * @param callbackURL + * The callback URL + */ + public void retrieveVerificationCode(String callbackURL) { + retrieveVerificationCode(callbackURL, new HashMap()); + } + + /** + * First step of the oAuth2 process. This redirects the user to the authorization page on the oAuth2 provider. This + * is a helper method that only takes one parameter name,value pair and then converts them into a map to be used by + * {@link #retrieveVerificationCode(String, Map)} + * + * @param callbackURL + * The URL to redirect the user to after authorization + * @param parameterName + * An additional parameter name + * @param parameterValue + * An additional parameter value + */ + public void retrieveVerificationCode(String callbackURL, String parameterName, String parameterValue) { + Map parameters = new HashMap<>(); + parameters.put(parameterName, parameterValue); + retrieveVerificationCode(callbackURL, parameters); + } + + /** + * First step of the oAuth2 process. This redirects the user to the authorisation page on the oAuth2 provider. + * + * @param callbackURL + * The URL to redirect the user to after authorisation + * @param parameters + * Any additional parameters that weren't included in the constructor. For example you might need to add + * a response_type. + */ + public void retrieveVerificationCode(String callbackURL, Map parameters) { + parameters.put(CLIENT_ID_NAME, clientid); + parameters.put(REDIRECT_URI, callbackURL); + throw new Redirect(authorizationURL, parameters); + } + public void retrieveVerificationCode() { retrieveVerificationCode(Request.current().getBase() + Request.current().url); } public Response retrieveAccessToken(String callbackURL) { String accessCode = Params.current().get("code"); - Map params = new HashMap(); + Map params = new HashMap<>(); params.put("client_id", clientid); params.put("client_secret", secret); params.put("redirect_uri", callbackURL); @@ -109,6 +107,7 @@ public void requestAccessToken() { } /** + * @return The access token * @deprecated Use @{link play.libs.OAuth2.retrieveAccessToken()} instead */ @Deprecated @@ -120,11 +119,13 @@ public static class Response { public final String accessToken; public final Error error; public final WS.HttpResponse httpResponse; + private Response(String accessToken, Error error, WS.HttpResponse response) { this.accessToken = accessToken; this.error = error; this.httpResponse = response; } + public Response(WS.HttpResponse response) { this.httpResponse = response; this.accessToken = getAccessToken(response); @@ -134,11 +135,13 @@ public Response(WS.HttpResponse response) { this.error = Error.oauth2(response); } } + public static Response error(Error error, WS.HttpResponse response) { return new Response(null, error, response); } + private String getAccessToken(WS.HttpResponse httpResponse) { - if(httpResponse.getContentType().contains("application/json")) { + if (httpResponse.getContentType().contains("application/json")) { JsonElement accessToken = httpResponse.getJson().getAsJsonObject().get("access_token"); return accessToken != null ? accessToken.getAsString() : null; } else { @@ -151,35 +154,37 @@ public static class Error { public final Type type; public final String error; public final String description; + public enum Type { - COMMUNICATION, - OAUTH, - UNKNOWN + COMMUNICATION, OAUTH, UNKNOWN } + private Error(Type type, String error, String description) { this.type = type; this.error = error; this.description = description; } + static Error communication() { return new Error(Type.COMMUNICATION, null, null); } + static Error oauth2(WS.HttpResponse response) { if (response.getQueryString().containsKey("error")) { Map qs = response.getQueryString(); - return new Error(Type.OAUTH, - qs.get("error"), - qs.get("error_description")); - } else if (response.getContentType().startsWith("text/javascript")) { // Stupid Facebook returns JSON with the wrong encoding + return new Error(Type.OAUTH, qs.get("error"), qs.get("error_description")); + } else if (response.getContentType().startsWith("text/javascript")) { // Stupid Facebook returns JSON with + // the wrong encoding JsonObject jsonResponse = response.getJson().getAsJsonObject().getAsJsonObject("error"); - return new Error(Type.OAUTH, - jsonResponse.getAsJsonPrimitive("type").getAsString(), + return new Error(Type.OAUTH, jsonResponse.getAsJsonPrimitive("type").getAsString(), jsonResponse.getAsJsonPrimitive("message").getAsString()); } else { return new Error(Type.UNKNOWN, null, null); } } - @Override public String toString() { + + @Override + public String toString() { return "OAuth2 Error: " + type + " - " + error + " (" + description + ")"; } } diff --git a/framework/src/play/libs/OpenID.java b/framework/src/play/libs/OpenID.java index 01b08e29d1..8e1c612318 100644 --- a/framework/src/play/libs/OpenID.java +++ b/framework/src/play/libs/OpenID.java @@ -35,10 +35,10 @@ private OpenID(String id) { String id; String returnAction; String realmAction; - List sregRequired = new ArrayList(); - List sregOptional = new ArrayList(); - Map axRequired = new HashMap(); - Map axOptional = new HashMap(); + List sregRequired = new ArrayList<>(); + List sregOptional = new ArrayList<>(); + Map axRequired = new HashMap<>(); + Map axOptional = new HashMap<>(); public OpenID returnTo(String action) { this.returnAction = action; @@ -201,9 +201,7 @@ public boolean verify() { } throw new Redirect(url); - } catch (Redirect e) { - throw e; - } catch (PlayException e) { + } catch (Redirect | PlayException e) { throw e; } catch (Exception e) { return false; @@ -217,6 +215,10 @@ public static OpenID id(String id) { /** * Normalize the given openid as a standard openid + * + * @param openID + * the given openid + * @return The normalize openID */ public static String normalize(String openID) { openID = openID.trim(); @@ -234,13 +236,15 @@ public static String normalize(String openID) { } openID = new URI(openID).toString(); } catch (Exception e) { - throw new RuntimeException(openID + " is not a valid URL"); + throw new RuntimeException(openID + " is not a valid URL", e); } return openID; } /** * Is the current request an authentication response from the OP ? + * + * @return true if the current request an authentication response */ public static boolean isAuthenticationResponse() { return Params.current().get("openid.mode") != null; @@ -354,7 +358,7 @@ public static class UserInfo { /** * Extensions values */ - public Map extensions = new HashMap(); + public Map extensions = new HashMap<>(); @Override public String toString() { diff --git a/framework/src/play/libs/Time.java b/framework/src/play/libs/Time.java index ffd550e707..f6ebfc51db 100644 --- a/framework/src/play/libs/Time.java +++ b/framework/src/play/libs/Time.java @@ -6,23 +6,23 @@ /** * Time utils - * + * * Provides a parser for time expression. *

    * Time expressions provide the ability to specify complex time combinations * such as "2d", "1w2d3h10s" or "2d4h10s". *

    - * + * */ public class Time { - private final static Pattern p = Pattern.compile("(([0-9]+?)((d|h|mi|min|mn|s)))+?"); - private final static Integer MINUTE = 60; - private final static Integer HOUR = 60 * MINUTE; - private final static Integer DAY = 24 * HOUR; + private static final Pattern p = Pattern.compile("(([0-9]+?)((d|h|mi|min|mn|s)))+?"); + private static final Integer MINUTE = 60; + private static final Integer HOUR = 60 * MINUTE; + private static final Integer DAY = 24 * HOUR; /** * Parse a duration - * + * * @param duration * 3h, 2mn, 7s or combination 2d4h10s, 1w2d3h10s * @return The number of seconds @@ -32,7 +32,7 @@ public static int parseDuration(String duration) { return 30 * DAY; } - final Matcher matcher = p.matcher(duration); + Matcher matcher = p.matcher(duration); int seconds = 0; if (!matcher.matches()) { throw new IllegalArgumentException("Invalid duration pattern : " + duration); @@ -56,7 +56,7 @@ public static int parseDuration(String duration) { /** * Parse a CRON expression - * + * * @param cron * The CRON String * @return The next Date that satisfy the expression @@ -72,7 +72,7 @@ public static Date parseCRONExpression(String cron) { /** * Compute the number of milliseconds between the next valid date and the * one after - * + * * @param cron * The CRON String * @return the number of milliseconds between the next valid date and the @@ -85,7 +85,7 @@ public static long cronInterval(String cron) { /** * Compute the number of milliseconds between the next valid date and the * one after - * + * * @param cron * The CRON String * @param date diff --git a/framework/src/play/libs/WS.java b/framework/src/play/libs/WS.java index d3ca56604c..3712449d9a 100644 --- a/framework/src/play/libs/WS.java +++ b/framework/src/play/libs/WS.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.InputStream; +import java.io.StringReader; import java.net.URI; import java.net.URLEncoder; import java.util.Arrays; @@ -12,10 +13,14 @@ import java.util.Map; import javax.xml.parsers.DocumentBuilder; + import org.apache.commons.lang.NotImplementedException; import org.w3c.dom.Document; import org.xml.sax.InputSource; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + import play.Logger; import play.Play; import play.PlayPlugin; @@ -26,27 +31,27 @@ import play.mvc.Http; import play.mvc.Http.Header; import play.utils.HTTP; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; /** * Simple HTTP client to make webservices requests. * - *

    + *

    * Get latest BBC World news as a RSS content + * *

    - *    HttpResponse response = WS.url("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml").get();
    - *    Document xmldoc = response.getXml();
    - *    // the real pain begins here...
    + * HttpResponse response = WS.url("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml").get();
    + * Document xmldoc = response.getXml();
    + * // the real pain begins here...
      * 
    - *

    + *

    * * Search what Yahoo! thinks of google (starting from the 30th result). + * *

    - *    HttpResponse response = WS.url("http://search.yahoo.com/search?p=%s&pstart=1&b=%s", "Google killed me", "30").get();
    - *    if( response.getStatus() == 200 ) {
    - *       html = response.getString();
    - *    }
    + * HttpResponse response = WS.url("http://search.yahoo.com/search?p=%s&pstart=1&b=%s", "Google killed me", "30").get();
    + * if (response.getStatus() == 200) {
    + *     html = response.getString();
    + * }
      * 
    */ public class WS extends PlayPlugin { @@ -58,17 +63,14 @@ public enum Scheme { } /** - * Singleton configured with default encoding - this one is used - * when calling static method on WS. + * Singleton configured with default encoding - this one is used when calling static method on WS. */ private static WSWithEncoding wsWithDefaultEncoding; /** - * Internal class exposing all the methods previously exposed by WS. - * This impl has information about encoding. - * When calling original static methos on WS, then a singleton of - * WSWithEncoding is called - configured with default encoding. - * This makes this encoding-enabling backward compatible + * Internal class exposing all the methods previously exposed by WS. This impl has information about encoding. When + * calling original static methods on WS, then a singleton of WSWithEncoding is called - configured with default + * encoding. This makes this encoding-enabling backward compatible */ public static class WSWithEncoding { public final String encoding; @@ -78,17 +80,21 @@ public WSWithEncoding(String encoding) { } /** - * Use thos method to get an instance to WS with diferent encoding - * @param newEncoding the encoding to use in the communication + * Use this method to get an instance to WS with different encoding + * + * @param newEncoding + * the encoding to use in the communication * @return a new instance of WS with specified encoding */ - public WSWithEncoding withEncoding(String newEncoding ) { - return new WSWithEncoding( newEncoding ); + public WSWithEncoding withEncoding(String newEncoding) { + return new WSWithEncoding(newEncoding); } /** * URL-encode a string to be used as a query string parameter. - * @param part string to encode + * + * @param part + * string to encode * @return url-encoded string */ public String encode(String part) { @@ -100,9 +106,11 @@ public String encode(String part) { } /** - * Build a WebService Request with the given URL. - * This object support chaining style programming for adding params, file, headers to requests. - * @param url of the request + * Build a WebService Request with the given URL. This object support chaining style programming for adding + * params, file, headers to requests. + * + * @param url + * of the request * @return a WSRequest on which you can add params, file headers using a chaining style programming. */ public WSRequest url(String url) { @@ -111,11 +119,13 @@ public WSRequest url(String url) { } /** - * Build a WebService Request with the given URL. - * This constructor will format url using params passed in arguments. - * This object support chaining style programming for adding params, file, headers to requests. - * @param url to format using the given params. - * @param params the params passed to format the URL. + * Build a WebService Request with the given URL. This constructor will format url using params passed in + * arguments. This object support chaining style programming for adding params, file, headers to requests. + * + * @param url + * to format using the given params. + * @param params + * the params passed to format the URL. * @return a WSRequest on which you can add params, file headers using a chaining style programming. */ public WSRequest url(String url, String... params) { @@ -130,13 +140,15 @@ public WSRequest url(String url, String... params) { /** * Use thos method to get an instance to WS with diferent encoding - * @param encoding the encoding to use in the communication + * + * @param encoding + * the encoding to use in the communication * @return a new instance of WS with specified encoding */ - public static WSWithEncoding withEncoding(String encoding ) { + public static WSWithEncoding withEncoding(String encoding) { return wsWithDefaultEncoding.withEncoding(encoding); } - + @Override public void onApplicationStop() { if (wsImpl != null) { @@ -152,8 +164,9 @@ public void onApplicationStart() { } - private synchronized static void init() { - if (wsImpl != null) return; + private static synchronized void init() { + if (wsImpl != null) + return; String implementation = Play.configuration.getProperty("webservice", "async"); if (implementation.equals("urlfetch")) { wsImpl = new WSUrlFetch(); @@ -167,30 +180,33 @@ private synchronized static void init() { wsImpl = new WSAsync(); } else { try { - wsImpl = (WSImpl)Play.classloader.loadClass(implementation).newInstance(); + wsImpl = (WSImpl) Play.classloader.loadClass(implementation).newInstance(); if (Logger.isTraceEnabled()) { Logger.trace("Using the class:" + implementation + " for web service"); } } catch (Exception e) { - throw new RuntimeException("Unable to load the class: " + implementation + " for web service"); + throw new RuntimeException("Unable to load the class: " + implementation + " for web service", e); } } } /** * URL-encode a string to be used as a query string parameter. - * @param part string to encode + * + * @param part + * string to encode * @return url-encoded string */ public static String encode(String part) { return wsWithDefaultEncoding.encode(part); } - /** - * Build a WebService Request with the given URL. - * This object support chaining style programming for adding params, file, headers to requests. - * @param url of the request + * Build a WebService Request with the given URL. This object support chaining style programming for adding params, + * file, headers to requests. + * + * @param url + * of the request * @return a WSRequest on which you can add params, file headers using a chaining style programming. */ public static WSRequest url(String url) { @@ -198,11 +214,13 @@ public static WSRequest url(String url) { } /** - * Build a WebService Request with the given URL. - * This constructor will format url using params passed in arguments. + * Build a WebService Request with the given URL. This constructor will format url using params passed in arguments. * This object support chaining style programming for adding params, file, headers to requests. - * @param url to format using the given params. - * @param params the params passed to format the URL. + * + * @param url + * to format using the given params. + * @param params + * the params passed to format the URL. * @return a WSRequest on which you can add params, file headers using a chaining style programming. */ public static WSRequest url(String url, String... params) { @@ -211,23 +229,38 @@ public static WSRequest url(String url, String... params) { public interface WSImpl { public WSRequest newRequest(String url, String encoding); + public void stop(); } - public static abstract class WSRequest { + public abstract static class WSRequest { public String url; + + /** + * The virtual host this request will use + */ + public String virtualHost; public final String encoding; public String username; public String password; public Scheme scheme; + + /** + * The body of this request + */ public Object body; public FileParam[] fileParams; - public Map headers = new HashMap(); - public Map parameters = new LinkedHashMap(); + public Map headers = new HashMap<>(); + public Map parameters = new LinkedHashMap<>(); public String mimeType; + + /** + * Sets whether redirects (301, 302) should be followed automatically + */ public boolean followRedirects = true; + /** - * timeout: value in seconds + * Timeout: value in seconds */ public Integer timeout = 60; @@ -248,9 +281,23 @@ public WSRequest(String url, String encoding) { this.encoding = encoding; } + /** + * Sets the virtual host to use in this request + * + * @param virtualHost + * The given virtual host + * @return the WSRequest + */ + public WSRequest withVirtualHost(String virtualHost) { + this.virtualHost = virtualHost; + return this; + } + /** * Add a MimeType to the web service request. + * * @param mimeType + * the given mimeType * @return the WSRequest for chaining. */ public WSRequest mimeType(String mimeType) { @@ -259,10 +306,14 @@ public WSRequest mimeType(String mimeType) { } /** - * define client authentication for a server host - * provided credentials will be used during the request + * Define client authentication for a server host provided credentials will be used during the request + * * @param username + * Login * @param password + * Password + * @param scheme + * The given Scheme * @return the WSRequest for chaining. */ public WSRequest authenticate(String username, String password, Scheme scheme) { @@ -273,11 +324,13 @@ public WSRequest authenticate(String username, String password, Scheme scheme) { } /** - * define client authentication for a server host - * provided credentials will be used during the request - * the basic scheme will be used + * define client authentication for a server host provided credentials will be used during the request the basic + * scheme will be used + * * @param username + * Login * @param password + * Password * @return the WSRequest for chaining. */ public WSRequest authenticate(String username, String password) { @@ -285,7 +338,15 @@ public WSRequest authenticate(String username, String password) { } /** - * Sign the request for do a call to a server protected by oauth + * Sign the request for do a call to a server protected by OAuth + * + * @param oauthInfo + * OAuth Information + * @param token + * The OAuth token + * @param secret + * The secret key + * * @return the WSRequest for chaining. */ public WSRequest oauth(ServiceInfo oauthInfo, String token, String secret) { @@ -302,6 +363,9 @@ public WSRequest oauth(ServiceInfo oauthInfo, OAuth.TokenPair oauthTokens) { /** * Indicate if the WS should continue when hitting a 301 or 302 + * + * @param value + * Indicate if follow or not follow redirects * @return the WSRequest for chaining. */ public WSRequest followRedirects(boolean value) { @@ -310,9 +374,11 @@ public WSRequest followRedirects(boolean value) { } /** - * Set the value of the request timeout, i.e. the number of seconds before cutting the - * connection - default to 60 seconds - * @param timeout the timeout value, e.g. "30s", "1min" + * Set the value of the request timeout, i.e. the number of seconds before cutting the connection - default to + * 60 seconds + * + * @param timeout + * the timeout value, e.g. "30s", "1min" * @return the WSRequest for chaining */ public WSRequest timeout(String timeout) { @@ -322,7 +388,9 @@ public WSRequest timeout(String timeout) { /** * Add files to request. This will only work with POST or PUT. + * * @param files + * list of files * @return the WSRequest for chaining. */ public WSRequest files(File... files) { @@ -332,7 +400,9 @@ public WSRequest files(File... files) { /** * Add fileParams aka File and Name parameter to the request. This will only work with POST or PUT. + * * @param fileParams + * The fileParams list * @return the WSRequest for chaining. */ public WSRequest files(FileParam... fileParams) { @@ -342,7 +412,9 @@ public WSRequest files(FileParam... fileParams) { /** * Add the given body to the request. + * * @param body + * The request body * @return the WSRequest for chaining. */ public WSRequest body(Object body) { @@ -352,19 +424,25 @@ public WSRequest body(Object body) { /** * Add a header to the request - * @param name header name - * @param value header value + * + * @param name + * header name + * @param value + * header value * @return the WSRequest for chaining. */ public WSRequest setHeader(String name, String value) { - this.headers.put( HTTP.fixCaseForHttpHeader(name), value); + this.headers.put(HTTP.fixCaseForHttpHeader(name), value); return this; } /** * Add a parameter to the request - * @param name parameter name - * @param value parameter value + * + * @param name + * parameter name + * @param value + * parameter value * @return the WSRequest for chaining. */ public WSRequest setParameter(String name, String value) { @@ -379,7 +457,9 @@ public WSRequest setParameter(String name, Object value) { /** * Use the provided headers when executing request. + * * @param headers + * The request headers * @return the WSRequest for chaining. */ public WSRequest headers(Map headers) { @@ -388,9 +468,13 @@ public WSRequest headers(Map headers) { } /** - * Add parameters to request. - * If POST or PUT, parameters are passed in body using x-www-form-urlencoded if alone, or form-data if there is files too. - * For any other method, those params are appended to the queryString. + * Add parameters to request. If POST or PUT, parameters are passed in body using x-www-form-urlencoded if + * alone, or form-data if there is files too. For any other method, those params are appended to the + * queryString. + * + * @param parameters + * The request parameters + * * @return the WSRequest for chaining. */ public WSRequest params(Map parameters) { @@ -399,9 +483,13 @@ public WSRequest params(Map parameters) { } /** - * Add parameters to request. - * If POST or PUT, parameters are passed in body using x-www-form-urlencoded if alone, or form-data if there is files too. - * For any other method, those params are appended to the queryString. + * Add parameters to request. If POST or PUT, parameters are passed in body using x-www-form-urlencoded if + * alone, or form-data if there is files too. For any other method, those params are appended to the + * queryString. + * + * @param parameters + * The request parameters + * * @return the WSRequest for chaining. */ public WSRequest setParameters(Map parameters) { @@ -409,64 +497,136 @@ public WSRequest setParameters(Map parameters) { return this; } - /** Execute a GET request synchronously. */ + /** + * Execute a GET request synchronously. + * + * @return The HTTP response + */ public abstract HttpResponse get(); - /** Execute a GET request asynchronously. */ + /** + * Execute a GET request asynchronously. + * + * @return The HTTP response + */ public Promise getAsync() { throw new NotImplementedException(); } - /** Execute a POST request.*/ + /** + * Execute a PATCH request. + * + * @return The HTTP response + */ + public abstract HttpResponse patch(); + + /** + * Execute a PATCH request asynchronously. + * + * @return The HTTP response + */ + public Promise patchAsync() { + throw new NotImplementedException(); + } + + /** + * Execute a POST request. + * + * @return The HTTP response + */ public abstract HttpResponse post(); - /** Execute a POST request asynchronously.*/ + /** + * Execute a POST request asynchronously. + * + * @return The HTTP response + */ public Promise postAsync() { throw new NotImplementedException(); } - /** Execute a PUT request.*/ + /** + * Execute a PUT request. + * + * @return The HTTP response + */ public abstract HttpResponse put(); - /** Execute a PUT request asynchronously.*/ + /** + * Execute a PUT request asynchronously. + * + * @return The HTTP response + */ public Promise putAsync() { throw new NotImplementedException(); } - /** Execute a DELETE request.*/ + /** + * Execute a DELETE request. + * + * @return The HTTP response + */ public abstract HttpResponse delete(); - /** Execute a DELETE request asynchronously.*/ + /** + * Execute a DELETE request asynchronously. + * + * @return The HTTP response + */ public Promise deleteAsync() { throw new NotImplementedException(); } - /** Execute a OPTIONS request.*/ + /** + * Execute a OPTIONS request. + * + * @return The HTTP response + */ public abstract HttpResponse options(); - /** Execute a OPTIONS request asynchronously.*/ + /** + * Execute a OPTIONS request asynchronously. + * + * @return The HTTP response + */ public Promise optionsAsync() { throw new NotImplementedException(); } - /** Execute a HEAD request.*/ + /** + * Execute a HEAD request. + * + * @return The HTTP response + */ public abstract HttpResponse head(); - /** Execute a HEAD request asynchronously.*/ + /** + * Execute a HEAD request asynchronously. + * + * @return The HTTP response + */ public Promise headAsync() { throw new NotImplementedException(); } - /** Execute a TRACE request.*/ + /** + * Execute a TRACE request. + * + * @return The HTTP response + */ public abstract HttpResponse trace(); - /** Execute a TRACE request asynchronously.*/ + /** + * Execute a TRACE request asynchronously. + * + * @return The HTTP response + */ public Promise traceAsync() { throw new NotImplementedException(); } - + protected String basicAuthHeader() { - return "Basic " + Codec.encodeBASE64(this.username + ":" + this.password); + return "Basic " + Codec.encodeBASE64(this.username + ":" + this.password); } protected String encode(String part) { @@ -527,18 +687,20 @@ public static FileParam[] getFileParams(File[] files) { /** * An HTTP response wrapper */ - public static abstract class HttpResponse { + public abstract static class HttpResponse { private String _encoding = null; /** * the HTTP status code + * * @return the status code of the http response */ public abstract Integer getStatus(); /** * The HTTP status text + * * @return the status text of the http response */ public abstract String getStatusText(); @@ -552,25 +714,26 @@ public boolean success() { /** * The http response content type + * * @return the content type of the http response */ public String getContentType() { - return getHeader("content-type") !=null ? getHeader("content-type") : getHeader("Content-Type"); + return getHeader("content-type") != null ? getHeader("content-type") : getHeader("Content-Type"); } public String getEncoding() { // Have we already parsed it? - if( _encoding != null ) { + if (_encoding != null) { return _encoding; } // no! must parse it and remember String contentType = getContentType(); - if( contentType == null ) { + if (contentType == null) { _encoding = Play.defaultWebEncoding; } else { - HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType( contentType ); - if( contentTypeEncoding.encoding == null ) { + HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType(contentType); + if (contentTypeEncoding.encoding == null) { _encoding = Play.defaultWebEncoding; } else { _encoding = contentTypeEncoding.encoding; @@ -586,20 +749,23 @@ public String getEncoding() { /** * Parse and get the response body as a {@link Document DOM document} + * * @return a DOM document */ public Document getXml() { - return getXml( getEncoding() ); + return getXml(getEncoding()); } /** * parse and get the response body as a {@link Document DOM document} - * @param encoding xml charset encoding + * + * @param encoding + * xml charset encoding * @return a DOM document */ public Document getXml(String encoding) { try { - InputSource source = new InputSource(getStream()); + InputSource source = new InputSource(new StringReader(getString())); source.setEncoding(encoding); DocumentBuilder builder = XML.newDocumentBuilder(); return builder.parse(source); @@ -610,34 +776,32 @@ public Document getXml(String encoding) { /** * get the response body as a string + * * @return the body of the http response */ - public String getString() { - return IO.readContentAsString(getStream(), getEncoding()); - } + public abstract String getString(); /** * get the response body as a string - * @param encoding string charset encoding + * + * @param encoding + * string charset encoding * @return the body of the http response */ - public String getString(String encoding) { - return IO.readContentAsString(getStream(), encoding); - } - + public abstract String getString(String encoding); /** * Parse the response string as a query string. - * @return The parameters as a Map. Return an empty map if the response - * is not formed as a query string. + * + * @return The parameters as a Map. Return an empty map if the response is not formed as a query string. */ public Map getQueryString() { - Map result = new HashMap(); + Map result = new HashMap<>(); String body = getString(); - for (String entry: body.split("&")) { + for (String entry : body.split("&")) { int pos = entry.indexOf("="); if (pos > -1) { - result.put(entry.substring(0,pos), entry.substring(pos+1)); + result.put(entry.substring(0, pos), entry.substring(pos + 1)); } else { result.put(entry, ""); } @@ -647,12 +811,18 @@ public Map getQueryString() { /** * get the response as a stream + *

    + * + this method can only be called onced because async implementation does not allow it to be called + multiple + * times + + *

    + * * @return an inputstream */ public abstract InputStream getStream(); /** * get the response body as a {@link com.google.gson.JsonElement} + * * @return the json response */ public JsonElement getJson() { diff --git a/framework/src/play/libs/XML.java b/framework/src/play/libs/XML.java index b0432a1d16..496d107d35 100644 --- a/framework/src/play/libs/XML.java +++ b/framework/src/play/libs/XML.java @@ -1,6 +1,10 @@ package play.libs; -import java.io.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; import java.security.Key; import java.security.Provider; import java.security.interfaces.RSAPrivateKey; @@ -67,7 +71,9 @@ public static DocumentBuilder newDocumentBuilder() { /** * Serialize to XML String - * @param document The DOM document + * + * @param document + * The DOM document * @return The XML String */ public static String serialize(Document document) { @@ -79,14 +85,16 @@ public static String serialize(Document document) { StreamResult streamResult = new StreamResult(writer); transformer.transform(domSource, streamResult); } catch (TransformerException e) { - throw new RuntimeException( - "Error when serializing XML document.", e); + throw new RuntimeException("Error when serializing XML document.", e); } return writer.toString(); } /** * Parse an XML file to DOM + * + * @param file + * The XML file * @return null if an error occurs during parsing. * */ @@ -103,6 +111,9 @@ public static Document getDocument(File file) { /** * Parse an XML string content to DOM + * + * @param xml + * The XML string * @return null if an error occurs during parsing. */ public static Document getDocument(String xml) { @@ -119,6 +130,9 @@ public static Document getDocument(String xml) { /** * Parse an XML coming from an input stream to DOM + * + * @param stream + * The XML stream * @return null if an error occurs during parsing. */ public static Document getDocument(InputStream stream) { @@ -134,12 +148,15 @@ public static Document getDocument(InputStream stream) { /** * Check the xmldsig signature of the XML document. - * @param document the document to test - * @param publicKey the public key corresponding to the key pair the document was signed with + * + * @param document + * the document to test + * @param publicKey + * the public key corresponding to the key pair the document was signed with * @return true if a correct signature is present, false otherwise */ public static boolean validSignature(Document document, Key publicKey) { - Node signatureNode = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0); + Node signatureNode = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0); KeySelector keySelector = KeySelector.singletonKeySelector(publicKey); try { @@ -157,9 +174,13 @@ public static boolean validSignature(Document document, Key publicKey) { /** * Sign the XML document using xmldsig. - * @param document the document to sign; it will be modified by the method. - * @param publicKey the public key from the key pair to sign the document. - * @param privateKey the private key from the key pair to sign the document. + * + * @param document + * the document to sign; it will be modified by the method. + * @param publicKey + * the public key from the key pair to sign the document. + * @param privateKey + * the private key from the key pair to sign the document. * @return the signed document for chaining. */ public static Document sign(Document document, RSAPublicKey publicKey, RSAPrivateKey privateKey) { @@ -167,16 +188,11 @@ public static Document sign(Document document, RSAPublicKey publicKey, RSAPrivat KeyInfoFactory keyInfoFactory = fac.getKeyInfoFactory(); try { - Reference ref =fac.newReference( - "", - fac.newDigestMethod(DigestMethod.SHA1, null), - Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), - null, - null); - SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, - (C14NMethodParameterSpec) null), - fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), - Collections.singletonList(ref)); + Reference ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA1, null), + Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null, null); + SignedInfo si = fac.newSignedInfo( + fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), + fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), Collections.singletonList(ref)); DOMSignContext dsc = new DOMSignContext(privateKey, document.getDocumentElement()); KeyValue keyValue = keyInfoFactory.newKeyValue(publicKey); KeyInfo ki = keyInfoFactory.newKeyInfo(Collections.singletonList(keyValue)); diff --git a/framework/src/play/libs/XPath.java b/framework/src/play/libs/XPath.java index 26d6e61c69..dd5b414815 100644 --- a/framework/src/play/libs/XPath.java +++ b/framework/src/play/libs/XPath.java @@ -13,12 +13,16 @@ public class XPath { /** - * Select all nodes that are selected by this XPath expression. If multiple nodes match, - * multiple nodes will be returned. Nodes will be returned in document-order, + * Select all nodes that are selected by this XPath expression. If multiple nodes match, multiple nodes will be + * returned. Nodes will be returned in document-order, + * * @param path + * Path expression * @param node - * @param namespaces Namespaces that need to be available in the xpath, where the key is the - * prefix and the value the namespace URI + * The node object + * @param namespaces + * Namespaces that need to be available in the xpath, where the key is the prefix and the value the + * namespace URI * @return Nodes in document-order */ @SuppressWarnings("unchecked") @@ -31,10 +35,13 @@ public static List selectNodes(String path, Object node, Map selectNodes(String path, Object node) { @@ -59,8 +66,14 @@ public static Node selectNode(String path, Object node) { /** * Return the text of a node, or the value of an attribute - * @param path the XPath to execute - * @param node the node, node-set or Context object for evaluation. This value can be null. + * + * @param path + * the XPath to execute + * @param node + * the node, node-set or Context object for evaluation. This value can be null. + * @param namespaces + * The name spaces + * @return The text of a node */ public static String selectText(String path, Object node, Map namespaces) { try { @@ -82,8 +95,12 @@ public static String selectText(String path, Object node, Map na /** * Return the text of a node, or the value of an attribute - * @param path the XPath to execute - * @param node the node, node-set or Context object for evaluation. This value can be null. + * + * @param path + * the XPath to execute + * @param node + * the node, node-set or Context object for evaluation. This value can be null. + * @return The text of a node */ public static String selectText(String path, Object node) { return selectText(path, node, null); @@ -92,7 +109,7 @@ public static String selectText(String path, Object node) { private static DOMXPath getDOMXPath(String path, Map namespaces) throws Exception { DOMXPath xpath = new DOMXPath(path); if (namespaces != null) { - for (String prefix: namespaces.keySet()) { + for (String prefix : namespaces.keySet()) { xpath.addNamespace(prefix, namespaces.get(prefix)); } } diff --git a/framework/src/play/libs/mail/test/LegacyMockMailSystem.java b/framework/src/play/libs/mail/test/LegacyMockMailSystem.java index c886a017c9..77c4c6921a 100644 --- a/framework/src/play/libs/mail/test/LegacyMockMailSystem.java +++ b/framework/src/play/libs/mail/test/LegacyMockMailSystem.java @@ -1,45 +1,38 @@ package play.libs.mail.test; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.Future; - -import javax.mail.BodyPart; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Part; -import javax.mail.Session; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; - import org.apache.commons.lang.StringUtils; import org.apache.commons.mail.Email; - import play.Logger; import play.libs.Mail; import play.libs.mail.MailSystem; import play.utils.ImmediateFuture; +import javax.mail.*; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Future; + /** * Just kept for compatibility reasons, use test double substitution mechanism instead. * * @see Mail#Mock * @see Mail#useMailSystem(MailSystem) - * @author Andreas Simon + * @author Andreas Simon <a.simon@quagilis.de> */ public class LegacyMockMailSystem implements MailSystem { // Has to remain static to preserve the possibility of testing mail sending within Selenium tests - static Map emails = new HashMap(); + static Map emails = new HashMap<>(); @Override public Future sendMessage(Email email) { try { - final StringBuffer content = new StringBuffer(); + StringBuilder content = new StringBuilder(); Properties props = new Properties(); props.put("mail.smtp.host", "myfakesmtpserver.com"); @@ -56,21 +49,21 @@ public Future sendMessage(Email email) { content.append("From Mock Mailer\n\tNew email received by"); - content.append("\n\tFrom: " + email.getFromAddress().getAddress()); - content.append("\n\tReplyTo: " + ((InternetAddress) email.getReplyToAddresses().get(0)).getAddress()); + content.append("\n\tFrom: ").append(email.getFromAddress().getAddress()); + content.append("\n\tReplyTo: ").append(email.getReplyToAddresses().get(0).getAddress()); addAddresses(content, "To", email.getToAddresses()); addAddresses(content, "Cc", email.getCcAddresses()); addAddresses(content, "Bcc", email.getBccAddresses()); - content.append("\n\tSubject: " + email.getSubject()); - content.append("\n\t" + body); + content.append("\n\tSubject: ").append(email.getSubject()); + content.append("\n\t").append(body); content.append("\n"); Logger.info(content.toString()); for (Object add : email.getToAddresses()) { - content.append(", " + add.toString()); + content.append(", ").append(add); emails.put(((InternetAddress) add).getAddress(), content.toString()); } @@ -117,18 +110,18 @@ private static String getContent(Part message) throws MessagingException, } - private static void addAddresses(final StringBuffer content, - String header, List ccAddresses) { + private static void addAddresses(StringBuilder content, + String header, List ccAddresses) { if (ccAddresses != null && !ccAddresses.isEmpty()) { - content.append("\n\t" + header + ": "); + content.append("\n\t").append(header).append(": "); for (Object add : ccAddresses) { - content.append(add.toString() + ", "); + content.append(add).append(", "); } removeTheLastComma(content); } } - private static void removeTheLastComma(final StringBuffer content) { + private static void removeTheLastComma(StringBuilder content) { content.delete(content.length() - 2, content.length()); } diff --git a/framework/src/play/libs/ws/WSAsync.java b/framework/src/play/libs/ws/WSAsync.java index 654ad2e687..74591e23e1 100644 --- a/framework/src/play/libs/ws/WSAsync.java +++ b/framework/src/play/libs/ws/WSAsync.java @@ -1,23 +1,40 @@ package play.libs.ws; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import com.ning.http.client.*; +import javax.net.ssl.SSLContext; + +import org.apache.commons.lang.NotImplementedException; + +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder; +import com.ning.http.client.AsyncHttpClientConfig; +import com.ning.http.client.AsyncHttpClientConfig.Builder; +import com.ning.http.client.ProxyServer; +import com.ning.http.client.Realm.AuthScheme; +import com.ning.http.client.Realm.RealmBuilder; +import com.ning.http.client.Response; +import com.ning.http.client.multipart.ByteArrayPart; +import com.ning.http.client.multipart.FilePart; +import com.ning.http.client.multipart.Part; + import oauth.signpost.AbstractOAuthConsumer; import oauth.signpost.exception.OAuthCommunicationException; import oauth.signpost.exception.OAuthExpectationFailedException; import oauth.signpost.exception.OAuthMessageSignerException; import oauth.signpost.http.HttpRequest; - -import org.apache.commons.lang.NotImplementedException; - import play.Logger; import play.Play; import play.libs.F.Promise; @@ -28,35 +45,26 @@ import play.libs.WS.WSRequest; import play.mvc.Http.Header; -import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder; -import com.ning.http.client.AsyncHttpClientConfig.Builder; -import com.ning.http.client.FilePart; -import com.ning.http.client.PerRequestConfig; -import com.ning.http.client.ProxyServer; -import com.ning.http.client.Realm.AuthScheme; -import com.ning.http.client.Realm.RealmBuilder; -import com.ning.http.client.Response; - -import javax.net.ssl.*; - /** * Simple HTTP client to make webservices requests. * - *

    + *

    * Get latest BBC World news as a RSS content + * *

    - *    HttpResponse response = WS.url("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml").get();
    - *    Document xmldoc = response.getXml();
    - *    // the real pain begins here...
    + * HttpResponse response = WS.url("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml").get();
    + * Document xmldoc = response.getXml();
    + * // the real pain begins here...
      * 
    - *

    + *

    * * Search what Yahoo! thinks of google (starting from the 30th result). + * *

    - *    HttpResponse response = WS.url("http://search.yahoo.com/search?p=%s&pstart=1&b=%s", "Google killed me", "30").get();
    - *    if( response.getStatus() == 200 ) {
    - *       html = response.getString();
    - *    }
    + * HttpResponse response = WS.url("http://search.yahoo.com/search?p=%s&pstart=1&b=%s", "Google killed me", "30").get();
    + * if (response.getStatus() == 200) {
    + *     html = response.getString();
    + * }
      * 
    */ public class WSAsync implements WSImpl { @@ -81,12 +89,14 @@ public WSAsync() { try { proxyPortInt = Integer.parseInt(proxyPort); } catch (NumberFormatException e) { - Logger.error("Cannot parse the proxy port property '%s'. Check property http.proxyPort either in System configuration or in Play config file.", proxyPort); + Logger.error(e, + "Cannot parse the proxy port property '%s'. Check property http.proxyPort either in System configuration or in Play config file.", + proxyPort); throw new IllegalStateException("WS proxy is misconfigured -- check the logs for details"); } ProxyServer proxy = new ProxyServer(proxyHost, proxyPortInt, proxyUser, proxyPassword); if (nonProxyHosts != null) { - final String[] strings = nonProxyHosts.split("\\|"); + String[] strings = nonProxyHosts.split("\\|"); for (String uril : strings) { proxy.addNonProxyHost(uril); } @@ -110,40 +120,42 @@ public WSAsync() { } } // when using raw urls, AHC does not encode the params in url. - // this means we can/must encode it(with correct encoding) before passing it to AHC - confBuilder.setUseRawUrl(true); + // this means we can/must encode it(with correct encoding) before + // passing it to AHC + confBuilder.setDisableUrlEncodingForBoundedRequests(true); httpClient = new AsyncHttpClient(confBuilder.build()); } + @Override public void stop() { Logger.trace("Releasing http client connections..."); httpClient.close(); } + @Override public WSRequest newRequest(String url, String encoding) { return new WSAsyncRequest(url, encoding); } - - public class WSAsyncRequest extends WSRequest { protected String type = null; private String generatedContentType = null; - protected WSAsyncRequest(String url, String encoding) { super(url, encoding); } /** - * Returns the url but removed the queryString-part of it - * The QueryString-info is later added with addQueryString() + * Returns the URL but removed the queryString-part of it The QueryString-info is later added with + * addQueryString() + * + * @return The URL without the queryString-part */ protected String getUrlWithoutQueryString() { int i = url.indexOf('?'); - if ( i > 0) { - return url.substring(0,i); + if (i > 0) { + return url.substring(0, i); } else { return url; } @@ -151,56 +163,65 @@ protected String getUrlWithoutQueryString() { /** * Adds the queryString-part of the url to the BoundRequestBuilder + * + * @param requestBuilder + * : The request buider to add the queryString-part */ protected void addQueryString(BoundRequestBuilder requestBuilder) { - // AsyncHttpClient is by default encoding everything in utf-8 so for us to be able to use - // different encoding we have configured AHC to use raw urls. When using raw urls, - // AHC does not encode url and QueryParam with utf-8 - but there is another problem: - // If we send raw (none-encoded) url (with queryString) to AHC, it does not url-encode it, + // AsyncHttpClient is by default encoding everything in utf-8 so for + // us to be able to use + // different encoding we have configured AHC to use raw urls. When + // using raw urls, + // AHC does not encode url and QueryParam with utf-8 - but there is + // another problem: + // If we send raw (none-encoded) url (with queryString) to AHC, it + // does not url-encode it, // but transform all illegal chars to '?'. - // If we pre-encoded the url with QueryString before sending it to AHC, ahc will decode it, and then + // If we pre-encoded the url with QueryString before sending it to + // AHC, ahc will decode it, and then // later break it with '?'. - // This method basically does the same as RequestBuilderBase.buildUrl() except from destroying the + // This method basically does the same as + // RequestBuilderBase.buildUrl() except from destroying the // pre-encoding // does url contain query_string? int i = url.indexOf('?'); - if ( i > 0) { + if (i > 0) { try { // extract query-string-part - String queryPart = url.substring(i+1); + String queryPart = url.substring(i + 1); - // parse queryPart - and decode it... (it is going to be re-encoded later) - for( String param : queryPart.split("&")) { + // parse queryPart - and decode it... (it is going to be + // re-encoded later) + for (String param : queryPart.split("&")) { i = param.indexOf('='); String name; String value = null; - if ( i<=0) { + if (i <= 0) { // only a flag name = URLDecoder.decode(param, encoding); } else { - name = URLDecoder.decode(param.substring(0,i), encoding); - value = URLDecoder.decode(param.substring(i+1), encoding); + name = URLDecoder.decode(param.substring(0, i), encoding); + value = URLDecoder.decode(param.substring(i + 1), encoding); } if (value == null) { - requestBuilder.addQueryParameter(URLEncoder.encode(name, encoding), null); + requestBuilder.addQueryParam(URLEncoder.encode(name, encoding), null); } else { - requestBuilder.addQueryParameter(URLEncoder.encode(name, encoding), URLEncoder.encode(value, encoding)); + requestBuilder.addQueryParam(URLEncoder.encode(name, encoding), URLEncoder.encode(value, encoding)); } } } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Error parsing query-part of url",e); + throw new RuntimeException("Error parsing query-part of url", e); } } } - private BoundRequestBuilder prepareAll(BoundRequestBuilder requestBuilder) { checkFileBody(requestBuilder); addQueryString(requestBuilder); @@ -208,7 +229,6 @@ private BoundRequestBuilder prepareAll(BoundRequestBuilder requestBuilder) { return requestBuilder; } - public BoundRequestBuilder prepareGet() { return prepareAll(httpClient.prepareGet(getUrlWithoutQueryString())); } @@ -221,6 +241,10 @@ public BoundRequestBuilder prepareHead() { return prepareAll(httpClient.prepareHead(getUrlWithoutQueryString())); } + public BoundRequestBuilder preparePatch() { + return prepareAll(httpClient.preparePatch(getUrlWithoutQueryString())); + } + public BoundRequestBuilder preparePost() { return prepareAll(httpClient.preparePost(getUrlWithoutQueryString())); } @@ -253,8 +277,27 @@ public Promise getAsync() { return execute(prepareGet()); } + /** Execute a PATCH request. */ + @Override + public HttpResponse patch() { + this.type = "PATCH"; + sign(); + try { + return new HttpAsyncResponse(prepare(preparePatch()).execute().get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - /** Execute a POST request.*/ + /** Execute a PATCH request asynchronously. */ + @Override + public Promise patchAsync() { + this.type = "PATCH"; + sign(); + return execute(preparePatch()); + } + + /** Execute a POST request. */ @Override public HttpResponse post() { this.type = "POST"; @@ -266,7 +309,7 @@ public HttpResponse post() { } } - /** Execute a POST request asynchronously.*/ + /** Execute a POST request asynchronously. */ @Override public Promise postAsync() { this.type = "POST"; @@ -274,7 +317,7 @@ public Promise postAsync() { return execute(preparePost()); } - /** Execute a PUT request.*/ + /** Execute a PUT request. */ @Override public HttpResponse put() { this.type = "PUT"; @@ -285,14 +328,14 @@ public HttpResponse put() { } } - /** Execute a PUT request asynchronously.*/ + /** Execute a PUT request asynchronously. */ @Override public Promise putAsync() { this.type = "PUT"; return execute(preparePut()); } - /** Execute a DELETE request.*/ + /** Execute a DELETE request. */ @Override public HttpResponse delete() { this.type = "DELETE"; @@ -303,14 +346,14 @@ public HttpResponse delete() { } } - /** Execute a DELETE request asynchronously.*/ + /** Execute a DELETE request asynchronously. */ @Override public Promise deleteAsync() { this.type = "DELETE"; return execute(prepareDelete()); } - /** Execute a OPTIONS request.*/ + /** Execute a OPTIONS request. */ @Override public HttpResponse options() { this.type = "OPTIONS"; @@ -321,14 +364,14 @@ public HttpResponse options() { } } - /** Execute a OPTIONS request asynchronously.*/ + /** Execute a OPTIONS request asynchronously. */ @Override public Promise optionsAsync() { this.type = "OPTIONS"; return execute(prepareOptions()); } - /** Execute a HEAD request.*/ + /** Execute a HEAD request. */ @Override public HttpResponse head() { this.type = "HEAD"; @@ -339,21 +382,21 @@ public HttpResponse head() { } } - /** Execute a HEAD request asynchronously.*/ + /** Execute a HEAD request asynchronously. */ @Override public Promise headAsync() { this.type = "HEAD"; return execute(prepareHead()); } - /** Execute a TRACE request.*/ + /** Execute a TRACE request. */ @Override public HttpResponse trace() { this.type = "TRACE"; throw new NotImplementedException(); } - /** Execute a TRACE request asynchronously.*/ + /** Execute a TRACE request asynchronously. */ @Override public Promise traceAsync() { this.type = "TRACE"; @@ -376,35 +419,41 @@ private BoundRequestBuilder prepare(BoundRequestBuilder builder) { if (this.username != null && this.password != null && this.scheme != null) { AuthScheme authScheme; switch (this.scheme) { - case DIGEST: authScheme = AuthScheme.DIGEST; break; - case NTLM: authScheme = AuthScheme.NTLM; break; - case KERBEROS: authScheme = AuthScheme.KERBEROS; break; - case SPNEGO: authScheme = AuthScheme.SPNEGO; break; - case BASIC: authScheme = AuthScheme.BASIC; break; - default: throw new RuntimeException("Scheme " + this.scheme + " not supported by the UrlFetch WS backend."); + case DIGEST: + authScheme = AuthScheme.DIGEST; + break; + case NTLM: + authScheme = AuthScheme.NTLM; + break; + case KERBEROS: + authScheme = AuthScheme.KERBEROS; + break; + case SPNEGO: + authScheme = AuthScheme.SPNEGO; + break; + case BASIC: + authScheme = AuthScheme.BASIC; + break; + default: + throw new RuntimeException("Scheme " + this.scheme + " not supported by the UrlFetch WS backend."); } - builder.setRealm( - (new RealmBuilder()) - .setScheme(authScheme) - .setPrincipal(this.username) - .setPassword(this.password) - .setUsePreemptiveAuth(true) - .build() - ); - } - for (String key: this.headers.keySet()) { + builder.setRealm((new RealmBuilder()).setScheme(authScheme).setPrincipal(this.username).setPassword(this.password) + .setUsePreemptiveAuth(true).build()); + } + for (String key : this.headers.keySet()) { builder.addHeader(key, headers.get(key)); } builder.setFollowRedirects(this.followRedirects); - PerRequestConfig perRequestConfig = new PerRequestConfig(); - perRequestConfig.setRequestTimeoutInMs(this.timeout * 1000); - builder.setPerRequestConfig(perRequestConfig); + builder.setRequestTimeout(this.timeout * 1000); + if (this.virtualHost != null) { + builder.setVirtualHost(this.virtualHost); + } return builder; } private Promise execute(BoundRequestBuilder builder) { try { - final Promise smartFuture = new Promise(); + final Promise smartFuture = new Promise<>(); prepare(builder).execute(new AsyncCompletionHandler() { @Override public HttpResponse onCompleted(Response response) throws Exception { @@ -412,9 +461,11 @@ public HttpResponse onCompleted(Response response) throws Exception { smartFuture.invoke(httpResponse); return httpResponse; } + @Override public void onThrowable(Throwable t) { - // An error happened - must "forward" the exception to the one waiting for the result + // An error happened - must "forward" the exception to + // the one waiting for the result smartFuture.invokeWithException(t); } }); @@ -428,12 +479,10 @@ public void onThrowable(Throwable t) { private void checkFileBody(BoundRequestBuilder builder) { setResolvedContentType(null); if (this.fileParams != null) { - //could be optimized, we know the size of this array. + // could be optimized, we know the size of this array. for (int i = 0; i < this.fileParams.length; i++) { - builder.addBodyPart(new FilePart(this.fileParams[i].paramName, - this.fileParams[i].file, - MimeTypes.getMimeType(this.fileParams[i].file.getName()), - encoding)); + builder.addBodyPart(new FilePart(this.fileParams[i].paramName, this.fileParams[i].file, + MimeTypes.getMimeType(this.fileParams[i].file.getName()), Charset.forName(encoding))); } if (this.parameters != null) { try { @@ -443,38 +492,43 @@ private void checkFileBody(BoundRequestBuilder builder) { if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; for (Object v : values) { - Part part = new ByteArrayPart(key, null, v.toString().getBytes(encoding), "text/plain", encoding); - builder.addBodyPart( part ); + Part part = new ByteArrayPart(key, v.toString().getBytes(encoding), "text/plain", + Charset.forName(encoding), null); + builder.addBodyPart(part); } } else { - Part part = new ByteArrayPart(key, null, value.toString().getBytes(encoding), "text/plain", encoding); - builder.addBodyPart( part ); + Part part = new ByteArrayPart(key, value.toString().getBytes(encoding), "text/plain", + Charset.forName(encoding), null); + builder.addBodyPart(part); } } - } catch(UnsupportedEncodingException e) { + } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } - // Don't have to set content-type: AHC will automatically choose multipart - + // Don't have to set content-type: AHC will automatically choose + // multipart + return; } if (this.parameters != null && !this.parameters.isEmpty()) { boolean isPostPut = "POST".equals(this.type) || ("PUT".equals(this.type)); if (isPostPut) { - // Since AHC is hard-coded to encode to use UTF-8, we must build + // Since AHC is hard-coded to encode to use UTF-8, we must + // build // the content ourself.. StringBuilder sb = new StringBuilder(); for (String key : this.parameters.keySet()) { Object value = this.parameters.get(key); - if (value == null) continue; + if (value == null) + continue; if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; - for (Object v: values) { + for (Object v : values) { if (sb.length() > 0) { sb.append('&'); } @@ -483,7 +537,8 @@ private void checkFileBody(BoundRequestBuilder builder) { sb.append(encode(v.toString())); } } else { - // Since AHC is hard-coded to encode using UTF-8, we must build + // Since AHC is hard-coded to encode using UTF-8, we + // must build // the content ourself.. if (sb.length() > 0) { sb.append('&'); @@ -505,16 +560,17 @@ private void checkFileBody(BoundRequestBuilder builder) { } else { for (String key : this.parameters.keySet()) { Object value = this.parameters.get(key); - if (value == null) continue; + if (value == null) + continue; if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; - for (Object v: values) { + for (Object v : values) { // must encode it since AHC uses raw urls - builder.addQueryParameter(encode(key), encode(v.toString())); + builder.addQueryParam(encode(key), encode(v.toString())); } } else { // must encode it since AHC uses raw urls - builder.addQueryParameter(encode(key), encode(value.toString())); + builder.addQueryParam(encode(key), encode(value.toString())); } } setResolvedContentType("text/html; charset=" + encoding); @@ -525,7 +581,7 @@ private void checkFileBody(BoundRequestBuilder builder) { throw new RuntimeException("POST or PUT method with parameters AND body are not supported."); } if (this.body instanceof InputStream) { - builder.setBody((InputStream)this.body); + builder.setBody((InputStream) this.body); } else { try { byte[] bodyBytes = this.body.toString().getBytes(this.encoding); @@ -536,29 +592,28 @@ private void checkFileBody(BoundRequestBuilder builder) { } setResolvedContentType("text/html; charset=" + encoding); } - - if(this.mimeType != null) { + + if (this.mimeType != null) { // User has specified mimeType this.headers.put("Content-Type", this.mimeType); } } /** - * Sets the resolved Content-type - This is added as Content-type-header to AHC - * if ser has not specified Content-type or mimeType manually - * (Cannot add it directly to this.header since this cause problem - * when Request-object is used multiple times with first GET, then POST) + * Sets the resolved Content-type - This is added as Content-type-header to AHC if ser has not specified + * Content-type or mimeType manually (Cannot add it directly to this.header since this cause problem when + * Request-object is used multiple times with first GET, then POST) */ private void setResolvedContentType(String contentType) { generatedContentType = contentType; } /** - * If generatedContentType is present AND if Content-type header is not already present, - * add generatedContentType as Content-Type to headers in requestBuilder + * If generatedContentType is present AND if Content-type header is not already present, add + * generatedContentType as Content-Type to headers in requestBuilder */ private void addGeneratedContentType(BoundRequestBuilder requestBuilder) { - if (!headers.containsKey("Content-Type") && generatedContentType!=null) { + if (!headers.containsKey("Content-Type") && generatedContentType != null) { requestBuilder.addHeader("Content-Type", generatedContentType); } } @@ -573,15 +628,18 @@ public static class HttpAsyncResponse extends HttpResponse { private Response response; /** - * you shouldnt have to create an HttpResponse yourself + * You shouldn't have to create an HttpResponse yourself + * * @param response + * The given response */ public HttpAsyncResponse(Response response) { this.response = response; } /** - * the HTTP status code + * The HTTP status code + * * @return the status code of the http response */ @Override @@ -591,6 +649,7 @@ public Integer getStatus() { /** * the HTTP status text + * * @return the status text of the http response */ @Override @@ -606,15 +665,34 @@ public String getHeader(String key) { @Override public List
    getHeaders() { Map> hdrs = response.getHeaders(); - List
    result = new ArrayList
    (); - for (String key: hdrs.keySet()) { + List
    result = new ArrayList<>(); + for (String key : hdrs.keySet()) { result.add(new Header(key, hdrs.get(key))); } return result; } + @Override + public String getString() { + try { + return response.getResponseBody(getEncoding()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String getString(String encoding) { + try { + return response.getResponseBody(encoding); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * get the response as a stream + * * @return an inputstream */ @Override @@ -622,7 +700,10 @@ public InputStream getStream() { try { return response.getResponseBodyAsStream(); } catch (IllegalStateException e) { - return new ByteArrayInputStream(new byte[]{}); // Workaround AHC's bug on empty responses + return new ByteArrayInputStream(new byte[] {}); // Workaround + // AHC's bug on + // empty + // responses } catch (Exception e) { throw new RuntimeException(e); } @@ -646,11 +727,12 @@ protected HttpRequest wrap(Object request) { if (!(request instanceof WSRequest)) { throw new IllegalArgumentException("WSOAuthConsumer expects requests of type play.libs.WS.WSRequest"); } - return new WSRequestAdapter((WSRequest)request); + return new WSRequestAdapter((WSRequest) request); } - public WSRequest sign(WSRequest request, String method) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException { - WSRequestAdapter req = (WSRequestAdapter)wrap(request); + public WSRequest sign(WSRequest request, String method) + throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException { + WSRequestAdapter req = (WSRequestAdapter) wrap(request); req.setMethod(method); sign(req); return request; @@ -665,10 +747,12 @@ public WSRequestAdapter(WSRequest request) { this.request = request; } + @Override public Map getAllHeaders() { return request.headers; } + @Override public String getContentType() { return request.mimeType; } @@ -678,14 +762,17 @@ public Object unwrap() { return null; } + @Override public String getHeader(String name) { return request.headers.get(name); } + @Override public InputStream getMessagePayload() throws IOException { return null; } + @Override public String getMethod() { return this.method; } @@ -694,14 +781,17 @@ private void setMethod(String method) { this.method = method; } + @Override public String getRequestUrl() { return request.url; } + @Override public void setHeader(String name, String value) { request.setHeader(name, value); } + @Override public void setRequestUrl(String url) { request.url = url; } diff --git a/framework/src/play/libs/ws/WSSSLContext.java b/framework/src/play/libs/ws/WSSSLContext.java index 4a0e0df724..5291865a65 100644 --- a/framework/src/play/libs/ws/WSSSLContext.java +++ b/framework/src/play/libs/ws/WSSSLContext.java @@ -9,7 +9,7 @@ import java.security.cert.X509Certificate; public class WSSSLContext { - public static SSLContext getSslContext(final String keyStore, final String keyStorePass, Boolean CAValidation) { + public static SSLContext getSslContext(String keyStore, String keyStorePass, Boolean CAValidation) { SSLContext sslCTX = null; try { diff --git a/framework/src/play/libs/ws/WSUrlFetch.java b/framework/src/play/libs/ws/WSUrlFetch.java index 5dddf6c273..dc54c7eb4a 100644 --- a/framework/src/play/libs/ws/WSUrlFetch.java +++ b/framework/src/play/libs/ws/WSUrlFetch.java @@ -1,18 +1,5 @@ package play.libs.ws; -import oauth.signpost.OAuthConsumer; -import oauth.signpost.basic.DefaultOAuthConsumer; -import play.Logger; -import play.Play; -import play.libs.IO; -import play.libs.WS.HttpResponse; -import play.libs.WS.WSImpl; -import play.libs.WS.WSRequest; -import play.mvc.Http.Header; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -28,19 +15,36 @@ import java.util.List; import java.util.Map; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +import oauth.signpost.OAuthConsumer; +import oauth.signpost.basic.DefaultOAuthConsumer; +import play.Logger; +import play.Play; +import play.libs.IO; +import play.libs.WS.HttpResponse; +import play.libs.WS.WSImpl; +import play.libs.WS.WSRequest; +import play.mvc.Http.Header; + /** - * Implementation of the WS interface based on Java URL Fetch API. - * This is to be used for example in Google App Engine, where the - * async http client can't be used. + * Implementation of the WS interface based on Java URL Fetch API. This is to be used for example in Google App Engine, + * where the async http client can't be used. */ public class WSUrlFetch implements WSImpl { private static SSLContext sslCTX = null; - public WSUrlFetch() {} + public WSUrlFetch() { + } - public void stop() {} + @Override + public void stop() { + } + @Override public play.libs.WS.WSRequest newRequest(String url, String encoding) { return new WSUrlfetchRequest(url, encoding); } @@ -54,25 +58,28 @@ protected WSUrlfetchRequest(String url, String encoding) { private String getPreparedUrl(String method) { String u = url; if (parameters != null && !parameters.isEmpty()) { - // If not PUT or POST, we must add these params to the queryString + // If not PUT or POST, we must add these params to the + // queryString if (!("PUT".equals(method) || "POST".equals(method))) { // must add params to queryString/url StringBuilder sb = new StringBuilder(url); - if (url.indexOf("?")>0) { + if (url.indexOf("?") > 0) { sb.append('&'); } else { sb.append('?'); } - int count=0; - for( Map.Entry e : parameters.entrySet()) { + int count = 0; + for (Map.Entry e : parameters.entrySet()) { count++; String key = e.getKey(); Object value = e.getValue(); - if (value == null) continue; + if (value == null) { + continue; + } if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; - for (Object v: values) { + for (Object v : values) { if (count > 1) { sb.append('&'); } @@ -93,7 +100,8 @@ private String getPreparedUrl(String method) { u = sb.toString(); - // Must clear the parameters to prevent us from using them again + // Must clear the parameters to prevent us from using them + // again parameters.clear(); } } @@ -101,6 +109,7 @@ private String getPreparedUrl(String method) { } /** Execute a GET request synchronously. */ + @Override public HttpResponse get() { try { return new HttpUrlfetchResponse(prepare(new URL(getPreparedUrl("GET")), "GET")); @@ -110,7 +119,18 @@ public HttpResponse get() { } } - /** Execute a POST request.*/ + @Override + public HttpResponse patch() { + try { + HttpURLConnection conn = prepare(new URL(getPreparedUrl("PATCH")), "PATCH"); + return new HttpUrlfetchResponse(conn); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** Execute a POST request. */ + @Override public HttpResponse post() { try { HttpURLConnection conn = prepare(new URL(getPreparedUrl("POST")), "POST"); @@ -120,7 +140,8 @@ public HttpResponse post() { } } - /** Execute a PUT request.*/ + /** Execute a PUT request. */ + @Override public HttpResponse put() { try { return new HttpUrlfetchResponse(prepare(new URL(getPreparedUrl("PUT")), "PUT")); @@ -129,7 +150,8 @@ public HttpResponse put() { } } - /** Execute a DELETE request.*/ + /** Execute a DELETE request. */ + @Override public HttpResponse delete() { try { return new HttpUrlfetchResponse(prepare(new URL(getPreparedUrl("DELETE")), "DELETE")); @@ -138,7 +160,8 @@ public HttpResponse delete() { } } - /** Execute a OPTIONS request.*/ + /** Execute a OPTIONS request. */ + @Override public HttpResponse options() { try { return new HttpUrlfetchResponse(prepare(new URL(getPreparedUrl("OPTIONS")), "OPTIONS")); @@ -147,7 +170,8 @@ public HttpResponse options() { } } - /** Execute a HEAD request.*/ + /** Execute a HEAD request. */ + @Override public HttpResponse head() { try { return new HttpUrlfetchResponse(prepare(new URL(getPreparedUrl("HEAD")), "HEAD")); @@ -156,7 +180,8 @@ public HttpResponse head() { } } - /** Execute a TRACE request.*/ + /** Execute a TRACE request. */ + @Override public HttpResponse trace() { try { return new HttpUrlfetchResponse(prepare(new URL(getPreparedUrl("TRACE")), "TRACE")); @@ -167,14 +192,18 @@ public HttpResponse trace() { private HttpURLConnection prepare(URL url, String method) { String keyStore = Play.configuration.getProperty("ssl.keyStore", System.getProperty("javax.net.ssl.keyStore")); - String keyStorePass = Play.configuration.getProperty("ssl.keyStorePassword", System.getProperty("javax.net.ssl.keyStorePassword")); + String keyStorePass = Play.configuration.getProperty("ssl.keyStorePassword", + System.getProperty("javax.net.ssl.keyStorePassword")); Boolean CAValidation = Boolean.parseBoolean(Play.configuration.getProperty("ssl.cavalidation", "true")); if (this.username != null && this.password != null && this.scheme != null) { String authString = null; switch (this.scheme) { - case BASIC: authString = basicAuthHeader(); break; - default: throw new RuntimeException("Scheme " + this.scheme + " not supported by the UrlFetch WS backend."); + case BASIC: + authString = basicAuthHeader(); + break; + default: + throw new RuntimeException("Scheme " + this.scheme + " not supported by the UrlFetch WS backend."); } this.headers.put("Authorization", authString); } @@ -193,13 +222,13 @@ private HttpURLConnection prepare(URL url, String method) { try { URLConnection connection = url.openConnection(); if (connection instanceof HttpsURLConnection) { - HttpsURLConnection cssl = (HttpsURLConnection) connection; - if (sslCTX != null) { - SSLSocketFactory sslSocketFactory = sslCTX.getSocketFactory(); - cssl.setSSLSocketFactory(sslSocketFactory); - } - cssl.setRequestMethod(method); - cssl.setInstanceFollowRedirects(this.followRedirects); + HttpsURLConnection cssl = (HttpsURLConnection) connection; + if (sslCTX != null) { + SSLSocketFactory sslSocketFactory = sslCTX.getSocketFactory(); + cssl.setSSLSocketFactory(sslSocketFactory); + } + cssl.setRequestMethod(method); + cssl.setInstanceFollowRedirects(this.followRedirects); } else { HttpURLConnection c = (HttpURLConnection) connection; c.setRequestMethod(method); @@ -225,32 +254,8 @@ private HttpURLConnection prepare(URL url, String method) { } private void checkFileBody(HttpURLConnection connection) throws IOException { - /* if (this.fileParams != null) { - connection.setDoOutput(true); - //could be optimized, we know the size of this array. - for (int i = 0; i < this.fileParams.length; i++) { - builder.addBodyPart(new FilePart(this.fileParams[i].paramName, - this.fileParams[i].file, - MimeTypes.getMimeType(this.fileParams[i].file.getName()), - null)); - } - if (this.parameters != null) { - for (String key : this.parameters.keySet()) { - Object value = this.parameters.get(key); - if (value instanceof Collection || value.getClass().isArray()) { - Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; - for (Object v : values) { - builder.addBodyPart(new StringPart(key, v.toString())); - } - } else { - builder.addBodyPart(new StringPart(key, value.toString())); - } - } - } - return; - }*/ if (this.parameters != null && !this.parameters.isEmpty()) { - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset="+encoding); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + encoding); connection.setDoOutput(true); OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); writer.write(createQueryString()); @@ -261,23 +266,23 @@ private void checkFileBody(HttpURLConnection connection) throws IOException { throw new RuntimeException("POST or PUT method with parameters AND body are not supported."); } connection.setDoOutput(true); - - if(this.mimeType != null) { - connection.setRequestProperty("Content-Type", this.mimeType+"; charset="+encoding); + + if (this.mimeType != null) { + connection.setRequestProperty("Content-Type", this.mimeType + "; charset=" + encoding); } OutputStream out = connection.getOutputStream(); - if(this.body instanceof InputStream) { - InputStream bodyStream = (InputStream)this.body; + if (this.body instanceof InputStream) { + InputStream bodyStream = (InputStream) this.body; byte[] buffer = new byte[1024]; int bytesRead; - while( (bytesRead = bodyStream.read(buffer, 0, buffer.length)) > 0) { + while ((bytesRead = bodyStream.read(buffer, 0, buffer.length)) > 0) { out.write(buffer, 0, bytesRead); } } else { try { - byte[] bodyBytes = this.body.toString().getBytes( this.encoding ); - out.write( bodyBytes ); - } catch ( UnsupportedEncodingException e) { + byte[] bodyBytes = this.body.toString().getBytes(this.encoding); + out.write(bodyBytes); + } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } @@ -298,7 +303,9 @@ public static class HttpUrlfetchResponse extends HttpResponse { /** * you shouldn't have to create an HttpResponse yourself + * * @param connection + * The current connection */ public HttpUrlfetchResponse(HttpURLConnection connection) { try { @@ -312,7 +319,9 @@ public HttpUrlfetchResponse(HttpURLConnection connection) { } else { is = connection.getInputStream(); } - if (is != null) this.body = IO.readContentAsString(is, getEncoding()); + if (is != null) { + this.body = IO.readContentAsString(is, getEncoding()); + } } catch (Exception ex) { throw new RuntimeException(ex); } finally { @@ -322,6 +331,7 @@ public HttpUrlfetchResponse(HttpURLConnection connection) { /** * the HTTP status code + * * @return the status code of the http response */ @Override @@ -331,6 +341,7 @@ public Integer getStatus() { /** * the HTTP status text + * * @return the status text of the http response */ @Override @@ -345,7 +356,7 @@ public String getHeader(String key) { @Override public List
    getHeaders() { - List
    result = new ArrayList
    (); + List
    result = new ArrayList<>(); for (String key : headersMap.keySet()) { result.add(new Header(key, headersMap.get(key))); } @@ -354,6 +365,7 @@ public List
    getHeaders() { /** * get the response body as a string + * * @return the body of the http response */ @Override @@ -361,14 +373,24 @@ public String getString() { return body; } + @Override + public String getString(String encoding) { + try { + return new String(body.getBytes(), encoding); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + /** * get the response as a stream + * * @return an inputstream */ @Override public InputStream getStream() { try { - return new ByteArrayInputStream(body.getBytes( getEncoding() )); + return new ByteArrayInputStream(body.getBytes(getEncoding())); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } diff --git a/framework/src/play/mvc/ActionInvoker.java b/framework/src/play/mvc/ActionInvoker.java index 9b2049fa25..3e5085a2b4 100644 --- a/framework/src/play/mvc/ActionInvoker.java +++ b/framework/src/play/mvc/ActionInvoker.java @@ -1,18 +1,15 @@ package play.mvc; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; - -import org.apache.commons.lang.StringUtils; +import com.jamonapi.Monitor; +import com.jamonapi.MonitorFactory; +import org.apache.commons.javaflow.Continuation; +import org.apache.commons.javaflow.bytecode.StackRecorder; +import play.Invoker.Suspend; import play.Logger; import play.Play; +import play.cache.Cache; import play.cache.CacheFor; +import play.classloading.enhancers.ControllersEnhancer; import play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation; import play.classloading.enhancers.ControllersEnhancer.ControllerSupport; import play.data.binding.Binder; @@ -28,19 +25,22 @@ import play.mvc.Http.Request; import play.mvc.Router.Route; import play.mvc.results.NoResult; +import play.mvc.results.NotFound; import play.mvc.results.Result; import play.utils.Java; import play.utils.Utils; -import com.jamonapi.Monitor; -import com.jamonapi.MonitorFactory; - +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; import java.util.concurrent.Future; -import org.apache.commons.javaflow.Continuation; -import org.apache.commons.javaflow.bytecode.StackRecorder; -import play.Invoker.Suspend; -import play.classloading.enhancers.ControllersEnhancer; -import play.mvc.results.NotFound; /** * Invoke an action after an HTTP request. @@ -80,7 +80,7 @@ public static void resolve(Http.Request request, Http.Response response) { // Find the action method try { - Method actionMethod = null; + Method actionMethod; Object[] ca = getActionMethod(request.action); actionMethod = (Method) ca[1]; request.controller = ((Class) ca[0]).getName().substring(12).replace("$", ""); @@ -106,16 +106,16 @@ public static void invoke(Http.Request request, Http.Response response) { Monitor monitor = null; try { - resolve(request, response); - final Method actionMethod = request.invokedMethod; + Method actionMethod = request.invokedMethod; // 1. Prepare request params Scope.Params.current().__mergeWith(request.routeArgs); // add parameters from the URI query string String encoding = Http.Request.current().encoding; - Scope.Params.current()._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes(encoding)))); + Scope.Params.current() + ._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes(encoding)))); // 2. Easy debugging ... if (Play.mode == Play.Mode.DEV) { @@ -135,6 +135,9 @@ public static void invoke(Http.Request request, Http.Response response) { // Monitoring monitor = MonitorFactory.start(request.action + "()"); + String cacheKey = null; + Result actionResult = null; + // 3. Invoke the action try { // @Before @@ -142,101 +145,48 @@ public static void invoke(Http.Request request, Http.Response response) { // Action - Result actionResult = null; - String cacheKey = null; - // Check the cache (only for GET or HEAD) if ((request.method.equals("GET") || request.method.equals("HEAD")) && actionMethod.isAnnotationPresent(CacheFor.class)) { cacheKey = actionMethod.getAnnotation(CacheFor.class).id(); if ("".equals(cacheKey)) { cacheKey = "urlcache:" + request.url + request.querystring; } - actionResult = (Result) play.cache.Cache.get(cacheKey); + actionResult = (Result) Cache.get(cacheKey); } if (actionResult == null) { ControllerInstrumentation.initActionCall(); - try { - inferResult(invokeControllerMethod(actionMethod)); - } catch(Result result) { - actionResult = result; - // Cache it if needed - if (cacheKey != null) { - play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value()); - } - } catch (InvocationTargetException ex) { - // It's a Result ? (expected) - if (ex.getTargetException() instanceof Result) { - actionResult = (Result) ex.getTargetException(); - // Cache it if needed - if (cacheKey != null) { - play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value()); - } - - } else { - // @Catch - Object[] args = new Object[]{ex.getTargetException()}; - List catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class); - ControllerInstrumentation.stopActionCall(); - for (Method mCatch : catches) { - Class[] exceptions = mCatch.getAnnotation(Catch.class).value(); - if (exceptions.length == 0) { - exceptions = new Class[]{Exception.class}; - } - for (Class exception : exceptions) { - if (exception.isInstance(args[0])) { - mCatch.setAccessible(true); - inferResult(invokeControllerMethod(mCatch, args)); - break; - } - } - } - - throw ex; - } - } + inferResult(invokeControllerMethod(actionMethod)); } - - // @After - handleAfters(request); - - monitor.stop(); - monitor = null; - - // OK, re-throw the original action result - if (actionResult != null) { - throw actionResult; + } catch (Result result) { + actionResult = result; + // Cache it if needed + if (cacheKey != null) { + Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value()); } + } catch (JavaExecutionException e) { + invokeControllerCatchMethods(e.getCause()); + throw e; + } - throw new NoResult(); + // @After + handleAfters(request); - } catch (IllegalAccessException ex) { - throw ex; - } catch (IllegalArgumentException ex) { - throw ex; - } catch (InvocationTargetException ex) { - // It's a Result ? (expected) - if (ex.getTargetException() instanceof Result) { - throw (Result) ex.getTargetException(); - } - // Re-throw the enclosed exception - if (ex.getTargetException() instanceof PlayException) { - throw (PlayException) ex.getTargetException(); - } - StackTraceElement element = PlayException.getInterestingStackTraceElement(ex.getTargetException()); - if (element != null) { - throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), ex.getTargetException()); - } - throw new JavaExecutionException(Http.Request.current().action, ex); + monitor.stop(); + monitor = null; + + // OK, re-throw the original action result + if (actionResult != null) { + throw actionResult; } - } catch (Result result) { + throw new NoResult(); + } catch (Result result) { Play.pluginCollection.onActionInvocationResult(result); // OK there is a result to apply // Save session & flash scope now - Scope.Session.current().save(); Scope.Flash.current().save(); @@ -247,6 +197,9 @@ public static void invoke(Http.Request request, Http.Response response) { // @Finally handleFinallies(request, null); + } catch (JavaExecutionException e) { + handleFinallies(request, e.getCause()); + throw e; } catch (PlayException e) { handleFinallies(request, e); throw e; @@ -254,33 +207,68 @@ public static void invoke(Http.Request request, Http.Response response) { handleFinallies(request, e); throw new UnexpectedException(e); } finally { + Play.pluginCollection.onActionInvocationFinally(); + if (monitor != null) { monitor.stop(); } } } - private static boolean isActionMethod(Method method) { - if (method.isAnnotationPresent(Before.class)) { - return false; - } - if (method.isAnnotationPresent(After.class)) { - return false; - } - if (method.isAnnotationPresent(Finally.class)) { - return false; - } - if (method.isAnnotationPresent(Catch.class)) { - return false; + private static void invokeControllerCatchMethods(Throwable throwable) throws Exception { + // @Catch + Object[] args = new Object[] {throwable}; + List catches = Java.findAllAnnotatedMethods(getControllerClass(), Catch.class); + ControllerInstrumentation.stopActionCall(); + for (Method mCatch : catches) { + Class[] exceptions = mCatch.getAnnotation(Catch.class).value(); + if (exceptions.length == 0) { + exceptions = new Class[]{Exception.class}; + } + for (Class exception : exceptions) { + if (exception.isInstance(args[0])) { + mCatch.setAccessible(true); + inferResult(invokeControllerMethod(mCatch, args)); + break; + } + } } - if (method.isAnnotationPresent(Util.class)) { - return false; + } + + private static boolean isActionMethod(Method method) { + return !method.isAnnotationPresent(Before.class) && + !method.isAnnotationPresent(After.class) && + !method.isAnnotationPresent(Finally.class) && + !method.isAnnotationPresent(Catch.class) && + !method.isAnnotationPresent(Util.class); + } + + /** + * Find the first public method of a controller class + * + * @param name + * The method name + * @param clazz + * The class + * @return The method or null + */ + public static Method findActionMethod(String name, Class clazz) { + while (!clazz.getName().equals("java.lang.Object")) { + for (Method m : clazz.getDeclaredMethods()) { + if (m.getName().equalsIgnoreCase(name) && Modifier.isPublic(m.getModifiers())) { + // Check that it is not an interceptor + if (isActionMethod(m)) { + return m; + } + } + } + clazz = clazz.getSuperclass(); } - return true; + return null; } 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(); @@ -314,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,21 +336,25 @@ private static void handleAfters(Http.Request request) throws Exception { } /** - * Checks and calla all methods in controller annotated with @Finally. - * The caughtException-value is sent as argument to @Finally-method if method has one argument which is Throwable + * Checks and calla all methods in controller annotated with @Finally. The + * caughtException-value is sent as argument to @Finally-method if method + * has one argument which is Throwable + * * @param request - * @param caughtException If @Finally-methods are called after an error, this variable holds the caught error + * @param caughtException + * If @Finally-methods are called after an error, this variable + * holds the caught error * @throws PlayException */ static void handleFinallies(Http.Request request, Throwable caughtException) throws PlayException { - if (Controller.getControllerClass() == null) { - //skip it + 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(); @@ -391,23 +383,21 @@ static void handleFinallies(Http.Request request, Throwable caughtException) thr if (!skip) { aFinally.setAccessible(true); - //check if method accepts Throwable as only parameter + // check if method accepts Throwable as only parameter Class[] parameterTypes = aFinally.getParameterTypes(); if (parameterTypes.length == 1 && parameterTypes[0] == Throwable.class) { - //invoking @Finally method with caughtException as parameter - invokeControllerMethod(aFinally, new Object[]{caughtException}); + // invoking @Finally method with caughtException as + // parameter + invokeControllerMethod(aFinally, new Object[] { caughtException }); } else { - //invoce @Finally-method the regular way without caughtException + // invoke @Finally-method the regular way without + // caughtException invokeControllerMethod(aFinally, null); } } } - } catch (InvocationTargetException ex) { - StackTraceElement element = PlayException.getInterestingStackTraceElement(ex.getTargetException()); - if (element != null) { - throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), ex.getTargetException()); - } - throw new JavaExecutionException(Http.Request.current().action, ex); + } catch (PlayException e) { + throw e; } catch (Exception e) { throw new UnexpectedException("Exception while doing @Finally", e); } @@ -447,36 +437,60 @@ public static Object invokeControllerMethod(Method method) throws Exception { } public static Object invokeControllerMethod(Method method, Object[] forceArgs) throws Exception { - if (Modifier.isStatic(method.getModifiers()) && !method.getDeclaringClass().getName().matches("^controllers\\..*\\$class$")) { - return invoke(method, null, forceArgs == null ? getActionMethodArgs(method, null) : forceArgs); - } else if (Modifier.isStatic(method.getModifiers())) { - Object[] args = getActionMethodArgs(method, null); - args[0] = Http.Request.current().controllerClass.getDeclaredField("MODULE$").get(null); - return invoke(method, null, args); - } else { - Object instance = null; + boolean isStatic = Modifier.isStatic(method.getModifiers()); + String declaringClassName = method.getDeclaringClass().getName(); + boolean isProbablyScala = declaringClassName.contains("$"); + + Http.Request request = Http.Request.current(); + + if (!isStatic && request.controllerInstance == null) { + request.controllerInstance = request.controllerClass.newInstance(); + } + + Object[] args = forceArgs != null ? forceArgs : getActionMethodArgs(method, request.controllerInstance); + + if (isProbablyScala) { try { - instance = method.getDeclaringClass().getDeclaredField("MODULE$").get(null); - } catch (Exception e) { - Annotation[] annotations = method.getDeclaredAnnotations(); - String annotation = Utils.getSimpleNames(annotations); - if (!StringUtils.isEmpty(annotation)) { - throw new UnexpectedException("Method public static void " + method.getName() + "() annotated with " + annotation + " in class " + method.getDeclaringClass().getName() + " is not static."); + Object scalaInstance = request.controllerClass.getDeclaredField("MODULE$").get(null); + if (declaringClassName.endsWith("$class")) { + args[0] = scalaInstance; // Scala trait method + } else { + request.controllerInstance = (PlayController) scalaInstance; // Scala object method } - // TODO: Find a better error report - throw new ActionNotFoundException(Http.Request.current().action, e); + } catch (NoSuchFieldException e) { + // not Scala } - return invoke(method, instance, forceArgs == null ? getActionMethodArgs(method, instance) : forceArgs); } + + Object methodClassInstance = isStatic ? null : + (method.getDeclaringClass().isAssignableFrom(request.controllerClass)) ? request.controllerInstance : + method.getDeclaringClass().newInstance(); + + return invoke(method, methodClassInstance, args); } - static Object invoke(Method method, Object instance, Object[] realArgs) throws Exception { - if(isActionMethod(method)) { - return invokeWithContinuation(method, instance, realArgs); - } else { - return method.invoke(instance, realArgs); + static Object invoke(Method method, Object instance, Object ... realArgs) throws Exception { + try { + if (isActionMethod(method)) { + return invokeWithContinuation(method, instance, realArgs); + } else { + return method.invoke(instance, realArgs); + } + } catch (InvocationTargetException ex) { + Throwable originalThrowable = ex.getTargetException(); + + if (originalThrowable instanceof Result || originalThrowable instanceof PlayException) + throw (Exception) originalThrowable; + + StackTraceElement element = PlayException.getInterestingStackTraceElement(originalThrowable); + if (element != null) { + throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), + originalThrowable); + } + throw new JavaExecutionException(originalThrowable); } } + static final String C = "__continuation"; static final String A = "__callback"; static final String F = "__future"; @@ -488,11 +502,11 @@ static Object invoke(Method method, Object instance, Object[] realArgs) throws E 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) { @@ -508,7 +522,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)); } @@ -516,7 +530,7 @@ static Object invokeWithContinuation(Method method, Object instance, Object[] re StackRecorder pStackRecorder = new StackRecorder(continuation.stackRecorder); Object result = null; - final StackRecorder old = pStackRecorder.registerThread(); + StackRecorder old = pStackRecorder.registerThread(); try { pStackRecorder.isRestoring = !pStackRecorder.isEmpty(); @@ -529,7 +543,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); @@ -543,7 +557,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); @@ -565,36 +579,36 @@ 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)) { - throw new ActionNotFoundException(fullAction, new Exception("class " + controller + " does not extend play.mvc.Controller")); + if (!PlayController.class.isAssignableFrom(controllerClass)) { + throw new ActionNotFoundException(fullAction, + new Exception("class " + controller + " does not extend play.mvc.Controller")); } } - actionMethod = Java.findActionMethod(action, controllerClass); + actionMethod = findActionMethod(action, controllerClass); if (actionMethod == null) { - throw new ActionNotFoundException(fullAction, new Exception("No method public static void " + action + "() was found in class " + controller)); + throw new ActionNotFoundException(fullAction, + new Exception("No method public static void " + action + "() was found in class " + controller)); } } catch (PlayException e) { throw e; } catch (Exception e) { throw new ActionNotFoundException(fullAction, e); } - return new Object[]{controllerClass, actionMethod}; + return new Object[] { controllerClass, actionMethod }; } - public static Object[] getActionMethodArgs(Method method, Object o) throws Exception { String[] paramsNames = Java.parameterNames(method); if (paramsNames == null && method.getParameterTypes().length > 0) { throw new UnexpectedException("Parameter names not found for method " + method); } - // Check if we have already performed the bind operation Object[] rArgs = CachedBoundActionMethodArgs.current().retrieveActionMethodArgs(method); - if ( rArgs != null) { + if (rArgs != null) { // We have already performed the binding-operation for this method // in this request. return rArgs; @@ -604,7 +618,7 @@ public static Object[] getActionMethodArgs(Method method, Object o) throws Excep for (int i = 0; i < method.getParameterTypes().length; i++) { Class type = method.getParameterTypes()[i]; - Map params = new HashMap (); + Map params = new HashMap<>(); // In case of simple params, we don't want to parse the body. if (type.equals(String.class) || Number.class.isAssignableFrom(type) || type.isPrimitive()) { @@ -612,20 +626,19 @@ public static Object[] getActionMethodArgs(Method method, Object o) throws Excep } else { params.putAll(Scope.Params.current().all()); } - Logger.trace("getActionMethodArgs name [" + paramsNames[i] + "] annotation [" + Utils.join(method.getParameterAnnotations()[i], " ") + "]"); + Logger.trace("getActionMethodArgs name [" + paramsNames[i] + "] annotation [" + + Utils.join(method.getParameterAnnotations()[i], " ") + "]"); RootParamNode root = ParamNode.convert(params); - rArgs[i] = Binder.bind( - root, - paramsNames[i], - method.getParameterTypes()[i], - method.getGenericParameterTypes()[i], - method.getParameterAnnotations()[i], - new Binder.MethodAndParamInfo(o, method, i + 1)); + rArgs[i] = Binder.bind(root, paramsNames[i], method.getParameterTypes()[i], method.getGenericParameterTypes()[i], + method.getParameterAnnotations()[i], new Binder.MethodAndParamInfo(o, method, i + 1)); } CachedBoundActionMethodArgs.current().storeActionMethodArgs(method, rArgs); return rArgs; } + private static Class getControllerClass() { + return Http.Request.current().controllerClass; + } } diff --git a/framework/src/play/mvc/After.java b/framework/src/play/mvc/After.java index 8b7475d07f..a7c206812e 100644 --- a/framework/src/play/mvc/After.java +++ b/framework/src/play/mvc/After.java @@ -10,16 +10,26 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface After { +public @interface After { /** * Does not intercept these actions + * + * @return List of actions not to intercept */ String[] unless() default {}; + + /** + * Only intercept these actions + * + * @return List of actions to intercept + */ String[] only() default {}; /** * Interceptor priority (0 is high priority) + * + * @return The Interceptor priority */ int priority() default 0; diff --git a/framework/src/play/mvc/Before.java b/framework/src/play/mvc/Before.java index 5e63277d07..ae60fc8386 100644 --- a/framework/src/play/mvc/Before.java +++ b/framework/src/play/mvc/Before.java @@ -14,12 +14,22 @@ /** * Does not intercept these actions + * + * @return List of actions not to intercept */ String[] unless() default {}; + + /** + * Only intercept these actions + * + * @return List of actions to intercept + */ String[] only() default {}; /** * Interceptor priority (0 is high priority) + * + * @return The Interceptor priority */ int priority() default 0; diff --git a/framework/src/play/mvc/Catch.java b/framework/src/play/mvc/Catch.java index fb9556f1df..d334163d04 100644 --- a/framework/src/play/mvc/Catch.java +++ b/framework/src/play/mvc/Catch.java @@ -10,11 +10,14 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface Catch { +public @interface Catch { Class[] value() default {}; + /** * Interceptor priority (0 is high priority) + * + * @return The Interceptor priority */ int priority() default 0; diff --git a/framework/src/play/mvc/Controller.java b/framework/src/play/mvc/Controller.java index e2e7f3c81f..7112a9d515 100644 --- a/framework/src/play/mvc/Controller.java +++ b/framework/src/play/mvc/Controller.java @@ -6,14 +6,21 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.concurrent.Future; +import org.apache.commons.javaflow.Continuation; +import org.apache.commons.javaflow.bytecode.StackRecorder; import org.w3c.dom.Document; +import com.google.gson.Gson; +import com.google.gson.JsonSerializer; +import com.thoughtworks.xstream.XStream; + import play.Invoker.Suspend; import play.Logger; import play.Play; @@ -27,7 +34,12 @@ import play.data.binding.Unbinder; import play.data.validation.Validation; import play.data.validation.ValidationPlugin; -import play.exceptions.*; +import play.exceptions.ContinuationsException; +import play.exceptions.NoRouteFoundException; +import play.exceptions.PlayException; +import play.exceptions.TemplateNotFoundException; +import play.exceptions.UnexpectedException; +import play.libs.F; import play.libs.Time; import play.mvc.Http.Request; import play.mvc.Router.ActionDefinition; @@ -53,66 +65,67 @@ import play.utils.Java; import play.vfs.VirtualFile; -import com.google.gson.JsonSerializer; -import com.thoughtworks.xstream.XStream; -import java.lang.reflect.Type; -import org.apache.commons.javaflow.Continuation; -import org.apache.commons.javaflow.bytecode.StackRecorder; -import play.libs.F; - /** - * Application controller support: The controller receives input and initiates a response by making calls on model objects. + * 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. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.request - controller.request.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.request - controller.request.current() * */ protected static Http.Request request = null; /** * The current HTTP response: The message sent back from the server after a request. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.response - controller.response.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.response - controller.response.current() * */ protected static Http.Response response = null; /** - * The current HTTP session. The Play! session is not living on the server side but on the client side. - * In fact, it is stored in a signed cookie. This session is therefore limited to 4kb. + * The current HTTP session. The Play! session is not living on the server side but on the client side. In fact, it + * is stored in a signed cookie. This session is therefore limited to 4kb. * * From Wikipedia: - * - * Client-side sessions use cookies and cryptographic techniques to maintain state without storing as much data on the server. When presenting a dynamic web page, the server sends the current state data to the client (web browser) in the form of a cookie. The client saves the cookie in memory or on disk. With each successive request, the client sends the cookie back to the server, and the server uses the data to "remember" the state of the application for that specific client and generate an appropriate response. - * This mechanism may work well in some contexts; however, data stored on the client is vulnerable to tampering by the user or by software that has access to the client computer. To use client-side sessions where confidentiality and integrity are required, the following must be guaranteed: - * Confidentiality: Nothing apart from the server should be able to interpret session data. - * Data integrity: Nothing apart from the server should manipulate session data (accidentally or maliciously). - * Authenticity: Nothing apart from the server should be able to initiate valid sessions. - * To accomplish this, the server needs to encrypt the session data before sending it to the client, and modification of such information by any other party should be prevented via cryptographic means. - * Transmitting state back and forth with every request is only practical when the size of the cookie is small. In essence, client-side sessions trade server disk space for the extra bandwidth that each web request will require. Moreover, web browsers limit the number and size of cookies that may be stored by a web site. To improve efficiency and allow for more session data, the server may compress the data before creating the cookie, decompressing it later when the cookie is returned by the client. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.session - controller.session.current() + * Client-side sessions use cookies and cryptographic techniques to maintain state without storing as much data on + * the server. When presenting a dynamic web page, the server sends the current state data to the client (web + * browser) in the form of a cookie. The client saves the cookie in memory or on disk. With each successive request, + * the client sends the cookie back to the server, and the server uses the data to "remember" the state of the + * application for that specific client and generate an appropriate response. This mechanism may work well in some + * contexts; however, data stored on the client is vulnerable to tampering by the user or by software that has + * access to the client computer. To use client-side sessions where confidentiality and integrity are required, the + * following must be guaranteed: Confidentiality: Nothing apart from the server should be able to interpret session + * data. Data integrity: Nothing apart from the server should manipulate session data (accidentally or maliciously). + * Authenticity: Nothing apart from the server should be able to initiate valid sessions. To accomplish this, the + * server needs to encrypt the session data before sending it to the client, and modification of such information by + * any other party should be prevented via cryptographic means. Transmitting state back and forth with every request + * is only practical when the size of the cookie is small. In essence, client-side sessions trade server disk space + * for the extra bandwidth that each web request will require. Moreover, web browsers limit the number and size of + * cookies that may be stored by a web site. To improve efficiency and allow for more session data, the server may + * compress the data before creating the cookie, decompressing it later when the cookie is returned by the client. + * + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.session - controller.session.current() */ protected static Scope.Session session = null; /** - * The current flash scope. The flash is a temporary storage mechanism that is a hash map - * You can store values associated with keys and later retrieve them. - * It has one special property: by default, values stored into the flash during the processing of a request - * will be available during the processing of the immediately following request. - * Once that second request has been processed, those values are removed automatically from the storage + * The current flash scope. The flash is a temporary storage mechanism that is a hash map You can store values + * associated with keys and later retrieve them. It has one special property: by default, values stored into the + * flash during the processing of a request will be available during the processing of the immediately following + * request. Once that second request has been processed, those values are removed automatically from the storage * * This scope is very useful to display messages after issuing a Redirect. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.flash - controller.flash.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.flash - controller.flash.current() */ protected static Scope.Flash flash = null; /** @@ -120,32 +133,33 @@ public class Controller implements ControllerSupport, LocalVariablesSupport { * * This is useful for example to know which submit button a user pressed on a form. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.params - controller.params.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.params - controller.params.current() */ protected static Scope.Params params = null; /** - * The current renderArgs scope: This is a hash map that is accessible during the rendering phase. It means you can access - * variables stored in this scope during the rendering phase (the template phase). + * The current renderArgs scope: This is a hash map that is accessible during the rendering phase. It means you can + * access variables stored in this scope during the rendering phase (the template phase). * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.renderArgs - controller.renderArgs.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.renderArgs - controller.renderArgs.current() */ protected static Scope.RenderArgs renderArgs = null; /** - * The current routeArgs scope: This is a hash map that is accessible during the reverse routing phase. - * Any variable added to this scope will be used for reverse routing. Useful when you have a param that you want - * to add to any route without add it expicitely to every action method. + * The current routeArgs scope: This is a hash map that is accessible during the reverse routing phase. Any variable + * added to this scope will be used for reverse routing. Useful when you have a param that you want to add to any + * route without add it explicitly to every action method. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.routeArgs - controller.routeArgs.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.routeArgs - controller.routeArgs.current() */ protected static Scope.RouteArgs routeArgs = null; /** - * The current Validation object. It allows you to validate objects and to retrieve potential validations errors for those objects. + * The current Validation object. It allows you to validate objects and to retrieve potential validations errors for + * those objects. * - * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. - * ie : controller.validation - controller.validation.current() + * Note: The ControllersEnhancer makes sure that an appropriate thread local version is applied. ie : + * controller.validation - controller.validation.current() */ protected static Validation validation = null; @@ -156,7 +170,9 @@ public class Controller implements ControllerSupport, LocalVariablesSupport { /** * Return a 200 OK text/plain response - * @param text The response content + * + * @param text + * The response content */ protected static void renderText(Object text) { throw new RenderText(text == null ? "" : text.toString()); @@ -164,7 +180,9 @@ protected static void renderText(Object text) { /** * Return a 200 OK text/html response - * @param html The response content + * + * @param html + * The response content */ protected static void renderHtml(Object html) { throw new RenderHtml(html == null ? "" : html.toString()); @@ -172,8 +190,11 @@ protected static void renderHtml(Object html) { /** * Return a 200 OK text/plain response - * @param pattern The response content to be formatted (with String.format) - * @param args Args for String.format + * + * @param pattern + * The response content to be formatted (with String.format) + * @param args + * Args for String.format */ protected static void renderText(CharSequence pattern, Object... args) { throw new RenderText(pattern == null ? "" : String.format(pattern.toString(), args)); @@ -181,7 +202,9 @@ protected static void renderText(CharSequence pattern, Object... args) { /** * Return a 200 OK text/xml response - * @param xml The XML string + * + * @param xml + * The XML string */ protected static void renderXml(String xml) { throw new RenderXml(xml); @@ -189,7 +212,9 @@ protected static void renderXml(String xml) { /** * Return a 200 OK text/xml response - * @param xml The DOM document object + * + * @param xml + * The DOM document object */ protected static void renderXml(Document xml) { throw new RenderXml(xml); @@ -197,7 +222,9 @@ protected static void renderXml(Document xml) { /** * Return a 200 OK text/xml response. Use renderXml(Object, XStream) to customize the result. - * @param o the object to serialize + * + * @param o + * the object to serialize */ protected static void renderXml(Object o) { throw new RenderXml(o); @@ -205,18 +232,23 @@ protected static void renderXml(Object o) { /** * Return a 200 OK text/xml response - * @param o the object to serialize - * @param xstream the XStream object to use for serialization. See XStream's documentation - * for details about customizing the output. + * + * @param o + * the object to serialize + * @param xstream + * the XStream object to use for serialization. See XStream's documentation for details about customizing + * the output. */ protected static void renderXml(Object o, XStream xstream) { throw new RenderXml(o, xstream); } /** - * Return a 200 OK application/binary response. - * Content is fully loaded in memory, so it should not be used with large data. - * @param is The stream to copy + * Return a 200 OK application/binary response. Content is fully loaded in memory, so it should not be used with + * large data. + * + * @param is + * The stream to copy */ protected static void renderBinary(InputStream is) { throw new RenderBinary(is, null, true); @@ -225,19 +257,23 @@ protected static void renderBinary(InputStream is) { /** * Return a 200 OK application/binary response. Content is streamed. * - * @param is The stream to copy - * @param length Stream's size in bytes. + * @param is + * The stream to copy + * @param length + * Stream's size in bytes. */ protected static void renderBinary(InputStream is, long length) { throw new RenderBinary(is, null, length, true); } /** - * Return a 200 OK application/binary response with content-disposition attachment. - * Content is fully loaded in memory, so it should not be used with large data. + * Return a 200 OK application/binary response with content-disposition attachment. Content is fully loaded in + * memory, so it should not be used with large data. * - * @param is The stream to copy - * @param name Name of file user is downloading. + * @param is + * The stream to copy + * @param name + * Name of file user is downloading. */ protected static void renderBinary(InputStream is, String name) { throw new RenderBinary(is, name, false); @@ -246,21 +282,27 @@ protected static void renderBinary(InputStream is, String name) { /** * Return a 200 OK application/binary response with content-disposition attachment. * - * @param is The stream to copy. Content is streamed. - * @param name Name of file user is downloading. - * @param length Stream's size in bytes. + * @param is + * The stream to copy. Content is streamed. + * @param name + * Name of file user is downloading. + * @param length + * Stream's size in bytes. */ protected static void renderBinary(InputStream is, String name, long length) { throw new RenderBinary(is, name, length, false); } /** - * Return a 200 OK application/binary response with content-disposition attachment. - * Content is fully loaded in memory, so it should not be used with large data. + * Return a 200 OK application/binary response with content-disposition attachment. Content is fully loaded in + * memory, so it should not be used with large data. * - * @param is The stream to copy - * @param name Name of file user is downloading. - * @param inline true to set the response Content-Disposition to inline + * @param is + * The stream to copy + * @param name + * Name of file user is downloading. + * @param inline + * true to set the response Content-Disposition to inline */ protected static void renderBinary(InputStream is, String name, boolean inline) { throw new RenderBinary(is, name, inline); @@ -269,23 +311,31 @@ protected static void renderBinary(InputStream is, String name, boolean inline) /** * Return a 200 OK application/binary response with content-disposition attachment. * - * @param is The stream to copy - * @param name The attachment name - * @param length Stream's size in bytes. - * @param inline true to set the response Content-Disposition to inline + * @param is + * The stream to copy + * @param name + * The attachment name + * @param length + * Stream's size in bytes. + * @param inline + * true to set the response Content-Disposition to inline */ protected static void renderBinary(InputStream is, String name, long length, boolean inline) { throw new RenderBinary(is, name, length, inline); } /** - * Return a 200 OK application/binary response with content-disposition attachment. - * Content is fully loaded in memory, so it should not be used with large data. - * - * @param is The stream to copy - * @param name The attachment name - * @param contentType The content type of the attachment - * @param inline true to set the response Content-Disposition to inline + * Return a 200 OK application/binary response with content-disposition attachment. Content is fully loaded in + * memory, so it should not be used with large data. + * + * @param is + * The stream to copy + * @param name + * The attachment name + * @param contentType + * The content type of the attachment + * @param inline + * true to set the response Content-Disposition to inline */ protected static void renderBinary(InputStream is, String name, String contentType, boolean inline) { throw new RenderBinary(is, name, contentType, inline); @@ -294,11 +344,16 @@ protected static void renderBinary(InputStream is, String name, String contentTy /** * Return a 200 OK application/binary response with content-disposition attachment. * - * @param is The stream to copy - * @param name The attachment name - * @param length Content's byte size. - * @param contentType The content type of the attachment - * @param inline true to set the response Content-Disposition to inline + * @param is + * The stream to copy + * @param name + * The attachment name + * @param length + * Content's byte size. + * @param contentType + * The content type of the attachment + * @param inline + * true to set the response Content-Disposition to inline */ protected static void renderBinary(InputStream is, String name, long length, String contentType, boolean inline) { throw new RenderBinary(is, name, length, contentType, inline); @@ -306,7 +361,9 @@ protected static void renderBinary(InputStream is, String name, long length, Str /** * Return a 200 OK application/binary response - * @param file The file to copy + * + * @param file + * The file to copy */ protected static void renderBinary(File file) { throw new RenderBinary(file); @@ -314,8 +371,11 @@ protected static void renderBinary(File file) { /** * Return a 200 OK application/binary response with content-disposition attachment - * @param file The file to copy - * @param name The attachment name + * + * @param file + * The file to copy + * @param name + * The attachment name */ protected static void renderBinary(File file, String name) { throw new RenderBinary(file, name); @@ -323,7 +383,9 @@ protected static void renderBinary(File file, String name) { /** * Render a 200 OK application/json response - * @param jsonString The JSON string + * + * @param jsonString + * The JSON string */ protected static void renderJSON(String jsonString) { throw new RenderJson(jsonString); @@ -331,7 +393,9 @@ protected static void renderJSON(String jsonString) { /** * Render a 200 OK application/json response - * @param o The Java object to serialize + * + * @param o + * The Java object to serialize */ protected static void renderJSON(Object o) { throw new RenderJson(o); @@ -339,8 +403,11 @@ protected static void renderJSON(Object o) { /** * Render a 200 OK application/json response - * @param o The Java object to serialize - * @param type The Type informations for complex generic types + * + * @param o + * The Java object to serialize + * @param type + * The Type information for complex generic types */ protected static void renderJSON(Object o, Type type) { throw new RenderJson(o, type); @@ -348,13 +415,28 @@ protected static void renderJSON(Object o, Type type) { /** * Render a 200 OK application/json response. - * @param o The Java object to serialize - * @param adapters A set of GSON serializers/deserializers/instance creator to use + * + * @param o + * The Java object to serialize + * @param adapters + * A set of GSON serializers/deserializers/instance creator to use */ protected static void renderJSON(Object o, JsonSerializer... adapters) { throw new RenderJson(o, adapters); } + /** + * Render a 200 OK application/json response. + * + * @param o + * The Java object to serialize + * @param gson + * The GSON serializer object use + */ + protected static void renderJSON(Object o, Gson gson) { + throw new RenderJson(o, gson); + } + /** * Send a 304 Not Modified response */ @@ -364,6 +446,9 @@ protected static void notModified() { /** * Send a 400 Bad request + * + * @param msg + * The message */ protected static void badRequest(String msg) { throw new BadRequest(msg); @@ -373,12 +458,14 @@ protected static void badRequest(String msg) { * Send a 400 Bad request */ protected static void badRequest() { - throw new BadRequest(); + throw new BadRequest("Bad request"); } /** * Send a 401 Unauthorized response - * @param realm The realm name + * + * @param realm + * The realm name */ protected static void unauthorized(String realm) { throw new Unauthorized(realm); @@ -393,7 +480,9 @@ protected static void unauthorized() { /** * Send a 404 Not Found response - * @param what The Not Found resource name + * + * @param what + * The Not Found resource name */ protected static void notFound(String what) { throw new NotFound(what); @@ -415,7 +504,9 @@ protected static void todo() { /** * Send a 404 Not Found response if object is null - * @param o The object to check + * + * @param o + * The object to check */ protected static void notFoundIfNull(Object o) { if (o == null) { @@ -425,8 +516,11 @@ protected static void notFoundIfNull(Object o) { /** * Send a 404 Not Found response if object is null - * @param o The object to check - * @param what The Not Found resource name + * + * @param o + * The object to check + * @param what + * The Not Found resource name */ protected static void notFoundIfNull(Object o, String what) { if (o == null) { @@ -435,7 +529,7 @@ protected static void notFoundIfNull(Object o, String what) { } /** - * Send a 404 Not Found reponse + * Send a 404 Not Found response */ protected static void notFound() { throw new NotFound(""); @@ -447,14 +541,17 @@ protected static void notFound() { * @see play.templates.FastTags#_authenticityToken */ protected static void checkAuthenticity() { - if(Scope.Params.current().get("authenticityToken") == null || !Scope.Params.current().get("authenticityToken").equals(Scope.Session.current().getAuthenticityToken())) { + if (Scope.Params.current().get("authenticityToken") == null + || !Scope.Params.current().get("authenticityToken").equals(Scope.Session.current().getAuthenticityToken())) { forbidden("Bad authenticity token"); } } /** * Send a 403 Forbidden response - * @param reason The reason + * + * @param reason + * The reason */ protected static void forbidden(String reason) { throw new Forbidden(reason); @@ -469,8 +566,11 @@ protected static void forbidden() { /** * Send a 5xx Error response - * @param status The exact status code - * @param reason The reason + * + * @param status + * The exact status code + * @param reason + * The reason */ protected static void error(int status, String reason) { throw new Error(status, reason); @@ -478,7 +578,9 @@ protected static void error(int status, String reason) { /** * Send a 500 Error response - * @param reason The reason + * + * @param reason + * The reason */ protected static void error(String reason) { throw new Error(reason); @@ -486,7 +588,9 @@ protected static void error(String reason) { /** * Send a 500 Error response - * @param reason The reason + * + * @param reason + * The reason */ protected static void error(Exception reason) { Logger.error(reason, "error()"); @@ -502,8 +606,11 @@ protected static void error() { /** * Add a value to the flash scope - * @param key The key - * @param value The value + * + * @param key + * The key + * @param value + * The value */ protected static void flash(String key, Object value) { Scope.Flash.current().put(key, value); @@ -511,7 +618,9 @@ protected static void flash(String key, Object value) { /** * Send a 302 redirect response. - * @param url The Location to redirect + * + * @param url + * The Location to redirect */ protected static void redirect(String url) { redirect(url, false); @@ -519,7 +628,9 @@ protected static void redirect(String url) { /** * Send a 302 redirect response. - * @param file The Location to redirect + * + * @param file + * The Location to redirect */ protected static void redirectToStatic(String file) { try { @@ -540,8 +651,11 @@ protected static void redirectToStatic(String file) { /** * Send a Redirect response. - * @param url The Location to redirect - * @param permanent true -> 301, false -> 302 + * + * @param url + * The Location to redirect + * @param permanent + * true -> 301, false -> 302 */ protected static void redirect(String url, boolean permanent) { if (url.indexOf("/") == -1) { // fix Java ! @@ -552,8 +666,11 @@ protected static void redirect(String url, boolean permanent) { /** * 302 Redirect to another action - * @param action The fully qualified action name (ex: Application.index) - * @param args Method arguments + * + * @param action + * The fully qualified action name (ex: Application.index) + * @param args + * Method arguments */ public static void redirect(String action, Object... args) { redirect(action, false, args); @@ -561,20 +678,25 @@ public static void redirect(String action, Object... args) { /** * Redirect to another action - * @param action The fully qualified action name (ex: Application.index) - * @param permanent true -> 301, false -> 302 - * @param args Method arguments + * + * @param action + * The fully qualified action name (ex: Application.index) + * @param permanent + * true -> 301, false -> 302 + * @param args + * Method arguments */ protected static void redirect(String action, boolean permanent, Object... args) { try { - Map newArgs = new HashMap(args.length); + Map newArgs = new HashMap<>(args.length); Method actionMethod = (Method) ActionInvoker.getActionMethod(action)[1]; - String[] names = (String[]) actionMethod.getDeclaringClass().getDeclaredField("$" + actionMethod.getName() + LocalVariablesNamesTracer.computeMethodHash(actionMethod.getParameterTypes())).get(null); + String[] names = Java.parameterNames(actionMethod); for (int i = 0; i < names.length && i < args.length; i++) { Annotation[] annotations = actionMethod.getParameterAnnotations()[i]; boolean isDefault = false; try { - Method defaultMethod = actionMethod.getDeclaringClass().getDeclaredMethod(actionMethod.getName() + "$default$" + (i + 1)); + Method defaultMethod = actionMethod.getDeclaringClass() + .getDeclaredMethod(actionMethod.getName() + "$default$" + (i + 1)); // Patch for scala defaults if (!Modifier.isStatic(actionMethod.getModifiers()) && actionMethod.getDeclaringClass().getSimpleName().endsWith("$")) { Object instance = actionMethod.getDeclaringClass().getDeclaredField("MODULE$").get(null); @@ -614,7 +736,8 @@ protected static void redirect(String action, boolean permanent, Object... args) } catch (NoRouteFoundException e) { StackTraceElement element = PlayException.getInterestingStackTraceElement(e); if (element != null) { - throw new NoRouteFoundException(action, newArgs, Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber()); + throw new NoRouteFoundException(action, newArgs, Play.classes.getApplicationClass(element.getClassName()), + element.getLineNumber()); } else { throw e; } @@ -641,12 +764,15 @@ protected static boolean templateExists(String templateName) { /** * Render a specific template - * @param templateName The template name - * @param args The template data + * + * @param templateName + * The template name + * @param args + * The template data */ protected static void renderTemplate(String templateName, Object... args) { // Template datas - Map templateBinding = new HashMap(16); + Map templateBinding = new HashMap<>(16); for (Object o : args) { List names = LocalVariablesNamesTracer.getAllLocalVariableNames(o); for (String name : names) { @@ -659,10 +785,12 @@ protected static void renderTemplate(String templateName, Object... args) { /** * Render a specific template. * - * @param templateName The template name. - * @param args The template data. + * @param templateName + * The template name. + * @param args + * The template data. */ - protected static void renderTemplate(String templateName, Map args) { + protected static void renderTemplate(String templateName, Map args) { // Template datas Scope.RenderArgs templateBinding = Scope.RenderArgs.current(); templateBinding.data.putAll(args); @@ -692,16 +820,18 @@ protected static void renderTemplate(String templateName, Map arg /** * Render the template corresponding to the action's package-class-method name (@see template()). * - * @param args The template data. + * @param args + * The template data. */ - protected static void renderTemplate(Map args) { + protected static void renderTemplate(Map args) { renderTemplate(template(), args); } /** * Render the corresponding template (@see template()). * - * @param args The template data + * @param args + * The template data */ protected static void render(Object... args) { String templateName = null; @@ -714,12 +844,14 @@ protected static void render(Object... args) { } /** - * Work out the default template to load for the invoked action. - * E.g. "controllers.Pages.index" returns "views/Pages/index.html". + * Work out the default template to load for the invoked action. E.g. "controllers.Pages.index" returns + * "views/Pages/index.html". + * + * @return The template name */ protected static String template() { - final Request theRequest = Request.current(); - final String format = theRequest.format; + Request theRequest = Request.current(); + String format = theRequest.format; String templateName = theRequest.action.replace(".", "/") + "." + (format == null ? "html" : format); if (templateName.startsWith("@")) { templateName = templateName.substring(1); @@ -732,12 +864,16 @@ protected static String template() { } /** - * Work out the default template to load for the action. - * E.g. "controllers.Pages.index" returns "views/Pages/index.html". + * Work out the default template to load for the action. E.g. "controllers.Pages.index" returns + * "views/Pages/index.html". + * + * @param templateName + * The template name to work out + * @return The template name */ protected static String template(String templateName) { - final Request theRequest = Request.current(); - final String format = theRequest.format; + Request theRequest = Request.current(); + String format = theRequest.format; if (templateName.startsWith("@")) { templateName = templateName.substring(1); if (!templateName.contains(".")) { @@ -750,7 +886,11 @@ protected static String template(String templateName) { /** * Retrieve annotation for the action method - * @param clazz The annotation class + * + * @param clazz + * The annotation class + * @param + * The class type * @return Annotation object or null if not found */ protected static T getActionAnnotation(Class clazz) { @@ -763,7 +903,11 @@ protected static T getActionAnnotation(Class clazz) { /** * Retrieve annotation for the controller class - * @param clazz The annotation class + * + * @param clazz + * The annotation class + * @param + * The class type * @return Annotation object or null if not found */ protected static T getControllerAnnotation(Class clazz) { @@ -775,7 +919,11 @@ protected static T getControllerAnnotation(Class clazz /** * Retrieve annotation for the controller class - * @param clazz The annotation class + * + * @param clazz + * The annotation class + * @param + * The class type * @return Annotation object or null if not found */ protected static T getControllerInheritedAnnotation(Class clazz) { @@ -791,18 +939,24 @@ protected static T getControllerInheritedAnnotation(Class /** * Retrieve the controller class + * * @return Annotation object or null if not found */ + @SuppressWarnings("unchecked") protected static Class getControllerClass() { - return Http.Request.current().controllerClass; + return (Class) Http.Request.current().controllerClass; } /** * Call the parent action adding this objects to the params scope + * + * @param args + * List of parameters + * @deprecated */ @Deprecated protected static void parent(Object... args) { - Map map = new HashMap(16); + Map map = new HashMap<>(16); for (Object o : args) { List names = LocalVariablesNamesTracer.getAllLocalVariableNames(o); for (String name : names) { @@ -813,7 +967,7 @@ protected static void parent(Object... args) { } /** - * Call the parent method + * Call the parent method * @deprecated */ @Deprecated protected static void parent() { @@ -822,6 +976,10 @@ protected static void parent() { /** * Call the parent action adding this objects to the params scope + * + * @param map + * List of objects to the params scop + * @deprecated */ @Deprecated protected static void parent(Map map) { @@ -845,7 +1003,7 @@ protected static void parent(Map map) { if (superMethod == null) { throw new RuntimeException("PAF"); } - Map mapss = new HashMap(map.size()); + Map mapss = new HashMap<>(map.size()); for (Map.Entry entry : map.entrySet()) { Object value = entry.getValue(); mapss.put(entry.getKey(), value == null ? null : value.toString()); @@ -870,10 +1028,13 @@ protected static void parent(Map map) { /** * Suspend the current request for a specified amount of time. * - *

    Important: The method will not resume on the line after you call this. The method will - * be called again as if there was a new HTTP request. + *

    + * Important: The method will not resume on the line after you call this. The method will be called again as + * if there was a new HTTP request. * - * @param timeout Period of time to wait, e.g. "1h" means 1 hour. + * @param timeout + * Period of time to wait, e.g. "1h" means 1 hour. + * @deprecated */ @Deprecated protected static void suspend(String timeout) { @@ -883,10 +1044,13 @@ protected static void suspend(String timeout) { /** * Suspend the current request for a specified amount of time (in milliseconds). * - *

    Important: The method will not resume on the line after you call this. The method will - * be called again as if there was a new HTTP request. + *

    + * Important: The method will not resume on the line after you call this. The method will be called again as + * if there was a new HTTP request. * - * @param millis Number of milliseconds to wait until trying again. + * @param millis + * Number of milliseconds to wait until trying again. + * @deprecated */ @Deprecated protected static void suspend(int millis) { @@ -897,10 +1061,14 @@ protected static void suspend(int millis) { /** * Suspend this request and wait for the task completion * - *

    Important: The method will not resume on the line after you call this. The method will - * be called again as if there was a new HTTP request. - * + *

    + * Important: The method will not resume on the line after you call this. The method will be called again as + * if there was a new HTTP request. + *

    + * * @param task + * Taks to wait for + * @deprecated */ @Deprecated protected static void waitFor(Future task) { @@ -928,20 +1096,21 @@ protected static void await(int millis) { * * If isRestoring == null, the method will try to resolve it. * - * important: when using isRestoring == null you have to KNOW that continuation suspend - * is going to happen and that this method is called twice for this single - * continuation suspend operation for this specific request. + * important: when using isRestoring == null you have to KNOW that continuation suspend is going to happen and that + * this method is called twice for this single continuation suspend operation for this specific request. * - * @param isRestoring true if restoring, false if storing, and null if you don't know + * @param isRestoring + * true if restoring, false if storing, and null if you don't know */ private static void storeOrRestoreDataStateForContinuations(Boolean isRestoring) { - if (isRestoring==null) { - // Sometimes, due to how continuations suspends/restarts the code, we do not + if (isRestoring == null) { + // Sometimes, due to how continuations suspends/restarts the code, + // we do not // know when calling this method if we're suspending or restoring. - final String continuationStateKey = "__storeOrRestoreDataStateForContinuations_started"; - if ( Http.Request.current().args.remove(continuationStateKey)!=null ) { + String continuationStateKey = "__storeOrRestoreDataStateForContinuations_started"; + if (Http.Request.current().args.remove(continuationStateKey) != null) { isRestoring = true; } else { Http.Request.current().args.put(continuationStateKey, true); @@ -950,46 +1119,51 @@ private static void storeOrRestoreDataStateForContinuations(Boolean isRestoring) } if (isRestoring) { - //we are restoring after suspend + // we are restoring after suspend // localVariablesState - Stack> localVariablesState = (Stack>) Http.Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_LOCAL_VARIABLE_NAMES); + Stack> localVariablesState = (Stack>) Http.Request.current().args + .remove(ActionInvoker.CONTINUATIONS_STORE_LOCAL_VARIABLE_NAMES); LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.setLocalVariablesStateAfterAwait(localVariablesState); // renderArgs Scope.RenderArgs renderArgs = (Scope.RenderArgs) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_RENDER_ARGS); - Scope.RenderArgs.current.set( renderArgs); + Scope.RenderArgs.current.set(renderArgs); // Params - // We know that the params are partially reprocessed during awake(Before now), but here we restore the correct values as + // We know that the params are partially reprocessed during + // awake(Before now), but here we restore the correct values as // they where when we performed the await(); - Map params = (Map) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_PARAMS); + Map params = (Map) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_PARAMS); Scope.Params.current().all().clear(); - Scope.Params.current().all().putAll(params); - + if (params != null) { + Scope.Params.current().all().putAll(params); + } // Validations Validation validation = (Validation) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_VALIDATIONS); Validation.current.set(validation); - ValidationPlugin.keys.set( (Map) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_VALIDATIONPLUGIN_KEYS) ); + ValidationPlugin.keys + .set((Map) Request.current().args.remove(ActionInvoker.CONTINUATIONS_STORE_VALIDATIONPLUGIN_KEYS)); } else { // we are storing before suspend // localVariablesState - Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_LOCAL_VARIABLE_NAMES, LocalVariablesNamesTracer.getLocalVariablesStateBeforeAwait()); + Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_LOCAL_VARIABLE_NAMES, + LocalVariablesNamesTracer.getLocalVariablesStateBeforeAwait()); // renderArgs Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_RENDER_ARGS, Scope.RenderArgs.current()); - // Params - // Store the actual params values so we can restore the exact same state when awaking. - Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_PARAMS, new HashMap(Scope.Params.current().data)); + // Params + // Store the actual params values so we can restore the exact same + // state when awaking. + Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_PARAMS, new HashMap<>(Scope.Params.current().data)); // Validations Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_VALIDATIONS, Validation.current()); Request.current().args.put(ActionInvoker.CONTINUATIONS_STORE_VALIDATIONPLUGIN_KEYS, ValidationPlugin.keys.get()); - } } @@ -1003,69 +1177,73 @@ protected static void await(int millis, F.Action0 callback) { @SuppressWarnings("unchecked") protected static T await(Future future) { - if(future != null) { + if (future != null) { Request.current().args.put(ActionInvoker.F, future); - } else if(Request.current().args.containsKey(ActionInvoker.F)) { - // Since the continuation will restart in this code that isn't intstrumented by javaflow, + } else if (Request.current().args.containsKey(ActionInvoker.F)) { + // Since the continuation will restart in this code that isn't + // instrumented by javaflow, // we need to reset the state manually. StackRecorder.get().isCapturing = false; StackRecorder.get().isRestoring = false; StackRecorder.get().value = null; - future = (Future)Request.current().args.get(ActionInvoker.F); + future = (Future) Request.current().args.get(ActionInvoker.F); // Now reset the Controller invocation context ControllerInstrumentation.stopActionCall(); - storeOrRestoreDataStateForContinuations( true ); + storeOrRestoreDataStateForContinuations(true); } else { throw new UnexpectedException("Lost promise for " + Http.Request.current() + "!"); } - if(future.isDone()) { + if (future.isDone()) { try { return future.get(); - } catch(Exception e) { + } catch (Exception e) { throw new UnexpectedException(e); } } else { Request.current().isNew = false; verifyContinuationsEnhancement(); - storeOrRestoreDataStateForContinuations( false ); + storeOrRestoreDataStateForContinuations(false); Continuation.suspend(future); return null; } } /** - * Verifies that all application-code is properly enhanched. - * "application code" is the code on the callstack after leaving actionInvoke into the app, and before reentering Controller.await + * Verifies that all application-code is properly enhanced. "application code" is the code on the callstack after + * leaving actionInvoke into the app, and before reentering Controller.await */ private static void verifyContinuationsEnhancement() { // only check in dev mode.. if (Play.mode == Play.Mode.PROD) { return; } - + try { throw new Exception(); } catch (Exception e) { boolean haveSeenFirstApplicationClass = false; - for (StackTraceElement ste : e.getStackTrace() ) { + for (StackTraceElement ste : e.getStackTrace()) { String className = ste.getClassName(); if (!haveSeenFirstApplicationClass) { haveSeenFirstApplicationClass = Play.classes.getApplicationClass(className) != null; - // when haveSeenFirstApplicationClass is set to true, we are entering the user application code.. + // when haveSeenFirstApplicationClass is set to true, we are + // entering the user application code.. } if (haveSeenFirstApplicationClass) { if (className.startsWith("sun.") || className.startsWith("play.")) { // we're back into the play framework code... - return ; // done checking + return; // done checking } else { - // is this class enhanched? + // is this class enhanced? boolean enhanced = ContinuationEnhancer.isEnhanced(className); if (!enhanced) { - throw new ContinuationsException("Cannot use await/continuations when not all application classes on the callstack are properly enhanced. The following class is not enhanced: " + className); + throw new ContinuationsException( + "Cannot use await/continuations when not all application classes on the callstack are properly enhanced. The following class is not enhanced: " + + className); } } } @@ -1085,19 +1263,21 @@ protected static void await(Future future, F.Action callback) { /** * Don't use this directly if you don't know why */ - public static ThreadLocal _currentReverse = new ThreadLocal(); + public static final ThreadLocal _currentReverse = new ThreadLocal<>(); /** - * @todo - this "Usage" example below doesn't make sense. + * @play.todo TODO - this "Usage" example below doesn't make sense. * - * Usage: + * Usage: * - * + * * ActionDefinition action = reverse(); { * Application.anyAction(anyParam, "toto"); * } * String url = action.url; * + * + * @return The ActionDefiniton */ protected static ActionDefinition reverse() { ActionDefinition actionDefinition = new ActionDefinition(); @@ -1106,21 +1286,29 @@ protected static ActionDefinition reverse() { } /** - * Register a custoer template name resolver. That letter allows to override the way templates are resolved. + * Register a customer template name resolver. That letter allows to override the way templates are resolved. + * + * @param templateNameResolver + * The template resolver */ public static void registerTemplateNameResolver(ITemplateNameResolver templateNameResolver) { - if (null != Controller.templateNameResolver) Logger.warn("Existing tempate name resolver will be overriden!"); + if (null != Controller.templateNameResolver) + Logger.warn("Existing template name resolver will be overridden!"); Controller.templateNameResolver = templateNameResolver; } /** * This allow people that implements their own template engine to override the way template are resolved. */ - public static interface ITemplateNameResolver { + public interface ITemplateNameResolver { /** * Return the template path given a template name. + * + * @param templateName + * The template name + * @return The template path */ - public String resolveTemplateName(String templateName); - } + String resolveTemplateName(String templateName); + } } diff --git a/framework/src/play/mvc/CookieDataCodec.java b/framework/src/play/mvc/CookieDataCodec.java index fa8edf3bce..d1a4a7ab67 100644 --- a/framework/src/play/mvc/CookieDataCodec.java +++ b/framework/src/play/mvc/CookieDataCodec.java @@ -12,17 +12,22 @@ */ public class CookieDataCodec { - /** + /** * Cookie session parser for cookie created by version 1.2.5 or before. - *

    We need it to support old Play 1.2.5 session data encoding so that the cookie data doesn't become invalid when - * applications are upgraded to a newer version of Play

    + *

    + * We need it to support old Play 1.2.5 session data encoding so that the cookie data doesn't become invalid when + * applications are upgraded to a newer version of Play + *

    */ public static Pattern oldCookieSessionParser = Pattern.compile("\u0000([^:]*):([^\u0000]*)\u0000"); /** - * @param map the map to decode data into. - * @param data the data to decode. + * @param map + * the map to decode data into. + * @param data + * the data to decode. * @throws UnsupportedEncodingException + * if the encoding is not supported */ public static void decode(Map map, String data) throws UnsupportedEncodingException { // support old Play 1.2.5 session data encoding so that the cookie data doesn't become invalid when @@ -38,26 +43,26 @@ public static void decode(Map map, String data) throws Unsupport String[] keyValues = data.split("&"); for (String keyValue : keyValues) { - String[] splitted = keyValue.split("=", 2); - if (splitted.length == 2) { - map.put(URLDecoder.decode(splitted[0], "utf-8"), URLDecoder.decode(splitted[1], "utf-8")); + String[] split = keyValue.split("=", 2); + if (split.length == 2) { + map.put(URLDecoder.decode(split[0], "utf-8"), URLDecoder.decode(split[1], "utf-8")); } } } /** - * @param map the data to encode. + * @param map + * the data to encode. * @return the encoded data. * @throws UnsupportedEncodingException + * if the encoding is not supported */ public static String encode(Map map) throws UnsupportedEncodingException { StringBuilder data = new StringBuilder(); String separator = ""; for (Map.Entry entry : map.entrySet()) { if (entry.getValue() != null) { - data.append(separator) - .append(URLEncoder.encode(entry.getKey(), "utf-8")) - .append("=") + data.append(separator).append(URLEncoder.encode(entry.getKey(), "utf-8")).append("=") .append(URLEncoder.encode(entry.getValue(), "utf-8")); separator = "&"; } @@ -67,6 +72,12 @@ public static String encode(Map map) throws UnsupportedEncodingE /** * Constant time for same length String comparison, to prevent timing attacks + * + * @param a + * The string a + * @param b + * the string b + * @return true is the 2 strings are equals */ public static boolean safeEquals(String a, String b) { if (a.length() != b.length()) { diff --git a/framework/src/play/mvc/Finally.java b/framework/src/play/mvc/Finally.java index 3bf07f3929..7b0bfd278a 100644 --- a/framework/src/play/mvc/Finally.java +++ b/framework/src/play/mvc/Finally.java @@ -10,17 +10,26 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface Finally { +public @interface Finally { /** * Does not intercept these actions + * + * @return List of actions not to intercept */ String[] unless() default {}; + + /** + * Only intercept these actions + * + * @return List of actions to intercept + */ String[] only() default {}; /** * Interceptor priority (0 is high priority) + * + * @return The Interceptor priority */ int priority() default 0; - -} +} \ No newline at end of file diff --git a/framework/src/play/mvc/Http.java b/framework/src/play/mvc/Http.java index cb8af65843..6e1e6e5d14 100644 --- a/framework/src/play/mvc/Http.java +++ b/framework/src/play/mvc/Http.java @@ -1,6 +1,5 @@ package play.mvc; -import com.google.gson.Gson; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -20,6 +19,8 @@ import org.jboss.netty.channel.ChannelHandlerContext; +import com.google.gson.Gson; + import play.Logger; import play.Play; import play.exceptions.UnexpectedException; @@ -28,7 +29,6 @@ import play.libs.F.BlockingEventStream; import play.libs.F.Option; import play.libs.F.Promise; -import play.libs.F.EventStream; import play.libs.Time; import play.utils.HTTP; import play.utils.Utils; @@ -89,12 +89,12 @@ public static class Header implements Serializable { public List values; public Header() { - this.values = new ArrayList(5); + this.values = new ArrayList<>(5); } public Header(String name, String value) { this.name = name; - this.values = new ArrayList(5); + this.values = new ArrayList<>(5); this.values.add(value); } @@ -105,6 +105,7 @@ public Header(String name, List values) { /** * First value + * * @return The first value */ public String value() { @@ -123,13 +124,10 @@ public String toString() { public static class Cookie implements Serializable { /** - * When creating cookie without specifying domain, - * this value is used. Can be configured using - * the property 'application.defaultCookieDomain' - * in application.conf. + * When creating cookie without specifying domain, this value is used. Can be configured using the property + * 'application.defaultCookieDomain' in application.conf. * - * This feature can be used to allow sharing - * session/cookies between multiple sub domains. + * This feature can be used to allow sharing session/cookies between multiple sub domains. */ public static String defaultDomain = null; @@ -185,11 +183,11 @@ public static class Request implements Serializable { */ public String querystring; /** - * URL path (excluding scheme, host and port), starting with '/'
    + * URL path (excluding scheme, host and port), starting with '/'
    * - * Example:
    - * With this full URL http://localhost:9000/path0/path1
    - * => url will be /path0/path1 + * Example:
    + * With this full URL http://localhost:9000/path0/path1
    + * => url will be /path0/path1 */ public String url; /** @@ -209,8 +207,8 @@ public static class Request implements Serializable { */ public String contentType; /** - * This is the encoding used to decode this request. - * If encoding-info is not found in request, then Play.defaultWebEncoding is used + * This is the encoding used to decode this request. If encoding-info is not found in request, then + * Play.defaultWebEncoding is used */ public String encoding = Play.defaultWebEncoding; /** @@ -256,19 +254,23 @@ public static class Request implements Serializable { /** * Bind to thread */ - public static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); /** - * The really invoker Java methid + * The really invoker Java method */ public transient Method invokedMethod; /** * The invoked controller class */ - public transient Class controllerClass; + public transient Class controllerClass; + /** + * The instance of invoked controller in case it uses non-static action methods. + */ + public transient PlayController controllerInstance; /** * Free space to store your request specific data */ - public Map args = new HashMap(16); + public Map args = new HashMap<>(16); /** * When the request has been received */ @@ -298,40 +300,56 @@ public static class Request implements Serializable { */ public final Scope.Params params = new Scope.Params(); - /** - * Deprecate the default constructor to encourage the use of createRequest() when creating new - * requests. + * Deprecate the default constructor to encourage the use of createRequest() when creating new requests. * - * Cannot hide it with protected because we have to be backward compatible with modules - ie PlayGrizzlyAdapter.java + * Cannot hide it with protected because we have to be backward compatible with modules - ie + * PlayGrizzlyAdapter.java */ @Deprecated public Request() { - headers = new HashMap(16); - cookies = new HashMap(16); + headers = new HashMap<>(16); + cookies = new HashMap<>(16); } /** - * All creation / initing of new requests should use this method. - * The purpose of this is to "show" what is needed when creating new Requests. + * All creation / initiating of new requests should use this method. The purpose of this is to "show" what is + * needed when creating new Requests. + * + * @param _remoteAddress + * The remote IP address + * @param _method + * the Method + * @param _path + * path + * @param _querystring + * The query String + * @param _contentType + * The content Type + * @param _body + * The request body + * @param _url + * The request URL + * @param _host + * The request host + * @param _isLoopback + * Indicate if the request comes from loopback interface + * @param _port + * The request port + * @param _domain + * The request domain + * @param _secure + * Indicate is request is secure or not + * @param _headers + * The request headers + * @param _cookies + * The request cookies + * * @return the newly created Request object */ - public static Request createRequest( - String _remoteAddress, - String _method, - String _path, - String _querystring, - String _contentType, - InputStream _body, - String _url, - String _host, - boolean _isLoopback, - int _port, - String _domain, - boolean _secure, - Map _headers, - Map _cookies - ) { + public static Request createRequest(String _remoteAddress, String _method, String _path, String _querystring, String _contentType, + InputStream _body, String _url, String _host, boolean _isLoopback, int _port, String _domain, boolean _secure, + Map _headers, Map _cookies) { Request newRequest = new Request(); newRequest.remoteAddress = _remoteAddress; @@ -340,14 +358,14 @@ public static Request createRequest( newRequest.querystring = _querystring; // must try to extract encoding-info from contentType - if( _contentType == null ) { + if (_contentType == null) { newRequest.contentType = "text/html".intern(); } else { - HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType( _contentType ); + HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType(_contentType); newRequest.contentType = contentTypeEncoding.contentType; // check for encoding-info - if( contentTypeEncoding.encoding != null ) { + if (contentTypeEncoding.encoding != null) { // encoding-info was found in request newRequest.encoding = contentTypeEncoding.encoding; } @@ -361,13 +379,13 @@ public static Request createRequest( newRequest.domain = _domain; newRequest.secure = _secure; - if(_headers == null) { - _headers = new HashMap(16); + if (_headers == null) { + _headers = new HashMap<>(16); } newRequest.headers = _headers; - if(_cookies == null) { - _cookies = new HashMap(16); + if (_cookies == null) { + _cookies = new HashMap<>(16); } newRequest.cookies = _cookies; @@ -381,35 +399,50 @@ public static Request createRequest( } protected void parseXForwarded() { - + String _host = this.host; if (Play.configuration.containsKey("XForwardedSupport") && headers.get("x-forwarded-for") != null) { - if (!"ALL".equalsIgnoreCase(Play.configuration.getProperty("XForwardedSupport")) && !Arrays.asList(Play.configuration.getProperty("XForwardedSupport", "127.0.0.1").split("[\\s,]+")).contains(remoteAddress)) { + if (!"ALL".equalsIgnoreCase(Play.configuration.getProperty("XForwardedSupport")) + && !Arrays.asList(Play.configuration.getProperty("XForwardedSupport", "127.0.0.1").split("[\\s,]+")) + .contains(remoteAddress)) { throw new RuntimeException("This proxy request is not authorized: " + remoteAddress); } else { - secure = isRequestSecure(); + this.secure = isRequestSecure(); if (Play.configuration.containsKey("XForwardedHost")) { - host = (String) Play.configuration.get("XForwardedHost"); - } else if (headers.get("x-forwarded-host") != null) { - host = headers.get("x-forwarded-host").value(); + this.host = (String) Play.configuration.get("XForwardedHost"); + } else if (this.headers.get("x-forwarded-host") != null) { + this.host = this.headers.get("x-forwarded-host").value(); } - if (headers.get("x-forwarded-for") != null) { - remoteAddress = headers.get("x-forwarded-for").value(); + if (this.headers.get("x-forwarded-for") != null) { + this.remoteAddress = this.headers.get("x-forwarded-for").value(); } } } + + if (Play.configuration.getProperty("XForwardedOverwriteDomainAndPort", "false").toLowerCase().equals("true") + && this.host != null && !this.host.equals(_host)) { + if (this.host.contains(":")) { + String[] hosts = this.host.split(":"); + this.port = Integer.parseInt(hosts[1]); + this.domain = hosts[0]; + } else { + this.port = 80; + this.domain = this.host; + } + } } - + private boolean isRequestSecure() { Header xForwardedProtoHeader = headers.get("x-forwarded-proto"); Header xForwardedSslHeader = headers.get("x-forwarded-ssl"); // Check the less common "front-end-https" header, - // used apparently only by "Microsoft Internet Security and Acceleration Server" + // used apparently only by "Microsoft Internet Security and + // Acceleration Server" // and Squid when using Squid as a SSL frontend. Header frontEndHttpsHeader = headers.get("front-end-https"); - return ("https".equals(Play.configuration.get("XForwardedProto")) || - (xForwardedProtoHeader != null && "https".equals(xForwardedProtoHeader.value())) || - (xForwardedSslHeader != null && "on".equals(xForwardedSslHeader.value())) || - (frontEndHttpsHeader != null && "on".equals(frontEndHttpsHeader.value().toLowerCase()))); + return ("https".equals(Play.configuration.get("XForwardedProto")) + || (xForwardedProtoHeader != null && "https".equals(xForwardedProtoHeader.value())) + || (xForwardedSslHeader != null && "on".equals(xForwardedSslHeader.value())) + || (frontEndHttpsHeader != null && "on".equals(frontEndHttpsHeader.value().toLowerCase()))); } /** @@ -424,24 +457,27 @@ protected void authorizationInit() { Header header = headers.get("authorization"); if (header != null && header.value().startsWith("Basic ")) { String data = header.value().substring(6); - //In basic auth, the password can contain a colon as well so split(":") may split the string into - //3 parts....username, part1 of password and part2 of password so don't use split here + // In basic auth, the password can contain a colon as well so + // split(":") may split the string into + // 3 parts....username, part1 of password and part2 of password + // so don't use split here String decoded = new String(Codec.decodeBASE64(data)); - //splitting on ONLY first : allows user's password to contain a : + // splitting on ONLY first : allows user's password to contain a + // : int indexOf = decoded.indexOf(":"); - if(indexOf < 0) - return; - + if (indexOf < 0) + return; + String username = decoded.substring(0, indexOf); - String thePasswd = decoded.substring(indexOf+1); + String thePasswd = decoded.substring(indexOf + 1); user = username.length() > 0 ? username : null; password = thePasswd.length() > 0 ? thePasswd : null; } } /** - * Automatically resolve request format from the Accept header - * (in this order : html > xml > json > text) + * Automatically resolve request format from the Accept header (in this order : html > xml > json > + * text) */ public void resolveFormat() { @@ -484,6 +520,7 @@ public void resolveFormat() { /** * Retrieve the current request + * * @return the current request */ public static Request current() { @@ -492,6 +529,7 @@ public static Request current() { /** * Useful because we sometime use a lazy request loader + * * @return itself */ public Request get() { @@ -499,8 +537,9 @@ public Request get() { } /** - * This request was sent by an Ajax framework. - * (rely on the X-Requested-With header). + * This request was sent by an Ajax framework. (rely on the X-Requested-With header). + * + * @return True is the request is an Ajax, false otherwise */ public boolean isAjax() { if (!headers.containsKey("x-requested-with")) { @@ -511,6 +550,7 @@ public boolean isAjax() { /** * Get the request base (ex: http://localhost:9000 + * * @return the request base of the url (protocol, host and port) */ public String getBase() { @@ -526,20 +566,21 @@ public String toString() { } /** - * Return the languages requested by the browser, ordered by preference (preferred first). - * If no Accept-Language header is present, an empty list is returned. + * Return the languages requested by the browser, ordered by preference (preferred first). If no Accept-Language + * header is present, an empty list is returned. * * @return Language codes in order of preference, e.g. "en-us,en-gb,en,de". */ public List acceptLanguage() { final Pattern qpattern = Pattern.compile("q=([0-9\\.]+)"); if (!headers.containsKey("accept-language")) { - return Collections.emptyList(); + return Collections.emptyList(); } String acceptLanguage = headers.get("accept-language").value(); List languages = Arrays.asList(acceptLanguage.split(",")); Collections.sort(languages, new Comparator() { + @Override public int compare(String lang1, String lang2) { double q1 = 1.0; double q2 = 1.0; @@ -554,7 +595,7 @@ public int compare(String lang1, String lang2) { return (int) (q2 - q1); } }); - List result = new ArrayList(10); + List result = new ArrayList<>(10); for (String lang : languages) { result.add(lang.trim().split(";")[0]); } @@ -599,11 +640,11 @@ public static class Response { /** * Response headers */ - public Map headers = new HashMap(16); + public Map headers = new HashMap<>(16); /** * Response cookies */ - public Map cookies = new HashMap(16); + public Map cookies = new HashMap<>(16); /** * Response body stream */ @@ -620,10 +661,11 @@ public static class Response { /** * Bind to thread */ - public static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); /** * Retrieve the current response + * * @return the current response */ public static Response current() { @@ -632,7 +674,9 @@ public static Response current() { /** * Get a response header - * @param name Header name case-insensitive + * + * @param name + * Header name case-insensitive * @return the header value as a String */ public String getHeader(String name) { @@ -648,13 +692,16 @@ public String getHeader(String name) { /** * Set a response header - * @param name Header name - * @param value Header value + * + * @param name + * Header name + * @param value + * Header value */ public void setHeader(String name, String value) { Header h = new Header(); h.name = name; - h.values = new ArrayList(1); + h.values = new ArrayList<>(1); h.values.add(value); headers.put(name, h); } @@ -667,8 +714,11 @@ public void setContentTypeIfNotSet(String contentType) { /** * Set a new cookie - * @param name Cookie name - * @param value Cookie value + * + * @param name + * Cookie name + * @param value + * Cookie value */ public void setCookie(String name, String value) { setCookie(name, value, null, "/", null, false); @@ -676,7 +726,9 @@ public void setCookie(String name, String value) { /** * Removes the specified cookie with path / - * @param name cookiename + * + * @param name + * cookie name */ public void removeCookie(String name) { removeCookie(name, "/"); @@ -684,8 +736,11 @@ public void removeCookie(String name) { /** * Removes the cookie - * @param name cookiename - * @param path cookiepath + * + * @param name + * cookie name + * @param path + * cookie path */ public void removeCookie(String name, String path) { setCookie(name, "", null, path, 0, false); @@ -693,9 +748,13 @@ public void removeCookie(String name, String path) { /** * Set a new cookie that will expire in (current) + duration + * * @param name + * the cookie name * @param value - * @param duration Ex: 3d + * The cookie value + * @param duration + * the cookie duration (Ex: 3d) */ public void setCookie(String name, String value, String duration) { setCookie(name, value, null, "/", Time.parseDuration(duration), false); @@ -707,11 +766,10 @@ public void setCookie(String name, String value, String domain, String path, Int public void setCookie(String name, String value, String domain, String path, Integer maxAge, boolean secure, boolean httpOnly) { path = Play.ctxPath + path; - if (cookies.containsKey(name) && cookies.get(name).path.equals(path) && ((cookies.get(name).domain == null && domain == null) || (cookies.get(name).domain.equals(domain)))) { + if (cookies.containsKey(name) && cookies.get(name).path.equals(path) + && ((cookies.get(name).domain == null && domain == null) || (cookies.get(name).domain.equals(domain)))) { cookies.get(name).value = value; - if (maxAge != null) { - cookies.get(name).maxAge = maxAge; - } + cookies.get(name).maxAge = maxAge; cookies.get(name).secure = secure; } else { Cookie cookie = new Cookie(); @@ -734,7 +792,9 @@ public void setCookie(String name, String value, String domain, String path, Int /** * Add a cache-control header - * @param duration Ex: 3h + * + * @param duration + * Ex: 3h */ public void cacheFor(String duration) { int maxAge = Time.parseDuration(duration); @@ -743,7 +803,14 @@ public void cacheFor(String duration) { /** * Add cache-control headers - * @param duration Ex: 3h + * + * @param etag + * the Etag value + * + * @param duration + * the cache duration (Ex: 3h) + * @param lastModified + * The last modified date */ public void cacheFor(String etag, String duration, long lastModified) { int maxAge = Time.parseDuration(duration); @@ -753,33 +820,41 @@ public void cacheFor(String etag, String duration, long lastModified) { } /** - * Add headers to allow cross-domain requests. Be careful, a lot of browsers don't support - * these features and will ignore the headers. Refer to the browsers' documentation to - * know what versions support them. - * @param allowOrigin a comma separated list of domains allowed to perform the x-domain call, or "*" for all. + * Add headers to allow cross-domain requests. Be careful, a lot of browsers don't support these features and + * will ignore the headers. Refer to the browsers' documentation to know what versions support them. + * + * @param allowOrigin + * a comma separated list of domains allowed to perform the x-domain call, or "*" for all. */ public void accessControl(String allowOrigin) { accessControl(allowOrigin, null, false); } /** - * Add headers to allow cross-domain requests. Be careful, a lot of browsers don't support - * these features and will ignore the headers. Refer to the browsers' documentation to - * know what versions support them. - * @param allowOrigin a comma separated list of domains allowed to perform the x-domain call, or "*" for all. - * @param allowCredentials Let the browser send the cookies when doing a x-domain request. Only respected by the browser if allowOrigin != "*" + * Add headers to allow cross-domain requests. Be careful, a lot of browsers don't support these features and + * will ignore the headers. Refer to the browsers' documentation to know what versions support them. + * + * @param allowOrigin + * a comma separated list of domains allowed to perform the x-domain call, or "*" for all. + * @param allowCredentials + * Let the browser send the cookies when doing a x-domain request. Only respected by the browser if + * allowOrigin != "*" */ public void accessControl(String allowOrigin, boolean allowCredentials) { accessControl(allowOrigin, null, allowCredentials); } /** - * Add headers to allow cross-domain requests. Be careful, a lot of browsers don't support - * these features and will ignore the headers. Refer to the browsers' documentation to - * know what versions support them. - * @param allowOrigin a comma separated list of domains allowed to perform the x-domain call, or "*" for all. - * @param allowMethods a comma separated list of HTTP methods allowed, or null for all. - * @param allowCredentials Let the browser send the cookies when doing a x-domain request. Only respected by the browser if allowOrigin != "*" + * Add headers to allow cross-domain requests. Be careful, a lot of browsers don't support these features and + * will ignore the headers. Refer to the browsers' documentation to know what versions support them. + * + * @param allowOrigin + * a comma separated list of domains allowed to perform the x-domain call, or "*" for all. + * @param allowMethods + * a comma separated list of HTTP methods allowed, or null for all. + * @param allowCredentials + * Let the browser send the cookies when doing a x-domain request. Only respected by the browser if + * allowOrigin != "*" */ public void accessControl(String allowOrigin, String allowMethods, boolean allowCredentials) { setHeader("Access-Control-Allow-Origin", allowOrigin); @@ -788,7 +863,8 @@ public void accessControl(String allowOrigin, String allowMethods, boolean allow } if (allowCredentials == true) { if (allowOrigin.equals("*")) { - Logger.warn("Response.accessControl: When the allowed domain is \"*\", Allow-Credentials is likely to be ignored by the browser."); + Logger.warn( + "Response.accessControl: When the allowed domain is \"*\", Allow-Credentials is likely to be ignored by the browser."); } setHeader("Access-Control-Allow-Credentials", "true"); } @@ -805,9 +881,10 @@ public void print(Object o) { public void reset() { out.reset(); } + // Chunked stream public boolean chunked = false; - final List> writeChunkHandlers = new ArrayList>(); + final List> writeChunkHandlers = new ArrayList<>(); public void writeChunk(Object o) { this.chunked = true; @@ -829,19 +906,17 @@ public void onWriteChunk(F.Action handler) { */ public abstract static class Inbound { - public final static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); final BlockingEventStream stream; - public Inbound(ChannelHandlerContext ctx) { - stream = new BlockingEventStream(ctx); - } - + stream = new BlockingEventStream<>(ctx); + } + public static Inbound current() { return current.get(); } - public void _received(WebSocketFrame frame) { stream.publish(frame); } @@ -863,9 +938,9 @@ public void close() { /** * A Websocket Outbound channel */ - public static abstract class Outbound { + public abstract static class Outbound { - public static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); public static Outbound current() { return current.get(); @@ -937,9 +1012,9 @@ public Option match(WebSocketEvent o) { */ public static class WebSocketFrame extends WebSocketEvent { - final public boolean isBinary; - final public String textData; - final public byte[] binaryData; + public final boolean isBinary; + public final String textData; + public final byte[] binaryData; public WebSocketFrame(String data) { this.isBinary = false; diff --git a/framework/src/play/mvc/Mailer.java b/framework/src/play/mvc/Mailer.java index 2a1cf0c0f6..5fcef74281 100644 --- a/framework/src/play/mvc/Mailer.java +++ b/framework/src/play/mvc/Mailer.java @@ -13,10 +13,18 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import org.apache.commons.io.IOUtils; +import javax.activation.DataSource; +import javax.activation.URLDataSource; +import javax.mail.internet.InternetAddress; + import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; -import org.apache.commons.mail.*; +import org.apache.commons.mail.Email; +import org.apache.commons.mail.EmailAttachment; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; +import org.apache.commons.mail.MultiPartEmail; +import org.apache.commons.mail.SimpleEmail; import play.Logger; import play.Play; @@ -25,66 +33,86 @@ import play.exceptions.MailException; import play.exceptions.TemplateNotFoundException; import play.exceptions.UnexpectedException; +import play.libs.F; +import play.libs.F.T4; import play.libs.Mail; import play.libs.MimeTypes; import play.templates.Template; import play.templates.TemplateLoader; import play.vfs.VirtualFile; -import javax.activation.DataSource; -import javax.activation.URLDataSource; -import javax.mail.internet.InternetAddress; -import play.libs.F; -import play.libs.F.T4; - /** * Application mailer support */ public class Mailer implements LocalVariablesSupport { - protected static ThreadLocal> infos = new ThreadLocal>(); + protected static final ThreadLocal> infos = new ThreadLocal<>(); /** * Set subject of mail, optionally providing formatting arguments - * @param subject plain String or formatted string - interpreted as formatted string only if aguments are provided - * @param args optional arguments for formatting subject + * + * @param subject + * plain String or formatted string - interpreted as formatted string only if arguments are provided + * @param args + * optional arguments for formatting subject */ public static void setSubject(String subject, Object... args) { - HashMap map = infos.get(); + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } - if(args.length != 0){ - subject = String.format(subject, args); - } + if (args.length != 0) { + subject = String.format(subject, args); + } map.put("subject", subject); infos.set(map); } @SuppressWarnings("unchecked") + public static void addRecipient(String... recipients) { + List recipientsParam = Arrays.asList(recipients); + addRecipients(recipientsParam); + } + + /** + * Add recipients + * + * @param recipients + * List of recipients + * @deprecated use method {{@link #addRecipient(String...)}} + */ + @Deprecated public static void addRecipient(Object... recipients) { - HashMap map = infos.get(); + List recipientList = new ArrayList<>(recipients.length); + for (Object recipient : recipients) { + recipientList.add(recipient.toString()); + } + addRecipients(recipientList); + } + + private static void addRecipients(List recipientsParam) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } - List recipientsList = (List) map.get("recipients"); + List recipientsList = (List) map.get("recipients"); if (recipientsList == null) { - recipientsList = new ArrayList(); + recipientsList = new ArrayList<>(); map.put("recipients", recipientsList); } - recipientsList.addAll(Arrays.asList(recipients)); + recipientsList.addAll(recipientsParam); infos.set(map); } @SuppressWarnings("unchecked") - public static void addBcc(Object... bccs) { - HashMap map = infos.get(); + public static void addBcc(String... bccs) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } - List bccsList = (List) map.get("bccs"); + List bccsList = (List) map.get("bccs"); if (bccsList == null) { - bccsList = new ArrayList(); + bccsList = new ArrayList<>(); map.put("bccs", bccsList); } bccsList.addAll(Arrays.asList(bccs)); @@ -92,14 +120,14 @@ public static void addBcc(Object... bccs) { } @SuppressWarnings("unchecked") - public static void addCc(Object... ccs) { - HashMap map = infos.get(); + public static void addCc(String... ccs) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } - List ccsList = (List) map.get("ccs"); + List ccsList = (List) map.get("ccs"); if (ccsList == null) { - ccsList = new ArrayList(); + ccsList = new ArrayList<>(); map.put("ccs", ccsList); } ccsList.addAll(Arrays.asList(ccs)); @@ -108,60 +136,60 @@ public static void addCc(Object... ccs) { @SuppressWarnings("unchecked") public static void addAttachment(EmailAttachment... attachments) { - HashMap map = infos.get(); + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } List attachmentsList = (List) map.get("attachments"); if (attachmentsList == null) { - attachmentsList = new ArrayList(); + attachmentsList = new ArrayList<>(); map.put("attachments", attachmentsList); } attachmentsList.addAll(Arrays.asList(attachments)); infos.set(map); } - @SuppressWarnings("unchecked") - public static void attachDataSource(DataSource dataSource, String name, String description, String disposition) { - HashMap map = infos.get(); + @SuppressWarnings("unchecked") + public static void attachDataSource(DataSource dataSource, String name, String description, String disposition) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } List> datasourceList = (List>) map.get("datasources"); if (datasourceList == null) { - datasourceList = new ArrayList>(); + datasourceList = new ArrayList<>(); map.put("datasources", datasourceList); } datasourceList.add(F.T4(dataSource, name, description, disposition)); infos.set(map); } - - public static void attachDataSource(DataSource dataSource, String name, String description){ - attachDataSource(dataSource, name, description, EmailAttachment.ATTACHMENT); + + public static void attachDataSource(DataSource dataSource, String name, String description) { + attachDataSource(dataSource, name, description, EmailAttachment.ATTACHMENT); + } + + public static String attachInlineEmbed(DataSource dataSource, String name) { + Map map = infos.get(); + if (map == null) { + throw new UnexpectedException("Mailer not instrumented ?"); + } + + InlineImage inlineImage = new InlineImage(dataSource); + + Map inlineEmbeds = (Map) map.get("inlineEmbeds"); + if (inlineEmbeds == null) { + inlineEmbeds = new HashMap<>(); + map.put("inlineEmbeds", inlineEmbeds); + } + + inlineEmbeds.put(name, inlineImage); + infos.set(map); + + return "cid:" + inlineImage.cid; } - - public static String attachInlineEmbed(DataSource dataSource, String name) { - HashMap map = infos.get(); - if (map == null) { - throw new UnexpectedException("Mailer not instrumented ?"); - } - - InlineImage inlineImage = new InlineImage(dataSource); - - Map inlineEmbeds = (Map) map.get("inlineEmbeds"); - if (inlineEmbeds == null) { - inlineEmbeds = new HashMap(); - map.put("inlineEmbeds", inlineEmbeds); - } - - inlineEmbeds.put(name, inlineImage); - infos.set(map); - - return "cid:" + inlineImage.cid; - } public static void setContentType(String contentType) { - HashMap map = infos.get(); + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } @@ -170,19 +198,24 @@ public static void setContentType(String contentType) { } /** - * Can be of the form xxx + * Can be of the form xxx <m@m.com> * * @param from + * The sender name (ex: xxx <m@m.com>) */ - public static void setFrom(Object from) { - HashMap map = infos.get(); + public static void setFrom(String from) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } map.put("from", from); infos.set(map); } - + + public static void setFrom(InternetAddress from) { + setFrom(from.toString()); + } + private static class InlineImage { /** content id */ private final String cid; @@ -190,7 +223,7 @@ private static class InlineImage { private final DataSource dataSource; public InlineImage(DataSource dataSource) { - this(null, dataSource); + this(null, dataSource); } public InlineImage(String cid, DataSource dataSource) { @@ -207,7 +240,7 @@ public DataSource getDataSource() { return this.dataSource; } } - + private static class VirtualFileDataSource implements DataSource { private final VirtualFile virtualFile; @@ -254,14 +287,19 @@ public boolean equals(Object obj) { return this.virtualFile.equals(rhs.virtualFile); } } - + + @Deprecated public static String getEmbedddedSrc(String urlString, String name) { - HashMap map = infos.get(); + return getEmbeddedSrc(urlString, name); + } + + public static String getEmbeddedSrc(String urlString, String name) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } - - DataSource dataSource = null; + + DataSource dataSource; URL url = null; VirtualFile img = Play.getVirtualFile(urlString); @@ -282,14 +320,12 @@ public static String getEmbedddedSrc(String urlString, String name) { throw new UnexpectedException("name cannot be null or empty"); } - dataSource = url.getProtocol().equals("file") ? new VirtualFileDataSource( - url.getFile()) : new URLDataSource(url); + dataSource = url.getProtocol().equals("file") ? new VirtualFileDataSource(url.getFile()) : new URLDataSource(url); } else { dataSource = new VirtualFileDataSource(img); } - Map inlineEmbeds = (Map) map - .get("inlineEmbeds"); + Map inlineEmbeds = (Map) map.get("inlineEmbeds"); // Check if a URLDataSource for this name has already been attached; // if so, return the cached CID value. @@ -297,24 +333,19 @@ public static String getEmbedddedSrc(String urlString, String name) { InlineImage ii = inlineEmbeds.get(name); if (ii.getDataSource() instanceof URLDataSource) { - URLDataSource urlDataSource = (URLDataSource) ii - .getDataSource(); + URLDataSource urlDataSource = (URLDataSource) ii.getDataSource(); // Make sure the supplied URL points to the same thing // as the one already associated with this name. // NOTE: Comparing URLs with URL.equals() is a blocking // operation // in the case of a network failure therefore we use // url.toExternalForm().equals() here. - if (url == null || urlDataSource == null || !url.toExternalForm().equals( - urlDataSource.getURL().toExternalForm())) { - throw new UnexpectedException("embedded name '" + name - + "' is already bound to URL " - + urlDataSource.getURL() + if (url == null || urlDataSource == null || !url.toExternalForm().equals(urlDataSource.getURL().toExternalForm())) { + throw new UnexpectedException("embedded name '" + name + "' is already bound to URL " + urlDataSource.getURL() + "; existing names cannot be rebound"); } } else if (!ii.getDataSource().equals(dataSource)) { - throw new UnexpectedException("embedded name '" + name - + "' is already bound to URL " + dataSource.getName() + throw new UnexpectedException("embedded name '" + name + "' is already bound to URL " + dataSource.getName() + "; existing names cannot be rebound"); } @@ -322,25 +353,23 @@ public static String getEmbedddedSrc(String urlString, String name) { } // Verify that the data source is valid. - InputStream is = null; - try { - is = dataSource.getInputStream(); + + try (InputStream is = dataSource.getInputStream()) { } catch (IOException e) { - throw new UnexpectedException("Invalid URL", e); - } finally { - IOUtils.closeQuietly(is); + throw new UnexpectedException("Invalid URL " + urlString + " for image " + name, e); } return attachInlineEmbed(dataSource, name); } /** - * Can be of the form xxx + * Can be of the form xxx <m@m.com> * * @param replyTo + * : The reply to address (ex: xxx <m@m.com>) */ - public static void setReplyTo(Object replyTo) { - HashMap map = infos.get(); + public static void setReplyTo(String replyTo) { + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } @@ -348,8 +377,12 @@ public static void setReplyTo(Object replyTo) { infos.set(map); } + public static void setReplyTo(InternetAddress replyTo) { + setReplyTo(replyTo.toString()); + } + public static void setCharset(String bodyCharset) { - HashMap map = infos.get(); + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } @@ -359,35 +392,34 @@ public static void setCharset(String bodyCharset) { @SuppressWarnings("unchecked") public static void addHeader(String key, String value) { - HashMap map = infos.get(); + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } - HashMap headers = (HashMap) map.get("headers"); + Map headers = (Map) map.get("headers"); if (headers == null) { - headers = new HashMap(); + headers = new HashMap<>(); } headers.put(key, value); map.put("headers", headers); infos.set(map); } - @SuppressWarnings("unchecked") public static Future send(Object... args) { try { - final HashMap map = infos.get(); + Map map = infos.get(); if (map == null) { throw new UnexpectedException("Mailer not instrumented ?"); } // Body character set - final String charset = (String) infos.get().get("charset"); + String charset = (String) infos.get().get("charset"); // Headers - final Map headers = (Map) infos.get().get("headers"); + Map headers = (Map) infos.get().get("headers"); // Subject - final String subject = (String) infos.get().get("subject"); + String subject = (String) infos.get().get("subject"); String templateName = (String) infos.get().get("method"); if (templateName.startsWith("notifiers.")) { @@ -404,8 +436,8 @@ public static Future send(Object... args) { templateName = args[0].toString(); } - final Map templateHtmlBinding = new HashMap(); - final Map templateTextBinding = new HashMap(); + Map templateHtmlBinding = new HashMap<>(); + Map templateTextBinding = new HashMap<>(); for (Object o : args) { List names = LocalVariablesNamesTracer.getAllLocalVariableNames(o); for (String name : names) { @@ -419,7 +451,7 @@ public static Future send(Object... args) { // If contentType is not specified look at the template available: // - .txt only -> text/plain // else - // - -> text/html + // - -> text/html String contentType = (String) infos.get().get("contentType"); String bodyHtml = null; String bodyText = ""; @@ -452,13 +484,14 @@ public static Future send(Object... args) { } // Recipients - final List recipientList = (List) infos.get().get("recipients"); + List recipientList = (List) infos.get().get("recipients"); // From - final Object from = infos.get().get("from"); - final Object replyTo = infos.get().get("replyTo"); + String from = (String) infos.get().get("from"); + String replyTo = (String) infos.get().get("replyTo"); - Email email = null; - if (infos.get().get("attachments") == null && infos.get().get("datasources") == null && infos.get().get("inlineEmbeds") == null ) { + Email email; + if (infos.get().get("attachments") == null && infos.get().get("datasources") == null + && infos.get().get("inlineEmbeds") == null) { if (StringUtils.isEmpty(bodyHtml)) { email = new SimpleEmail(); email.setMsg(bodyText); @@ -482,15 +515,15 @@ public static Future send(Object... args) { htmlEmail.setTextMsg(bodyText); } email = htmlEmail; - + Map inlineEmbeds = (Map) infos.get().get("inlineEmbeds"); if (inlineEmbeds != null) { for (Map.Entry entry : inlineEmbeds.entrySet()) { - htmlEmail.embed(entry.getValue().getDataSource(), entry.getKey(), entry.getValue().getCid()); - } + htmlEmail.embed(entry.getValue().getDataSource(), entry.getKey(), entry.getValue().getCid()); + } } } - + MultiPartEmail multiPartEmail = (MultiPartEmail) email; List objectList = (List) infos.get().get("attachments"); if (objectList != null) { @@ -500,8 +533,8 @@ public static Future send(Object... args) { } // Handle DataSource - List> datasourceList = (List>) infos - .get().get("datasources"); + List> datasourceList = (List>) infos.get() + .get("datasources"); if (datasourceList != null) { for (T4 ds : datasourceList) { multiPartEmail.attach(ds._1, ds._2, ds._3, ds._4); @@ -512,54 +545,53 @@ public static Future send(Object... args) { if (from != null) { try { - InternetAddress iAddress = new InternetAddress(from.toString()); + InternetAddress iAddress = new InternetAddress(from); email.setFrom(iAddress.getAddress(), iAddress.getPersonal()); } catch (Exception e) { - email.setFrom(from.toString()); + email.setFrom(from); } } if (replyTo != null) { try { - InternetAddress iAddress = new InternetAddress(replyTo.toString()); + InternetAddress iAddress = new InternetAddress(replyTo); email.addReplyTo(iAddress.getAddress(), iAddress.getPersonal()); } catch (Exception e) { - email.addReplyTo(replyTo.toString()); + email.addReplyTo(replyTo); } } if (recipientList != null) { - for (Object recipient : recipientList) { + for (String recipient : recipientList) { try { - InternetAddress iAddress = new InternetAddress(recipient.toString()); + InternetAddress iAddress = new InternetAddress(recipient); email.addTo(iAddress.getAddress(), iAddress.getPersonal()); } catch (Exception e) { - email.addTo(recipient.toString()); + email.addTo(recipient); } } } else { throw new MailException("You must specify at least one recipient."); } - - List ccsList = (List) infos.get().get("ccs"); + List ccsList = (List) infos.get().get("ccs"); if (ccsList != null) { - for (Object cc : ccsList) { - email.addCc(cc.toString()); + for (String cc : ccsList) { + email.addCc(cc); } } - List bccsList = (List) infos.get().get("bccs"); + List bccsList = (List) infos.get().get("bccs"); if (bccsList != null) { - for (Object bcc : bccsList) { + for (String bcc : bccsList) { try { - InternetAddress iAddress = new InternetAddress(bcc.toString()); + InternetAddress iAddress = new InternetAddress(bcc); email.addBcc(iAddress.getAddress(), iAddress.getPersonal()); } catch (Exception e) { - email.addBcc(bcc.toString()); + email.addBcc(bcc); } } } @@ -586,9 +618,7 @@ public static boolean sendAndWait(Object... args) { try { Future result = send(args); return result.get(); - } catch (InterruptedException e) { - Logger.error(e, "Error while waiting Mail.send result"); - } catch (ExecutionException e) { + } catch (InterruptedException | ExecutionException e) { Logger.error(e, "Error while waiting Mail.send result"); } return false; 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/framework/src/play/mvc/Router.java b/framework/src/play/mvc/Router.java index bc2cdfa8b5..37bdf6d310 100644 --- a/framework/src/play/mvc/Router.java +++ b/framework/src/play/mvc/Router.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.lang.StringUtils; @@ -33,12 +34,11 @@ public class Router { static Pattern routePattern = new Pattern( - "^({method}GET|POST|PUT|DELETE|OPTIONS|HEAD|WS|\\*)[(]?({headers}[^)]*)(\\))?\\s+({path}.*/[^\\s]*)\\s+({action}[^\\s(]+)({params}.+)?(\\s*)$"); + "^({method}GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|WS|\\*)[(]?({headers}[^)]*)(\\))?\\s+({path}.*/[^\\s]*)\\s+({action}[^\\s(]+)({params}.+)?(\\s*)$"); /** - * Pattern used to locate a method override instruction in - * request.querystring + * Pattern used to locate a method override instruction in request.querystring */ - static Pattern methodOverride = new Pattern("^.*x-http-method-override=({method}GET|PUT|POST|DELETE).*$"); + static Pattern methodOverride = new Pattern("^.*x-http-method-override=({method}GET|PUT|POST|PATCH|DELETE).*$"); /** * Timestamp the routes file was last loaded at. */ @@ -48,11 +48,12 @@ public class Router { * Parse the routes file. This is called at startup. * * @param prefix - * The prefix that the path of all routes in this route file - * start with. This prefix should not end with a '/' character. + * The prefix that the path of all routes in this route file start with. This prefix should not end with + * a '/' character. */ public static void load(String prefix) { routes.clear(); + actionRoutesCache.clear(); parse(Play.routes, prefix); lastLoading = System.currentTimeMillis(); // Plugins @@ -60,16 +61,30 @@ public static void load(String prefix) { } /** - * This one can be called to add new route. Last added is first in the route - * list. + * This one can be called to add new route. Last added is first in the route list. + * + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param headers + * The headers */ public static void prependRoute(String method, String path, String action, String headers) { prependRoute(method, path, action, null, headers); } /** - * This one can be called to add new route. Last added is first in the route - * list. + * This one can be called to add new route. Last added is first in the route list. + * + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action */ public static void prependRoute(String method, String path, String action) { prependRoute(method, path, action, null, null); @@ -77,6 +92,19 @@ public static void prependRoute(String method, String path, String action) { /** * Add a route at the given position + * + * @param position + * The position where to insert the route + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param params + * The parameters + * @param headers + * The headers */ public static void addRoute(int position, String method, String path, String action, String params, String headers) { if (position > routes.size()) { @@ -87,6 +115,15 @@ public static void addRoute(int position, String method, String path, String act /** * Add a route at the given position + * + * @param position + * The position where to insert the route + * @param method + * The method of the route + * @param path + * The path of the route + * @param headers + * The headers */ public static void addRoute(int position, String method, String path, String headers) { addRoute(position, method, path, null, null, headers); @@ -94,6 +131,17 @@ public static void addRoute(int position, String method, String path, String hea /** * Add a route at the given position + * + * @param position + * The position where to insert the route + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param headers + * The headers */ public static void addRoute(int position, String method, String path, String action, String headers) { addRoute(position, method, path, action, null, headers); @@ -101,6 +149,13 @@ public static void addRoute(int position, String method, String path, String act /** * Add a new route. Will be first in the route list + * + * @param method + * The method of the route * @param action : The associated action + * @param path + * The path of the route + * @param action + * The associated action */ public static void addRoute(String method, String path, String action) { prependRoute(method, path, action); @@ -108,6 +163,15 @@ public static void addRoute(String method, String path, String action) { /** * Add a route at the given position + * + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param headers + * The headers */ public static void addRoute(String method, String path, String action, String headers) { addRoute(method, path, action, null, headers); @@ -115,15 +179,40 @@ public static void addRoute(String method, String path, String action, String he /** * Add a route + * + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param params + * The parameters + * @param headers + * The headers */ public static void addRoute(String method, String path, String action, String params, String headers) { appendRoute(method, path, action, params, headers, null, 0); } /** - * This is used internally when reading the route file. The order the routes - * are added matters and we want the method to append the routes to the - * list. + * This is used internally when reading the route file. The order the routes are added matters and we want the + * method to append the routes to the list. + * + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param params + * The parameters + * @param headers + * The headers + * @param sourceFile + * The source file + * @param line + * The source line */ public static void appendRoute(String method, String path, String action, String params, String headers, String sourceFile, int line) { routes.add(getRoute(method, path, action, params, headers, sourceFile, line)); @@ -151,20 +240,30 @@ public static Route getRoute(String method, String path, String action, String p /** * Add a new route at the beginning of the route list + * + * @param method + * The method of the route + * @param path + * The path of the route + * @param action + * The associated action + * @param params + * The parameters + * @param headers + * The headers */ public static void prependRoute(String method, String path, String action, String params, String headers) { routes.add(0, getRoute(method, path, action, params, headers)); } /** - * Parse a route file. If an action starts with "plugin:name", - * replace that route by the ones declared in the plugin route file denoted - * by that name, if found. + * Parse a route file. If an action starts with "plugin:name", replace that route by the ones declared in the + * plugin route file denoted by that name, if found. * * @param routeFile * @param prefix - * The prefix that the path of all routes in this route file - * start with. This prefix should not end with a '/' character. + * The prefix that the path of all routes in this route file start with. This prefix should not end with + * a '/' character. */ static void parse(VirtualFile routeFile, String prefix) { String fileAbsolutePath = routeFile.getRealFile().getAbsolutePath(); @@ -217,15 +316,16 @@ static void parse(String content, String prefix, String fileAbsolutePath) { } /** + *

    * In PROD mode and if the routes are already loaded, this does nothing. - *

    + *

    *

    - * In DEV mode, this checks each routes file's "last modified" time to see - * if the routes need updated. - * + * In DEV mode, this checks each routes file's "last modified" time to see if the routes need updated. + *

    + * * @param prefix - * The prefix that the path of all routes in this route file - * start with. This prefix should not end with a '/' character. + * The prefix that the path of all routes in this route file start with. This prefix should not end with + * a '/' character. */ public static void detectChanges(String prefix) { if (Play.mode == Mode.PROD && lastLoading > 0) { @@ -246,7 +346,7 @@ public static void detectChanges(String prefix) { /** * All the loaded routes. */ - public static List routes = new CopyOnWriteArrayList(); + public static List routes = new CopyOnWriteArrayList<>(); public static void routeOnlyStatic(Http.Request request) { for (Route route : routes) { @@ -269,13 +369,13 @@ public static Route route(Http.Request request) { if (Logger.isTraceEnabled()) { Logger.trace("Route: " + request.path + " - " + request.querystring); } - // request method may be overriden if a x-http-method-override parameter + // request method may be overridden if a x-http-method-override parameter // is given if (request.querystring != null && methodOverride.matches(request.querystring)) { Matcher matcher = methodOverride.matcher(request.querystring); if (matcher.matches()) { if (Logger.isTraceEnabled()) { - Logger.trace("request method %s overriden to %s ", request.method, matcher.group("method")); + Logger.trace("request method %s overridden to %s ", request.method, matcher.group("method")); } request.method = matcher.group("method"); } @@ -328,7 +428,7 @@ public static Map route(String method, String path, String heade return args; } } - return new HashMap(16); + return new HashMap<>(16); } public static ActionDefinition reverse(String action) { @@ -426,7 +526,7 @@ public static ActionDefinition reverse(String action, Map args) if (action.startsWith("controllers.")) { action = action.substring(12); } - Map argsbackup = new HashMap(args); + Map argsbackup = new HashMap<>(args); // Add routeArgs if (Scope.RouteArgs.current() != null) { for (String key : Scope.RouteArgs.current().data.keySet()) { @@ -435,160 +535,198 @@ public static ActionDefinition reverse(String action, Map args) } } } - for (Route route : routes) { - if (route.actionPattern != null) { - Matcher matcher = route.actionPattern.matcher(action); - if (matcher.matches()) { - for (String group : route.actionArgs) { - String v = matcher.group(group); - if (v == null) { - continue; - } - args.put(group, v.toLowerCase()); + + Http.Request request = Http.Request.current(); + String requestFormat = request == null || request.format == null ? "" : request.format; + + List matchingRoutes = getActionRoutes(action); + for (ActionRoute actionRoute : matchingRoutes) { + Route route = actionRoute.route; + args.putAll(actionRoute.args); + + List inPathArgs = new ArrayList<>(16); + boolean allRequiredArgsAreHere = true; + // les noms de parametres matchent ils ? + for (Route.Arg arg : route.args) { + inPathArgs.add(arg.name); + Object value = args.get(arg.name); + if (value == null) { + // This is a hack for reverting on hostname that are + // a regex expression. + // See [#344] for more into. This is not optimal and + // should retough. However, + // it allows us to do things like {(.*}}.domain.com + String host = route.host.replaceAll("\\{", "").replaceAll("\\}", ""); + if (host.equals(arg.name) || host.matches(arg.name)) { + args.remove(arg.name); + route.host = request == null ? "" : request.domain; + break; + } else { + allRequiredArgsAreHere = false; + break; } - List inPathArgs = new ArrayList(16); - boolean allRequiredArgsAreHere = true; - // les noms de parametres matchent ils ? - for (Route.Arg arg : route.args) { - inPathArgs.add(arg.name); - Object value = args.get(arg.name); - if (value == null) { - // This is a hack for reverting on hostname that are - // a regex expression. - // See [#344] for more into. This is not optimal and - // should retough. However, - // it allows us to do things like {(.*}}.domain.com - String host = route.host.replaceAll("\\{", "").replaceAll("\\}", ""); - if (host.equals(arg.name) || host.matches(arg.name)) { - args.remove(arg.name); - route.host = Http.Request.current() == null ? "" : Http.Request.current().domain; - break; - } else { - allRequiredArgsAreHere = false; - break; - } + } else { + if (value instanceof List) { + @SuppressWarnings("unchecked") + List l = (List) value; + value = l.get(0); + } + if (!value.toString().startsWith(":") && !arg.constraint.matches(Utils.urlEncodePath(value.toString()))) { + allRequiredArgsAreHere = false; + break; + } + } + } + // les parametres codes en dur dans la route matchent-ils ? + for (String staticKey : route.staticArgs.keySet()) { + if (staticKey.equals("format")) { + if (!requestFormat.equals(route.staticArgs.get("format"))) { + allRequiredArgsAreHere = false; + break; + } + continue; // format is a special key + } + if (!args.containsKey(staticKey) || (args.get(staticKey) == null) + || !args.get(staticKey).toString().equals(route.staticArgs.get(staticKey))) { + allRequiredArgsAreHere = false; + break; + } + } + if (allRequiredArgsAreHere) { + StringBuilder queryString = new StringBuilder(); + String path = route.path; + String host = route.host; + if (path.endsWith("/?")) { + path = path.substring(0, path.length() - 2); + } + for (Map.Entry entry : args.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (inPathArgs.contains(key) && value != null) { + if (List.class.isAssignableFrom(value.getClass())) { + @SuppressWarnings("unchecked") + List vals = (List) value; + path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", vals.get(0).toString()).replace("$", "\\$"); } else { - if (value instanceof List) { - @SuppressWarnings("unchecked") - List l = (List) value; - value = l.get(0); + try { + path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", URLEncoder.encode(value.toString(), encoding) + .replace("$", "\\$").replace("%3A", ":").replace("%40", "@").replace("+", "%20")); + } catch (UnsupportedEncodingException e) { + path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", + value.toString().replace("$", "\\$").replace("%3A", ":").replace("%40", "@").replace("+", "%20")); } - if (!value.toString().startsWith(":") && !arg.constraint.matches(Utils.urlEncodePath(value.toString()))) { - allRequiredArgsAreHere = false; - break; + try { + host = host.replaceAll("\\{(<[^>]+>)?" + key + "\\}", URLEncoder.encode(value.toString(), encoding) + .replace("$", "\\$").replace("%3A", ":").replace("%40", "@").replace("+", "%20")); + } catch (UnsupportedEncodingException e) { + host = host.replaceAll("\\{(<[^>]+>)?" + key + "\\}", + value.toString().replace("$", "\\$").replace("%3A", ":").replace("%40", "@").replace("+", "%20")); } } - } - // les parametres codes en dur dans la route matchent-ils ? - for (String staticKey : route.staticArgs.keySet()) { - if (staticKey.equals("format")) { - if (!(Http.Request.current() == null ? "" : Http.Request.current().format) - .equals(route.staticArgs.get("format"))) { - allRequiredArgsAreHere = false; - break; - } - continue; // format is a special key - } - if (!args.containsKey(staticKey) || (args.get(staticKey) == null) - || !args.get(staticKey).toString().equals(route.staticArgs.get(staticKey))) { - allRequiredArgsAreHere = false; - break; - } - } - if (allRequiredArgsAreHere) { - StringBuilder queryString = new StringBuilder(); - String path = route.path; - String host = route.host; - if (path.endsWith("/?")) { - path = path.substring(0, path.length() - 2); - } - for (Map.Entry entry : args.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - if (inPathArgs.contains(key) && value != null) { - if (List.class.isAssignableFrom(value.getClass())) { - @SuppressWarnings("unchecked") - List vals = (List) value; - path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", vals.get(0).toString()).replace("$", "\\$"); - } else { - try { - path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", URLEncoder.encode(value.toString(), encoding) - .replace("$", "\\$").replace("%3A", ":").replace("%40", "@").replace("+", "%20")); - } catch (UnsupportedEncodingException e) { - path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", value.toString().replace("$", "\\$") - .replace("%3A", ":").replace("%40", "@").replace("+", "%20")); - } - try { - host = host.replaceAll("\\{(<[^>]+>)?" + key + "\\}", URLEncoder.encode(value.toString(), encoding) - .replace("$", "\\$").replace("%3A", ":").replace("%40", "@").replace("+", "%20")); - } catch (UnsupportedEncodingException e) { - host = host.replaceAll("\\{(<[^>]+>)?" + key + "\\}", value.toString().replace("$", "\\$") - .replace("%3A", ":").replace("%40", "@").replace("+", "%20")); + } else if (route.staticArgs.containsKey(key)) { + // Do nothing -> The key is static + } else if (!argsbackup.containsKey(key)) { + // Do nothing -> The key is provided in + // RouteArgs and not used (see #447) + } else if (value != null) { + if (List.class.isAssignableFrom(value.getClass())) { + @SuppressWarnings("unchecked") + List vals = (List) value; + for (Object object : vals) { + try { + queryString.append(URLEncoder.encode(key, encoding)); + queryString.append("="); + String objStr = object.toString(); + // Special case to handle jsAction + // tag + if (objStr.startsWith(":") && objStr.length() > 1) { + queryString.append(':'); + objStr = objStr.substring(1); } + queryString.append(URLEncoder.encode(objStr + "", encoding)); + queryString.append("&"); + } catch (UnsupportedEncodingException ex) { } - } else if (route.staticArgs.containsKey(key)) { - // Do nothing -> The key is static - } else if (!argsbackup.containsKey(key)) { - // Do nothing -> The key is provided in - // RouteArgs and not used (see #447) - } else if (value != null) { - if (List.class.isAssignableFrom(value.getClass())) { - @SuppressWarnings("unchecked") - List vals = (List) value; - for (Object object : vals) { - try { - queryString.append(URLEncoder.encode(key, encoding)); - queryString.append("="); - String objStr = object.toString(); - // Special case to handle jsAction - // tag - if (objStr.startsWith(":") && objStr.length() > 1) { - queryString.append(':'); - objStr = objStr.substring(1); - } - queryString.append(URLEncoder.encode(objStr + "", encoding)); - queryString.append("&"); - } catch (UnsupportedEncodingException ex) { - } - } - } else if (value.getClass().equals(Default.class)) { - // Skip defaults in queryString - } else { - try { - queryString.append(URLEncoder.encode(key, encoding)); - queryString.append("="); - String objStr = value.toString(); - // Special case to handle jsAction tag - if (objStr.startsWith(":") && objStr.length() > 1) { - queryString.append(':'); - objStr = objStr.substring(1); - } - queryString.append(URLEncoder.encode(objStr + "", encoding)); - queryString.append("&"); - } catch (UnsupportedEncodingException ex) { - } + } + } else if (value.getClass().equals(Default.class)) { + // Skip defaults in queryString + } else { + try { + queryString.append(URLEncoder.encode(key, encoding)); + queryString.append("="); + String objStr = value.toString(); + // Special case to handle jsAction tag + if (objStr.startsWith(":") && objStr.length() > 1) { + queryString.append(':'); + objStr = objStr.substring(1); } + queryString.append(URLEncoder.encode(objStr + "", encoding)); + queryString.append("&"); + } catch (UnsupportedEncodingException ex) { } } - String qs = queryString.toString(); - if (qs.endsWith("&")) { - qs = qs.substring(0, qs.length() - 1); - } - ActionDefinition actionDefinition = new ActionDefinition(); - actionDefinition.url = qs.length() == 0 ? path : path + "?" + qs; - actionDefinition.method = route.method == null || route.method.equals("*") ? "GET" : route.method.toUpperCase(); - actionDefinition.star = "*".equals(route.method); - actionDefinition.action = action; - actionDefinition.args = argsbackup; - actionDefinition.host = host; - return actionDefinition; } } + String qs = queryString.toString(); + if (qs.endsWith("&")) { + qs = qs.substring(0, qs.length() - 1); + } + ActionDefinition actionDefinition = new ActionDefinition(); + actionDefinition.url = qs.length() == 0 ? path : path + "?" + qs; + actionDefinition.method = route.method == null || route.method.equals("*") ? "GET" : route.method.toUpperCase(); + actionDefinition.star = "*".equals(route.method); + actionDefinition.action = action; + actionDefinition.args = argsbackup; + actionDefinition.host = host; + if (Boolean.parseBoolean(Play.configuration.getProperty("application.forceSecureReverseRoutes", "false"))) { + actionDefinition.secure(); + } + return actionDefinition; } } + throw new NoRouteFoundException(action, args); } + private static final Map> actionRoutesCache = new ConcurrentHashMap<>(); + + private static List getActionRoutes(String action) { + List matchingRoutes = actionRoutesCache.get(action); + if (matchingRoutes == null) { + matchingRoutes = findActionRoutes(action); + actionRoutesCache.put(action, matchingRoutes); + } + return matchingRoutes; + } + + private static List findActionRoutes(String action) { + List matchingRoutes = new ArrayList<>(2); + for (Router.Route route : routes) { + if (route.actionPattern != null) { + Matcher matcher = route.actionPattern.matcher(action); + if (matcher.matches()) { + ActionRoute matchingRoute = new ActionRoute(); + matchingRoute.route = route; + + for (String group : route.actionArgs) { + String v = matcher.group(group); + if (v == null) { + continue; + } + matchingRoute.args.put(group, v.toLowerCase()); + } + matchingRoutes.add(matchingRoute); + } + } + } + return matchingRoutes; + } + + private static final class ActionRoute { + private Route route; + private Map args = new HashMap<>(2); + } + public static class ActionDefinition { /** @@ -600,7 +738,7 @@ public static class ActionDefinition { */ public String method; /** - * @todo - what is this? does it include the domain? + * FIXME - what is this? does it include the domain? */ public String url; /** @@ -608,12 +746,11 @@ public static class ActionDefinition { */ public boolean star; /** - * @todo - what is this? does it include the class and package? + * FIXME - what is this? does it include the class and package? */ public String action; /** - * @todo - are these the required args in the routing file, or the query - * string in a request? + * FIXME - are these the required args in the routing file, or the query string in a request? */ public Map args; @@ -684,18 +821,18 @@ public static class Route { public String method; public String path; /** - * @todo - what is this? + * FIXME - what is this? */ public String action; Pattern actionPattern; - List actionArgs = new ArrayList(3); + List actionArgs = new ArrayList<>(3); String staticDir; boolean staticFile; Pattern pattern; Pattern hostPattern; - List args = new ArrayList(3); - Map staticArgs = new HashMap(3); - List formats = new ArrayList(1); + List args = new ArrayList<>(3); + Map staticArgs = new HashMap<>(3); + List formats = new ArrayList<>(1); String host; Arg hostArg = null; public int routesFileLine; @@ -813,7 +950,7 @@ public void addParams(String params) { } params = params.substring(1, params.length() - 1); for (String param : params.split(",")) { - Matcher matcher = paramPattern.matcher(param); + Matcher matcher = paramPattern.matcher(param.trim()); if (matcher.matches()) { staticArgs.put(matcher.group(1), matcher.group(2)); } else { @@ -861,8 +998,7 @@ public Map matches(String method, String path, String accept) { * @param method * GET/POST/etc. * @param path - * Part after domain and before query-string. Starts with a - * "/". + * Part after domain and before query-string. Starts with a "/". * @param accept * Format, e.g. html. * @param domain @@ -910,7 +1046,7 @@ public Map matches(String method, String path, String accept, St } throw new NotFound(resource); } else { - Map localArgs = new HashMap(); + Map localArgs = new HashMap<>(); for (Arg arg : args) { // FIXME: Careful with the arguments that are not // matching as they are part of the hostname diff --git a/framework/src/play/mvc/Scope.java b/framework/src/play/mvc/Scope.java index da2b7d5371..bf1b5695fc 100644 --- a/framework/src/play/mvc/Scope.java +++ b/framework/src/play/mvc/Scope.java @@ -15,9 +15,9 @@ import play.data.binding.RootParamNode; import play.data.parsing.DataParser; import play.data.parsing.DataParsers; -import play.data.parsing.TextParser; import play.data.validation.Validation; import play.exceptions.UnexpectedException; +import play.i18n.Messages; import play.libs.Codec; import play.libs.Crypto; import play.libs.Time; @@ -29,18 +29,21 @@ public class Scope { public static final String COOKIE_PREFIX = Play.configuration.getProperty("application.session.cookie", "PLAY"); - public static final boolean COOKIE_SECURE = Play.configuration.getProperty("application.session.secure", "false").toLowerCase().equals("true"); + public static final boolean COOKIE_SECURE = Play.configuration.getProperty("application.session.secure", "false").toLowerCase() + .equals("true"); public static final String COOKIE_EXPIRE = Play.configuration.getProperty("application.session.maxAge"); - public static final boolean SESSION_HTTPONLY = Play.configuration.getProperty("application.session.httpOnly", "false").toLowerCase().equals("true"); - public static final boolean SESSION_SEND_ONLY_IF_CHANGED = Play.configuration.getProperty("application.session.sendOnlyIfChanged", "false").toLowerCase().equals("true"); + public static final boolean SESSION_HTTPONLY = Play.configuration.getProperty("application.session.httpOnly", "false").toLowerCase() + .equals("true"); + public static final boolean SESSION_SEND_ONLY_IF_CHANGED = Play.configuration + .getProperty("application.session.sendOnlyIfChanged", "false").toLowerCase().equals("true"); /** * Flash scope */ public static class Flash { - Map data = new HashMap(); - Map out = new HashMap(); + Map data = new HashMap<>(); + Map out = new HashMap<>(); public static Flash restore() { try { @@ -61,7 +64,7 @@ void save() { return; } if (out.isEmpty()) { - if(Http.Request.current().cookies.containsKey(COOKIE_PREFIX + "_FLASH") || !SESSION_SEND_ONLY_IF_CHANGED) { + if (Http.Request.current().cookies.containsKey(COOKIE_PREFIX + "_FLASH") || !SESSION_SEND_ONLY_IF_CHANGED) { Http.Response.current().setCookie(COOKIE_PREFIX + "_FLASH", "", null, "/", 0, COOKIE_SECURE, SESSION_HTTPONLY); } return; @@ -72,8 +75,9 @@ void save() { } catch (Exception e) { throw new UnexpectedException("Flash serializationProblem", e); } - } // ThreadLocal access - public static ThreadLocal current = new ThreadLocal(); + } // ThreadLocal access + + public static final ThreadLocal current = new ThreadLocal<>(); public static Flash current() { return current.get(); @@ -102,11 +106,11 @@ public void now(String key, String value) { } public void error(String value, Object... args) { - put("error", String.format(value, args)); + put("error", Messages.get(value, args)); } public void success(String value, Object... args) { - put("success", String.format(value, args)); + put("success", Messages.get(value, args)); } public void discard(String key) { @@ -162,38 +166,40 @@ public static Session restore() { try { Session session = new Session(); Http.Cookie cookie = Http.Request.current().cookies.get(COOKIE_PREFIX + "_SESSION"); - final int duration = Time.parseDuration(COOKIE_EXPIRE) ; - final long expiration = (duration * 1000l); + int duration = Time.parseDuration(COOKIE_EXPIRE); + long expiration = (duration * 1000l); if (cookie != null && Play.started && cookie.value != null && !cookie.value.trim().equals("")) { String value = cookie.value; - int firstDashIndex = value.indexOf("-"); - if(firstDashIndex > -1) { - String sign = value.substring(0, firstDashIndex); - String data = value.substring(firstDashIndex + 1); - if (CookieDataCodec.safeEquals(sign, Crypto.sign(data, Play.secretKey.getBytes()))) { + int firstDashIndex = value.indexOf("-"); + if (firstDashIndex > -1) { + String sign = value.substring(0, firstDashIndex); + String data = value.substring(firstDashIndex + 1); + if (CookieDataCodec.safeEquals(sign, Crypto.sign(data, Play.secretKey.getBytes()))) { CookieDataCodec.decode(session.data, data); - } - } + } + } if (COOKIE_EXPIRE != null) { - // Verify that the session contains a timestamp, and that it's not expired - if (!session.contains(TS_KEY)) { + // Verify that the session contains a timestamp, and + // that it's not expired + if (!session.contains(TS_KEY)) { session = new Session(); } else { - if ((Long.parseLong(session.get(TS_KEY))) < System.currentTimeMillis()) { + if ((Long.parseLong(session.get(TS_KEY))) < System.currentTimeMillis()) { // Session expired session = new Session(); } } - session.put(TS_KEY, System.currentTimeMillis() + expiration); + session.put(TS_KEY, System.currentTimeMillis() + expiration); } else { // Just restored. Nothing changed. No cookie-expire. session.changed = false; } } else { - // no previous cookie to restore; but we may have to set the timestamp in the new cookie - if (COOKIE_EXPIRE != null) { - session.put(TS_KEY, (System.currentTimeMillis() + expiration)); + // no previous cookie to restore; but we may have to set the + // timestamp in the new cookie + if (COOKIE_EXPIRE != null) { + session.put(TS_KEY, (System.currentTimeMillis() + expiration)); } } @@ -202,9 +208,10 @@ public static Session restore() { throw new UnexpectedException("Corrupted HTTP session from " + Http.Request.current().remoteAddress, e); } } - Map data = new HashMap(); // ThreadLocal access + + Map data = new HashMap<>(); // ThreadLocal access boolean changed = false; - public static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); public static Session current() { return current.get(); @@ -238,13 +245,14 @@ void save() { // Some request like WebSocket don't have any response return; } - if(!changed && SESSION_SEND_ONLY_IF_CHANGED && COOKIE_EXPIRE == null) { - // Nothing changed and no cookie-expire, consequently send nothing back. + if (!changed && SESSION_SEND_ONLY_IF_CHANGED && COOKIE_EXPIRE == null) { + // Nothing changed and no cookie-expire, consequently send + // nothing back. return; } if (isEmpty()) { // The session is empty: delete the cookie - if(Http.Request.current().cookies.containsKey(COOKIE_PREFIX + "_SESSION") || !SESSION_SEND_ONLY_IF_CHANGED) { + if (Http.Request.current().cookies.containsKey(COOKIE_PREFIX + "_SESSION") || !SESSION_SEND_ONLY_IF_CHANGED) { Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", "", null, "/", 0, COOKIE_SECURE, SESSION_HTTPONLY); } return; @@ -253,9 +261,11 @@ void save() { String sessionData = CookieDataCodec.encode(data); String sign = Crypto.sign(sessionData, Play.secretKey.getBytes()); if (COOKIE_EXPIRE == null) { - Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/", null, COOKIE_SECURE, SESSION_HTTPONLY); + Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/", null, COOKIE_SECURE, + SESSION_HTTPONLY); } else { - Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/", Time.parseDuration(COOKIE_EXPIRE), COOKIE_SECURE, SESSION_HTTPONLY); + Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/", + Time.parseDuration(COOKIE_EXPIRE), COOKIE_SECURE, SESSION_HTTPONLY); } } catch (Exception e) { throw new UnexpectedException("Session serializationProblem", e); @@ -278,8 +288,9 @@ public void put(String key, Object value) { change(); if (value == null) { put(key, (String) null); + } else { + put(key, value.toString()); } - put(key, value + ""); } public String get(String key) { @@ -303,8 +314,9 @@ public void clear() { } /** - * Returns true if the session is empty, - * e.g. does not contain anything else than the timestamp + * Returns true if the session is empty, e.g. does not contain anything else than the timestamp + * + * @return true if the session is empty, otherwise false */ public boolean isEmpty() { for (String key : data.keySet()) { @@ -331,13 +343,14 @@ public String toString() { public static class Params { // ThreadLocal access - public static ThreadLocal current = new ThreadLocal(); + public static final ThreadLocal current = new ThreadLocal<>(); public static Params current() { return current.get(); } + boolean requestIsParsed; - public Map data = new LinkedHashMap(); + public Map data = new LinkedHashMap<>(); boolean rootParamsNodeIsGenerated = false; private RootParamNode rootParamNode = null; @@ -380,7 +393,7 @@ public void checkAndParse() { public void put(String key, String value) { checkAndParse(); - data.put(key, new String[]{value}); + data.put(key, new String[] { value }); // make sure rootsParamsNode is regenerated if needed rootParamsNodeIsGenerated = false; } @@ -398,7 +411,7 @@ public void remove(String key) { // make sure rootsParamsNode is regenerated if needed rootParamsNodeIsGenerated = false; } - + public void removeStartWith(String prefix) { checkAndParse(); Iterator> iterator = data.entrySet().iterator(); @@ -426,9 +439,11 @@ public String get(String key) { public T get(String key, Class type) { try { checkAndParse(); - // TODO: This is used by the test, but this is not the most convenient. + // TODO: This is used by the test, but this is not the most + // convenient. return (T) Binder.bind(getRootParamNode(), key, type, type, null); - } catch (Exception e) { + } catch (RuntimeException e) { + Logger.error(e, "Failed to get %s of type %s", key, type); Validation.addError(key, "validation.invalid"); return null; } @@ -439,6 +454,7 @@ public T get(Annotation[] annotations, String key, Class type) { try { return (T) Binder.directBind(annotations, get(key), type, null); } catch (Exception e) { + Logger.error(e, "Failed to get %s of type %s", key, type); Validation.addError(key, "validation.invalid"); return null; } @@ -462,7 +478,7 @@ public Map all() { public Map sub(String prefix) { checkAndParse(); - Map result = new LinkedHashMap(); + Map result = new LinkedHashMap<>(); for (String key : data.keySet()) { if (key.startsWith(prefix + ".")) { result.put(key.substring(prefix.length() + 1), data.get(key)); @@ -473,7 +489,7 @@ public Map sub(String prefix) { public Map allSimple() { checkAndParse(); - Map result = new HashMap(); + Map result = new HashMap<>(); for (String key : data.keySet()) { result.put(key, data.get(key)[0]); } @@ -561,8 +577,8 @@ public String toString() { */ public static class RenderArgs { - public Map data = new HashMap(); // ThreadLocal access - public static ThreadLocal current = new ThreadLocal(); + public Map data = new HashMap<>(); // ThreadLocal access + public static final ThreadLocal current = new ThreadLocal<>(); public static RenderArgs current() { return current.get(); @@ -592,8 +608,8 @@ public String toString() { */ public static class RouteArgs { - public Map data = new HashMap(); // ThreadLocal access - public static ThreadLocal current = new ThreadLocal(); + public Map data = new HashMap<>(); // ThreadLocal access + public static final ThreadLocal current = new ThreadLocal<>(); public static RouteArgs current() { return current.get(); diff --git a/framework/src/play/mvc/With.java b/framework/src/play/mvc/With.java index 2be386d248..6deb5b1bfb 100644 --- a/framework/src/play/mvc/With.java +++ b/framework/src/play/mvc/With.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * Deleguate interceptors. + * Delegate interceptors. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/framework/src/play/mvc/results/BadRequest.java b/framework/src/play/mvc/results/BadRequest.java index 812fdab6c1..623aea4d8f 100644 --- a/framework/src/play/mvc/results/BadRequest.java +++ b/framework/src/play/mvc/results/BadRequest.java @@ -1,24 +1,13 @@ package play.mvc.results; import play.mvc.Http; -import play.mvc.Http.Request; -import play.mvc.Http.Response; /** * 400 Bad Request */ -public class BadRequest extends Result { +public class BadRequest extends Error { - public BadRequest(String msg) { - super(msg); - } - - public BadRequest() { - } - - @Override - public void apply(Request request, Response response) { - response.status = Http.StatusCode.BAD_REQUEST; + public BadRequest(String msg) { + super(Http.StatusCode.BAD_REQUEST, msg); } - } diff --git a/framework/src/play/mvc/results/Error.java b/framework/src/play/mvc/results/Error.java index 172ef9d1cd..b5ea5523db 100644 --- a/framework/src/play/mvc/results/Error.java +++ b/framework/src/play/mvc/results/Error.java @@ -1,7 +1,6 @@ package play.mvc.results; import java.util.Map; -import play.Logger; import play.Play; import play.exceptions.UnexpectedException; @@ -17,7 +16,7 @@ */ public class Error extends Result { - private int status; + private final int status; public Error(String reason) { super(reason); @@ -29,6 +28,7 @@ public Error(int status, String reason) { this.status = status; } + @Override public void apply(Request request, Response response) { response.status = status; String format = request.format; @@ -48,7 +48,7 @@ public void apply(Request request, Response response) { try { errorHtml = TemplateLoader.load("errors/" + this.status + "." + (format == null ? "html" : format)).render(binding); } catch (Exception e) { - Logger.warn(e, "Error page caused an error"); + // no template in desired format, just display the default response } try { response.out.write(errorHtml.getBytes(getEncoding())); @@ -56,4 +56,8 @@ public void apply(Request request, Response response) { throw new UnexpectedException(e); } } + + public int getStatus() { + return status; + } } diff --git a/framework/src/play/mvc/results/Forbidden.java b/framework/src/play/mvc/results/Forbidden.java index 962a7080b9..2baa5ec8ad 100644 --- a/framework/src/play/mvc/results/Forbidden.java +++ b/framework/src/play/mvc/results/Forbidden.java @@ -1,49 +1,13 @@ package play.mvc.results; -import java.util.Map; - -import play.Play; -import play.exceptions.UnexpectedException; -import play.libs.MimeTypes; import play.mvc.Http; -import play.mvc.Http.Request; -import play.mvc.Http.Response; -import play.mvc.Scope; -import play.templates.TemplateLoader; /** * 403 Forbidden */ -public class Forbidden extends Result { +public class Forbidden extends Error { public Forbidden(String reason) { - super(reason); - } - - public void apply(Request request, Response response) { - response.status = Http.StatusCode.FORBIDDEN; - String format = request.format; - if(request.isAjax() && "html".equals(format)) { - format = "txt"; - } - response.contentType = MimeTypes.getContentType("xx."+format); - Map binding = Scope.RenderArgs.current().data; - binding.put("result", this); - binding.put("session", Scope.Session.current()); - binding.put("request", Http.Request.current()); - binding.put("flash", Scope.Flash.current()); - binding.put("params", Scope.Params.current()); - binding.put("play", new Play()); - String errorHtml = getMessage(); - try { - errorHtml = TemplateLoader.load("errors/403."+(format == null ? "html" : format)).render(binding); - } catch(Exception e) { - } - try { - response.out.write(errorHtml.getBytes(getEncoding())); - } catch (Exception e) { - throw new UnexpectedException(e); - } + super(Http.StatusCode.FORBIDDEN, reason); } - } diff --git a/framework/src/play/mvc/results/NotFound.java b/framework/src/play/mvc/results/NotFound.java index c2e75246f5..aef6e560b6 100644 --- a/framework/src/play/mvc/results/NotFound.java +++ b/framework/src/play/mvc/results/NotFound.java @@ -31,6 +31,7 @@ public NotFound(String method, String path) { super(method + " " + path); } + @Override public void apply(Request request, Response response) { response.status = Http.StatusCode.NOT_FOUND; String format = request.format; diff --git a/framework/src/play/mvc/results/NotModified.java b/framework/src/play/mvc/results/NotModified.java index 214b32c30d..c785fb081f 100644 --- a/framework/src/play/mvc/results/NotModified.java +++ b/framework/src/play/mvc/results/NotModified.java @@ -9,7 +9,7 @@ */ public class NotModified extends Result { - String etag; + private String etag; public NotModified() { super("NotModified"); @@ -19,10 +19,15 @@ public NotModified(String etag) { this.etag = etag; } + @Override public void apply(Request request, Response response) { response.status = Http.StatusCode.NOT_MODIFIED; if (etag != null) { response.setHeader("Etag", etag); } } + + public String getEtag() { + return etag; + } } diff --git a/framework/src/play/mvc/results/Ok.java b/framework/src/play/mvc/results/Ok.java index 8f0f2482b5..a721ac31a0 100644 --- a/framework/src/play/mvc/results/Ok.java +++ b/framework/src/play/mvc/results/Ok.java @@ -14,6 +14,7 @@ public Ok() { super("OK"); } + @Override public void apply(Request request, Response response) { response.status = Http.StatusCode.OK; } diff --git a/framework/src/play/mvc/results/Redirect.java b/framework/src/play/mvc/results/Redirect.java index 4badd2c4f0..21454de089 100644 --- a/framework/src/play/mvc/results/Redirect.java +++ b/framework/src/play/mvc/results/Redirect.java @@ -1,13 +1,13 @@ package play.mvc.results; -import java.util.Map; -import java.util.Map.Entry; - import play.exceptions.UnexpectedException; import play.mvc.Http; import play.mvc.Http.Request; import play.mvc.Http.Response; +import java.util.Map; +import java.util.Map.Entry; + /** * 302 Redirect */ @@ -19,29 +19,29 @@ public class Redirect extends Result { public Redirect(String url) { this.url = url; } - - /** - * Redirects to a given URL with the parameters specified in a {@link Map} - * - * @param url - * The URL to redirect to as a {@link String} - * @param parameters - * Parameters to be included at the end of the URL as a HTTP GET. This is a map whose entries are written out as key1=value1&key2=value2 etc.. - */ - public Redirect(String url, Map parameters) { - StringBuffer urlSb = new StringBuffer(url); - char prepend = '?'; - if (parameters != null && parameters.size() > 0) { + /** + * Redirects to a given URL with the parameters specified in a {@link Map} + * + * @param url + * The URL to redirect to as a {@link String} + * @param parameters + * Parameters to be included at the end of the URL as a HTTP GET. This is a map whose entries are written out as key1=value1&key2=value2 etc.. + */ + public Redirect(String url, Map parameters) { + StringBuilder urlSb = new StringBuilder(url); + + if (parameters != null && !parameters.isEmpty()) { + char prepend = '?'; - for (Entry parameter : parameters.entrySet()) { - urlSb.append(prepend).append(parameter.getKey()).append('=').append(parameter.getValue()); - prepend = '&'; - } - } + for (Entry parameter : parameters.entrySet()) { + urlSb.append(prepend).append(parameter.getKey()).append('=').append(parameter.getValue()); + prepend = '&'; + } + } - this.url = urlSb.toString(); - } + this.url = urlSb.toString(); + } public Redirect(String url,boolean permanent) { this.url = url; @@ -54,10 +54,11 @@ public Redirect(String url,int code) { this.code=code; } + @Override public void apply(Request request, Response response) { try { - // do not touch any valid uri: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30 - if (url.matches("^\\w+://.*")) { + // do not touch any valid uri: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30 + if (url.matches("^\\w+://.*")) { } else if (url.startsWith("/")) { url = String.format("http%s://%s%s%s", request.secure ? "s" : "", request.domain, (request.port == 80 || request.port == 443) ? "" : ":" + request.port, url); } else { @@ -69,4 +70,12 @@ public void apply(Request request, Response response) { throw new UnexpectedException(e); } } + + public String getUrl() { + return url; + } + + public int getCode() { + return code; + } } diff --git a/framework/src/play/mvc/results/RedirectToStatic.java b/framework/src/play/mvc/results/RedirectToStatic.java index fec84e9e54..a8c16f4fb0 100644 --- a/framework/src/play/mvc/results/RedirectToStatic.java +++ b/framework/src/play/mvc/results/RedirectToStatic.java @@ -10,12 +10,13 @@ */ public class RedirectToStatic extends Result { - String file; + private final String file; public RedirectToStatic(String file) { this.file = file; } - + + @Override public void apply(Request request, Response response) { try { response.status = Http.StatusCode.FOUND; @@ -24,4 +25,8 @@ public void apply(Request request, Response response) { throw new UnexpectedException(e); } } + + public String getFile() { + return file; + } } diff --git a/framework/src/play/mvc/results/RenderBinary.java b/framework/src/play/mvc/results/RenderBinary.java index 8e565c040a..559c3e504d 100644 --- a/framework/src/play/mvc/results/RenderBinary.java +++ b/framework/src/play/mvc/results/RenderBinary.java @@ -1,18 +1,22 @@ package play.mvc.results; -import org.apache.commons.codec.net.URLCodec; -import org.apache.commons.io.IOUtils; -import play.exceptions.UnexpectedException; -import play.libs.MimeTypes; -import play.mvc.Http.Request; -import play.mvc.Http.Response; +import static org.apache.commons.io.IOUtils.closeQuietly; import java.io.File; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; -import static org.apache.commons.io.IOUtils.closeQuietly; +import org.apache.commons.codec.net.URLCodec; +import org.apache.commons.io.IOUtils; + +import play.exceptions.UnexpectedException; +import play.libs.MimeTypes; +import play.mvc.Http.Request; +import play.mvc.Http.Response; /** * 200 OK with application/octet-stream @@ -22,18 +26,21 @@ public class RenderBinary extends Result { private static final String INLINE_DISPOSITION_TYPE = "inline"; private static final String ATTACHMENT_DISPOSITION_TYPE = "attachment"; - private static URLCodec encoder = new URLCodec(); - boolean inline = false; - long length = 0; - File file; - InputStream is; - String name; - String contentType; + private static final URLCodec encoder = new URLCodec(); + private final boolean inline; + private long length; + private File file; + private InputStream is; + private final String name; + private String contentType; /** - * send a binary stream as the response - * @param is the stream to read from - * @param name the name to use as Content-Diposition attachement filename + * Send a binary stream as the response + * + * @param is + * the stream to read from + * @param name + * the name to use as Content-Disposition attachment filename */ public RenderBinary(InputStream is, String name) { this(is, name, false); @@ -44,20 +51,30 @@ public RenderBinary(InputStream is, String name, long length) { } /** - * send a binary stream as the response - * @param is the stream to read from - * @param name the name to use as Content-Diposition attachement filename - * @param inline true to set the response Content-Disposition to inline + * Send a binary stream as the response + * + * @param is + * the stream to read from + * @param name + * the name to use as Content-Disposition attachment filename + * @param inline + * true to set the response Content-Disposition to inline */ public RenderBinary(InputStream is, String name, boolean inline) { this(is, name, null, inline); } /** - * send a binary stream as the response - * @param is the stream to read from - * @param name the name to use as Content-Diposition attachement filename - * @param inline true to set the response Content-Disposition to inline + * Send a binary stream as the response + * + * @param is + * the stream to read from + * @param name + * the name to use as Content-Disposition attachment filename + * @param contentType + * The content type of the stream + * @param inline + * true to set the response Content-Disposition to inline */ public RenderBinary(InputStream is, String name, String contentType, boolean inline) { this.is = is; @@ -65,7 +82,7 @@ public RenderBinary(InputStream is, String name, String contentType, boolean inl this.contentType = contentType; this.inline = inline; } - + public RenderBinary(InputStream is, String name, long length, String contentType, boolean inline) { this.is = is; this.name = name; @@ -82,105 +99,139 @@ public RenderBinary(InputStream is, String name, long length, boolean inline) { } /** - * Send a file as the response. Content-disposion is set to attachment. + * Send a file as the response. Content-disposition is set to attachment. * - * @param file readable file to send back - * @param name a name to use as Content-disposion's filename + * @param file + * readable file to send back + * @param name + * a name to use as Content-disposition's filename */ public RenderBinary(File file, String name) { this(file, name, false); - if (file == null) { - throw new RuntimeException("file is null"); - } } /** - * Send a file as the response. - * Content-disposion is set to attachment, name is taken from file's name - * @param file readable file to send back + * Send a file as the response. Content-disposition is set to attachment, name is taken from file's name + * + * @param file + * readable file to send back */ public RenderBinary(File file) { this(file, file.getName(), true); } /** - * Send a file as the response. - * Content-disposion is set to attachment, name is taken from file's name - * @param file readable file to send back + * Send a file as the response. Content-disposition is set to attachment, name is taken from file's name + * + * @param file + * readable file to send back + * @param name + * a name to use as Content-disposition's filename + * @param inline + * true to set the response Content-Disposition to inline */ public RenderBinary(File file, String name, boolean inline) { - this.file = file; - this.name = name; - this.inline = inline; if (file == null) { throw new RuntimeException("file is null"); } + this.file = file; + this.name = name; + this.inline = inline; } @Override public void apply(Request request, Response response) { + if (name != null) { + setContentTypeIfNotSet(response, MimeTypes.getContentType(name)); + } + if (contentType != null) { + response.contentType = contentType; + } try { - if (name != null) { - setContentTypeIfNotSet(response, MimeTypes.getContentType(name)); - } - if (contentType != null) { - response.contentType = contentType; - } - String dispositionType; - if(inline) { - dispositionType = INLINE_DISPOSITION_TYPE; - } else { - dispositionType = ATTACHMENT_DISPOSITION_TYPE; - } if (!response.headers.containsKey("Content-Disposition")) { - if(name == null) { - response.setHeader("Content-Disposition", dispositionType); - } else { - if(canAsciiEncode(name)) { - String contentDisposition = "%s; filename=\"%s\""; - response.setHeader("Content-Disposition", String.format(contentDisposition, dispositionType, name)); - } else { - final String encoding = getEncoding(); - String contentDisposition = "%1$s; filename*="+encoding+"''%2$s; filename=\"%2$s\""; - response.setHeader("Content-Disposition", String.format(contentDisposition, dispositionType, encoder.encode(name, encoding))); - } - } + addContentDispositionHeader(response); } if (file != null) { - if (!file.exists()) { - throw new UnexpectedException("Your file does not exists (" + file + ")"); - } - if (!file.canRead()) { - throw new UnexpectedException("Can't read your file (" + file + ")"); - } - if (!file.isFile()) { - throw new UnexpectedException("Your file is not a real file (" + file + ")"); - } - response.direct = file; + renderFile(file, response); } else { - if (response.getHeader("Content-Length") != null) { - response.direct = is; - } else { - if (length != 0) { - response.setHeader("Content-Length", length + ""); - response.direct = is; - } else { - try { - IOUtils.copyLarge(is, response.out); - } - finally { - closeQuietly(is); - } - } - } + renderInputStream(is, length, response); } } catch (Exception e) { throw new UnexpectedException(e); } } + private void addContentDispositionHeader(Response response) throws UnsupportedEncodingException { + if (name == null) { + response.setHeader("Content-Disposition", dispositionType()); + } else if (canAsciiEncode(name)) { + String contentDisposition = "%s; filename=\"%s\""; + response.setHeader("Content-Disposition", String.format(contentDisposition, dispositionType(), name)); + } else { + String encoding = getEncoding(); + String contentDisposition = "%1$s; filename*=" + encoding + "''%2$s; filename=\"%2$s\""; + response.setHeader("Content-Disposition", String.format(contentDisposition, dispositionType(), encoder.encode(name, encoding))); + } + } + + private static void renderFile(File file, Response response) { + if (!file.exists()) { + throw new UnexpectedException("Your file does not exists (" + file + ")"); + } + if (!file.canRead()) { + throw new UnexpectedException("Can't read your file (" + file + ")"); + } + if (!file.isFile()) { + throw new UnexpectedException("Your file is not a real file (" + file + ")"); + } + response.direct = file; + } + + private static void renderInputStream(InputStream is, long length, Response response) throws IOException { + if (response.getHeader("Content-Length") != null) { + response.direct = is; + } else if (length != 0) { + response.setHeader("Content-Length", String.valueOf(length)); + response.direct = is; + } else { + copyInputStreamAndClose(is, response.out); + } + } + + private String dispositionType() { + return inline ? INLINE_DISPOSITION_TYPE : ATTACHMENT_DISPOSITION_TYPE; + } + + private static void copyInputStreamAndClose(InputStream is, OutputStream out) throws IOException { + try { + IOUtils.copyLarge(is, out); + } finally { + closeQuietly(is); + } + } + private boolean canAsciiEncode(String string) { CharsetEncoder asciiEncoder = Charset.forName("US-ASCII").newEncoder(); return asciiEncoder.canEncode(string); } + + public boolean isInline() { + return inline; + } + + public long getLength() { + return length; + } + + public File getFile() { + return file; + } + + public String getName() { + return name; + } + + public String getContentType() { + return contentType; + } } diff --git a/framework/src/play/mvc/results/RenderHtml.java b/framework/src/play/mvc/results/RenderHtml.java index 05f50a56d2..ede4d76b17 100644 --- a/framework/src/play/mvc/results/RenderHtml.java +++ b/framework/src/play/mvc/results/RenderHtml.java @@ -9,19 +9,23 @@ */ public class RenderHtml extends Result { - String text; + private final String html; - public RenderHtml(CharSequence text) { - this.text = text.toString(); + public RenderHtml(CharSequence html) { + this.html = html.toString(); } + @Override public void apply(Request request, Response response) { try { setContentTypeIfNotSet(response, "text/html"); - response.out.write(text.getBytes(getEncoding())); + response.out.write(html.getBytes(getEncoding())); } catch(Exception e) { throw new UnexpectedException(e); } } + public String getHtml() { + return html; + } } diff --git a/framework/src/play/mvc/results/RenderJson.java b/framework/src/play/mvc/results/RenderJson.java index 248c0d4e8d..57f8f4fccd 100644 --- a/framework/src/play/mvc/results/RenderJson.java +++ b/framework/src/play/mvc/results/RenderJson.java @@ -5,52 +5,77 @@ import com.google.gson.JsonSerializer; import java.lang.reflect.Method; import java.lang.reflect.Type; + +import play.exceptions.UnexpectedException; import play.mvc.Http.Request; import play.mvc.Http.Response; -import play.exceptions.UnexpectedException; /** * 200 OK with application/json */ public class RenderJson extends Result { - String json; + private static final Gson GSON = new Gson(); + + private final String json; + private final Object response; - public RenderJson(Object o) { - json = new Gson().toJson(o); + public RenderJson(Object response) { + this.response = response; + json = GSON.toJson(response); } - public RenderJson(Object o, Type type) { - json = new Gson().toJson(o, type); + public RenderJson(Object response, Type type) { + this.response = response; + json = GSON.toJson(response, type); } - public RenderJson(Object o, JsonSerializer... adapters) { + public RenderJson(Object response, JsonSerializer... adapters) { + this.response = response; GsonBuilder gson = new GsonBuilder(); for (Object adapter : adapters) { Type t = getMethod(adapter.getClass(), "serialize").getParameterTypes()[0]; gson.registerTypeAdapter(t, adapter); } - json = gson.create().toJson(o); + json = gson.create().toJson(response); } public RenderJson(String jsonString) { json = jsonString; + this.response = null; + } + + public RenderJson(Object response, Gson gson) { + this.response = response; + if (gson != null) { + json = gson.toJson(response); + } else { + json = GSON.toJson(response); + } } + @Override public void apply(Request request, Response response) { try { String encoding = getEncoding(); - setContentTypeIfNotSet(response, "application/json; charset="+encoding); + setContentTypeIfNotSet(response, "application/json; charset=" + encoding); response.out.write(json.getBytes(encoding)); } catch (Exception e) { throw new UnexpectedException(e); } } - // - static Method getMethod(Class clazz, String methodName) { + public String getJson() { + return json; + } + + public Object getResponse() { + return response; + } + + private static Method getMethod(Class clazz, String methodName) { Method bestMatch = null; - for(Method m : clazz.getDeclaredMethods()) { + for (Method m : clazz.getDeclaredMethods()) { if (m.getName().equals(methodName) && !m.isBridge()) { if (bestMatch == null || !Object.class.equals(m.getParameterTypes()[0])) { bestMatch = m; @@ -59,6 +84,4 @@ static Method getMethod(Class clazz, String methodName) { } return bestMatch; } - - } diff --git a/framework/src/play/mvc/results/RenderTemplate.java b/framework/src/play/mvc/results/RenderTemplate.java index 384ffeddbf..9186d31f0b 100644 --- a/framework/src/play/mvc/results/RenderTemplate.java +++ b/framework/src/play/mvc/results/RenderTemplate.java @@ -1,32 +1,38 @@ package play.mvc.results; -import java.util.Map; - import play.exceptions.UnexpectedException; import play.libs.MimeTypes; import play.mvc.Http.Request; import play.mvc.Http.Response; import play.templates.Template; +import java.util.Map; + /** * 200 OK with a template rendering */ public class RenderTemplate extends Result { - private String name; - private String content; + private final String name; + private final String content; + private final Map arguments; + private final long renderTime; - public RenderTemplate(Template template, Map args) { - this.name = template.name; - if (args.containsKey("out")) { - throw new RuntimeException("Assertion failed! args shouldn't contain out"); + public RenderTemplate(Template template, Map arguments) { + if (arguments.containsKey("out")) { + throw new RuntimeException("Arguments should not contain out"); } - this.content = template.render(args); + this.name = template.name; + this.arguments = arguments; + long start = System.currentTimeMillis(); + this.content = template.render(arguments); + this.renderTime = System.currentTimeMillis() - start; } + @Override public void apply(Request request, Response response) { try { - final String contentType = MimeTypes.getContentType(name, "text/plain"); + String contentType = MimeTypes.getContentType(name, "text/plain"); response.out.write(content.getBytes(getEncoding())); setContentTypeIfNotSet(response, contentType); } catch (Exception e) { @@ -34,8 +40,19 @@ public void apply(Request request, Response response) { } } + public String getName() { + return name; + } + public String getContent() { return content; } + public Map getArguments() { + return arguments; + } + + public long getRenderTime() { + return renderTime; + } } diff --git a/framework/src/play/mvc/results/RenderText.java b/framework/src/play/mvc/results/RenderText.java index 216efda1a3..605088cb4b 100644 --- a/framework/src/play/mvc/results/RenderText.java +++ b/framework/src/play/mvc/results/RenderText.java @@ -10,12 +10,13 @@ */ public class RenderText extends Result { - String text; + private final String text; public RenderText(CharSequence text) { this.text = text.toString(); } + @Override public void apply(Request request, Response response) { try { setContentTypeIfNotSet(response, "text/plain; charset=" + Http.Response.current().encoding); @@ -25,4 +26,7 @@ public void apply(Request request, Response response) { } } + public String getText() { + return text; + } } diff --git a/framework/src/play/mvc/results/RenderXml.java b/framework/src/play/mvc/results/RenderXml.java index 7f2632fe0a..0c2eadab5b 100644 --- a/framework/src/play/mvc/results/RenderXml.java +++ b/framework/src/play/mvc/results/RenderXml.java @@ -14,7 +14,7 @@ */ public class RenderXml extends Result { - String xml; + private final String xml; public RenderXml(CharSequence xml) { this.xml = xml.toString(); @@ -32,6 +32,7 @@ public RenderXml(Object o) { this(o, new XStream()); } + @Override public void apply(Request request, Response response) { try { setContentTypeIfNotSet(response, "text/xml"); @@ -41,4 +42,7 @@ public void apply(Request request, Response response) { } } + public String getXml() { + return xml; + } } diff --git a/framework/src/play/mvc/results/Result.java b/framework/src/play/mvc/results/Result.java index 04c7fd755f..7c0b3fd23b 100644 --- a/framework/src/play/mvc/results/Result.java +++ b/framework/src/play/mvc/results/Result.java @@ -24,6 +24,8 @@ protected void setContentTypeIfNotSet(Http.Response response, String contentType /** * The encoding that should be used when writing this response to the client + * + * @return The encoding of the response */ protected String getEncoding() { return Http.Response.current().encoding; diff --git a/framework/src/play/mvc/results/Status.java b/framework/src/play/mvc/results/Status.java index 6ada8da92e..fc61afd746 100644 --- a/framework/src/play/mvc/results/Status.java +++ b/framework/src/play/mvc/results/Status.java @@ -5,14 +5,19 @@ public class Status extends Result { - int code; + private final int code; public Status(int code) { super(code+""); this.code = code; } + @Override public void apply(Request request, Response response) { response.status = code; } + + public int getCode() { + return code; + } } \ No newline at end of file diff --git a/framework/src/play/mvc/results/Unauthorized.java b/framework/src/play/mvc/results/Unauthorized.java index 7aaefa5c16..b80b3e2978 100644 --- a/framework/src/play/mvc/results/Unauthorized.java +++ b/framework/src/play/mvc/results/Unauthorized.java @@ -9,15 +9,20 @@ */ public class Unauthorized extends Result { - String realm; + private final String realm; public Unauthorized(String realm) { super(realm); this.realm = realm; } + @Override public void apply(Request request, Response response) { response.status = Http.StatusCode.UNAUTHORIZED; response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); } + + public String getRealm() { + return realm; + } } diff --git a/framework/src/play/plugins/ConfigurablePluginDisablingPlugin.java b/framework/src/play/plugins/ConfigurablePluginDisablingPlugin.java index bb8578cf2e..60c59ae46e 100644 --- a/framework/src/play/plugins/ConfigurablePluginDisablingPlugin.java +++ b/framework/src/play/plugins/ConfigurablePluginDisablingPlugin.java @@ -27,14 +27,14 @@ public class ConfigurablePluginDisablingPlugin extends PlayPlugin { * when reloading config, we have to enable hem again, in case, * they are no longer listed in the "disable plugins"-section */ - protected final static Set previousDisabledPlugins = new HashSet(); + protected static final Set previousDisabledPlugins = new HashSet<>(); @Override public void onConfigurationRead() { Logger.trace("Looking for plugins to disable"); - Set disabledPlugins = new HashSet(); + Set disabledPlugins = new HashSet<>(); for( Map.Entry e : Play.configuration.entrySet()){ String key = (String)e.getKey(); diff --git a/framework/src/play/plugins/PluginCollection.java b/framework/src/play/plugins/PluginCollection.java index 010acfb840..3bc6a6c199 100644 --- a/framework/src/play/plugins/PluginCollection.java +++ b/framework/src/play/plugins/PluginCollection.java @@ -1,22 +1,5 @@ package play.plugins; -import play.Logger; -import play.Play; -import play.PlayPlugin; -import play.classloading.ApplicationClasses; -import play.classloading.ApplicationClassloader; -import play.data.binding.RootParamNode; -import play.db.Model; -import play.exceptions.UnexpectedException; -import play.mvc.Http; -import play.mvc.Router; -import play.mvc.results.Result; -import play.templates.BaseTemplate; -import play.templates.Template; -import play.test.BaseTest; -import play.test.TestEngine; -import play.vfs.VirtualFile; - import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.annotation.Annotation; @@ -35,59 +18,88 @@ import java.util.ListIterator; import java.util.Map; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import play.Logger; +import play.Play; +import play.PlayPlugin; +import play.classloading.ApplicationClasses; +import play.classloading.ApplicationClassloader; +import play.data.binding.RootParamNode; +import play.db.Model; +import play.exceptions.UnexpectedException; +import play.libs.F; +import play.mvc.Http; +import play.mvc.Router; +import play.mvc.results.Result; +import play.templates.BaseTemplate; +import play.templates.Template; +import play.test.BaseTest; +import play.test.TestEngine; +import play.vfs.VirtualFile; /** * Class handling all plugins used by Play. * * Loading/reloading/enabling/disabling is handled here. * - * This class also exposes many PlayPlugin-methods which - * when called, the method is executed on all enabled plugins. + * This class also exposes many PlayPlugin-methods which when called, the method is executed on all enabled plugins. * - * Since all the enabled-plugins-iteration is done here, - * the code elsewhere is cleaner. + * Since all the enabled-plugins-iteration is done here, the code elsewhere is cleaner. */ public class PluginCollection { /** - * Property holding the name of the play.plugins-resource-name. - * Can be modified in unittest to supply modifies plugin-list + * Property holding the name of the play.plugins-resource-name. Can be modified in unittest to supply modifies + * plugin-list */ protected String play_plugins_resourceName = "play.plugins"; /** * List that holds all loaded plugins, enabled or disabled */ - protected List allPlugins = new ArrayList(); + protected List allPlugins = new ArrayList<>(); /** - * Readonly copy of allPlugins - updated each time allPlugins is updated. - * Using this cached copy so we don't have to create it all the time.. + * Readonly copy of allPlugins - updated each time allPlugins is updated. Using this cached copy so we don't have to + * create it all the time.. */ protected List allPlugins_readOnlyCopy = createReadonlyCopy(allPlugins); /** * List of all enabled plugins */ - protected List enabledPlugins = new ArrayList(); + protected List enabledPlugins = new ArrayList<>(); /** - * Readonly copy of enabledPlugins - updated each time enabledPlugins is updated. - * Using this cached copy so we don't have to create it all the time + * Readonly copy of enabledPlugins - updated each time enabledPlugins is updated. Using this cached copy so we don't + * have to create it all the time */ protected List enabledPlugins_readOnlyCopy = createReadonlyCopy(enabledPlugins); + /** + * List of all enabled plugins with filters + */ + protected List enabledPluginsWithFilters = new ArrayList<>(); + + /** + * Readonly copy of enabledPluginsWithFilters - updated each time enabledPluginsWithFilters is updated. Using this + * cached copy so we don't have to create it all the time + */ + protected List enabledPluginsWithFilters_readOnlyCopy = createReadonlyCopy(enabledPluginsWithFilters); /** * Using readonly list to crash if someone tries to modify the copy. + * * @param list + * The list of plugins * @return Read only list of plugins */ - protected List createReadonlyCopy( List list ){ - return Collections.unmodifiableList( new ArrayList( list )); + protected List createReadonlyCopy(List list) { + return Collections.unmodifiableList(new ArrayList<>(list)); } - private static class LoadingPluginInfo implements Comparable { public final String name; public final int index; @@ -101,13 +113,10 @@ private LoadingPluginInfo(String name, int index, URL url) { @Override public String toString() { - return "LoadingPluginInfo{" + - "name='" + name + '\'' + - ", index=" + index + - ", url=" + url + - '}'; + return "LoadingPluginInfo{" + "name='" + name + '\'' + ", index=" + index + ", url=" + url + '}'; } + @Override public int compareTo(LoadingPluginInfo o) { int res = index < o.index ? -1 : (index == o.index ? 0 : 1); if (res != 0) { @@ -118,7 +127,32 @@ public int compareTo(LoadingPluginInfo o) { // sort on name to get consistent order return name.compareTo(o.name); } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + LoadingPluginInfo that = (LoadingPluginInfo) o; + + if (index != that.index) + return false; + if (name != null ? !name.equals(that.name) : that.name != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + index; + return result; + } } + /** * Enable found plugins */ @@ -130,14 +164,25 @@ public void loadPlugins() { urls = Play.classloader.getResources(play_plugins_resourceName); } catch (Exception e) { Logger.error("Error loading play.plugins", e); - return ; + return; } - // First we build one big list of all plugins to load, then we sort it based - // on index before we load the classes. + // First we build one big SortedSet of all plugins to load (sorted based + // on index) // This must be done to make sure the enhancing is happening // when loading plugins using other classes that must be enhanced. - List pluginsToLoad = new ArrayList(); + // Data structure is a SortedSet instead of a List to avoid including + // the same class+index twice -- + // this happened in the past under a range of circumstances, including: + // 1. Class path on NTFS or other case insensitive file system includes + // play.plugins directory 2x + // (C:/myproject/conf;c:/myproject/conf) + // 2. + // https://play.lighthouseapp.com/projects/57987/tickets/176-app-playplugins-loaded-twice-conf-on-2-classpaths + // I can see loading the same plugin with different indexes, but I can't + // think of a reasonable use case for + // loading the same plugin multiple times at the same priority. + SortedSet pluginsToLoad = new TreeSet<>(); while (urls != null && urls.hasMoreElements()) { URL url = urls.nextElement(); Logger.trace("Found one plugins descriptor, %s", url); @@ -153,17 +198,15 @@ public void loadPlugins() { pluginsToLoad.add(info); } } catch (Exception e) { - Logger.error("Error interpreting %s", url ); + Logger.error(e, "Error interpreting %s", url); } } - Collections.sort(pluginsToLoad); - for (LoadingPluginInfo info : pluginsToLoad) { Logger.trace("Loading plugin %s", info.name); try { - PlayPlugin plugin = (PlayPlugin)Play.classloader.loadClass(info.name).newInstance(); + PlayPlugin plugin = (PlayPlugin) Play.classloader.loadClass(info.name).newInstance(); plugin.index = info.index; if (addPlugin(plugin)) { Logger.trace("Plugin %s loaded", plugin); @@ -174,11 +217,13 @@ public void loadPlugins() { Logger.error(ex, "Error loading plugin %s", info.toString()); } } - // Mow we must call onLoad for all plugins - and we must detect if a plugin - // disables another plugin the old way, by removing it from Play.plugins. + // Mow we must call onLoad for all plugins - and we must detect if a + // plugin + // disables another plugin the old way, by removing it from + // Play.plugins. for (PlayPlugin plugin : getEnabledPlugins()) { - //is this plugin still enabled? + // is this plugin still enabled? if (isEnabled(plugin)) { initializePlugin(plugin); } @@ -191,24 +236,29 @@ public void loadPlugins() { /** * Reloads all loaded plugins that is application-supplied. + * + * @throws Exception + * If problem occurred during reload */ - public void reloadApplicationPlugins() throws Exception{ - - Set reloadedPlugins = new HashSet(); + public void reloadApplicationPlugins() throws Exception { + + Set reloadedPlugins = new HashSet<>(); for (PlayPlugin plugin : getAllPlugins()) { // Is this plugin an application-supplied-plugin? if (isLoadedByApplicationClassloader(plugin)) { // This plugin is application-supplied - Must reload it String pluginClassName = plugin.getClass().getName(); - Class pluginClazz = Play.classloader.loadClass( pluginClassName); + Class pluginClazz = Play.classloader.loadClass(pluginClassName); // First look for constructors the old way Constructor[] constructors = pluginClazz.getConstructors(); - if( constructors.length == 0){ + if (constructors.length == 0) { // No constructors in plugin - // using getDeclaredConstructors() instead of getConstructors() to make it work for plugins without constructor + // using getDeclaredConstructors() instead of + // getConstructors() to make it work for plugins without + // constructor constructors = pluginClazz.getDeclaredConstructors(); } @@ -222,7 +272,7 @@ public void reloadApplicationPlugins() throws Exception{ // Now we must call onLoad for all reloaded plugins for (PlayPlugin plugin : reloadedPlugins) { - initializePlugin( plugin ); + initializePlugin(plugin); } updatePlayPluginsList(); @@ -233,40 +283,43 @@ protected boolean isLoadedByApplicationClassloader(PlayPlugin plugin) { return plugin.getClass().getClassLoader().getClass().equals(ApplicationClassloader.class); } - /** - * Calls plugin.onLoad but detects if plugin removes other plugins from Play.plugins-list to detect - * if plugins disables a plugin the old hacked way.. + * Calls plugin.onLoad but detects if plugin removes other plugins from Play.plugins-list to detect if plugins + * disables a plugin the old hacked way.. + * * @param plugin + * The given plugin */ - @SuppressWarnings({"deprecation"}) + @SuppressWarnings({ "deprecation" }) protected void initializePlugin(PlayPlugin plugin) { Logger.trace("Initializing plugin " + plugin); // We're ready to call onLoad for this plugin. // must create a unique Play.plugins-list for this onLoad-method-call so // we can detect if some plugins are removed/disabled - Play.plugins = new ArrayList(getEnabledPlugins()); + Play.plugins = new ArrayList<>(getEnabledPlugins()); plugin.onLoad(); // Check for missing/removed plugins for (PlayPlugin enabledPlugin : getEnabledPlugins()) { if (!Play.plugins.contains(enabledPlugin)) { - Logger.info("Detected that plugin '" + plugin + "' disabled the plugin '" + enabledPlugin + "' the old way - should use Play.disablePlugin()"); + Logger.info("Detected that plugin '" + plugin + "' disabled the plugin '" + enabledPlugin + + "' the old way - should use Play.disablePlugin()"); // This enabled plugin was disabled. // must disable it in pluginCollection - disablePlugin( enabledPlugin); + disablePlugin(enabledPlugin); } } } - /** * Adds one plugin and enables it + * * @param plugin + * The given plugin * @return true if plugin was new and was added */ protected synchronized boolean addPlugin(PlayPlugin plugin) { if (!allPlugins.contains(plugin)) { - allPlugins.add( plugin ); + allPlugins.add(plugin); Collections.sort(allPlugins); allPlugins_readOnlyCopy = createReadonlyCopy(allPlugins); enablePlugin(plugin); @@ -276,78 +329,102 @@ protected synchronized boolean addPlugin(PlayPlugin plugin) { } protected synchronized void replacePlugin(PlayPlugin oldPlugin, PlayPlugin newPlugin) { - if (allPlugins.remove(oldPlugin)) { - allPlugins.add( newPlugin); - Collections.sort( allPlugins); - allPlugins_readOnlyCopy = createReadonlyCopy( allPlugins); - } + if (allPlugins.remove(oldPlugin)) { + allPlugins.add(newPlugin); + Collections.sort(allPlugins); + allPlugins_readOnlyCopy = createReadonlyCopy(allPlugins); + } - if (enabledPlugins.remove( oldPlugin)) { - enabledPlugins.add(newPlugin); - Collections.sort( enabledPlugins); - enabledPlugins_readOnlyCopy = createReadonlyCopy( enabledPlugins); + if (enabledPlugins.remove(oldPlugin)) { + enabledPlugins.add(newPlugin); + Collections.sort(enabledPlugins); + enabledPlugins_readOnlyCopy = createReadonlyCopy(enabledPlugins); + + if (enabledPluginsWithFilters.remove(oldPlugin) && newPlugin.hasFilter()) { + enabledPluginsWithFilters.add(newPlugin); + Collections.sort(enabledPluginsWithFilters); + enabledPluginsWithFilters_readOnlyCopy = createReadonlyCopy(enabledPluginsWithFilters); } + } - } /** * Enable plugin. * * @param plugin + * The given plugin * @return true if plugin exists and was enabled now */ public synchronized boolean enablePlugin(PlayPlugin plugin) { - if (allPlugins.contains(plugin)) { - //the plugin exists - if (!enabledPlugins.contains(plugin)) { - //plugin not currently enabled - enabledPlugins.add( plugin ); - Collections.sort( enabledPlugins); - enabledPlugins_readOnlyCopy = createReadonlyCopy( enabledPlugins); - updatePlayPluginsList(); - Logger.trace("Plugin " + plugin + " enabled"); - return true; + if (allPlugins.contains(plugin)) { + // the plugin exists + if (!enabledPlugins.contains(plugin)) { + // plugin not currently enabled + enabledPlugins.add(plugin); + Collections.sort(enabledPlugins); + enabledPlugins_readOnlyCopy = createReadonlyCopy(enabledPlugins); + + if (plugin.hasFilter()) { + enabledPluginsWithFilters.add(plugin); + Collections.sort(enabledPluginsWithFilters); + enabledPluginsWithFilters_readOnlyCopy = createReadonlyCopy(enabledPluginsWithFilters); } + + updatePlayPluginsList(); + Logger.trace("Plugin " + plugin + " enabled"); + return true; } - + } + return false; } /** * enable plugin of specified type + * + * @param pluginClazz + * The plugin class + * * @return true if plugin was enabled */ public boolean enablePlugin(Class pluginClazz) { return enablePlugin(getPluginInstance(pluginClazz)); } - /** * Returns the first instance of a loaded plugin of specified type + * * @param pluginClazz + * The plugin class * @return PlayPlugin */ - public synchronized PlayPlugin getPluginInstance(Class pluginClazz) { + public synchronized T getPluginInstance(Class pluginClazz) { for (PlayPlugin p : getAllPlugins()) { if (pluginClazz.isInstance(p)) { - return p; + return (T) p; } } return null; } - /** * disable plugin + * * @param plugin + * The given plugin * @return true if plugin was enabled and now is disabled */ - public synchronized boolean disablePlugin(PlayPlugin plugin ){ - //try to disable it? + public synchronized boolean disablePlugin(PlayPlugin plugin) { + // try to disable it? if (enabledPlugins.remove(plugin)) { - //plugin was removed - enabledPlugins_readOnlyCopy = createReadonlyCopy( enabledPlugins); + // plugin was removed + enabledPlugins_readOnlyCopy = createReadonlyCopy(enabledPlugins); + + if (enabledPluginsWithFilters.remove(plugin)) { + enabledPluginsWithFilters_readOnlyCopy = createReadonlyCopy(enabledPluginsWithFilters); + } + updatePlayPluginsList(); Logger.trace("Plugin " + plugin + " disabled"); return true; @@ -356,78 +433,114 @@ public synchronized boolean disablePlugin(PlayPlugin plugin ){ } /** - * disable plugin of specified type + * Disable plugin of specified type + * + * @param pluginClazz + * The plugin class + * * @return true if plugin was enabled and now is disabled */ public boolean disablePlugin(Class pluginClazz) { - return disablePlugin(getPluginInstance( pluginClazz)); + return disablePlugin(getPluginInstance(pluginClazz)); } - - /** * Must update Play.plugins-list to be backward compatible */ - @SuppressWarnings({"deprecation"}) + @SuppressWarnings({ "deprecation" }) public void updatePlayPluginsList() { Play.plugins = Collections.unmodifiableList(getEnabledPlugins()); } /** * Returns new readonly list of all enabled plugins + * * @return List of plugins */ public List getEnabledPlugins() { return enabledPlugins_readOnlyCopy; } - + + /** + * Returns new readonly list of all enabled plugins that define filters. + * + * @return List of plugins + */ + public List getEnabledPluginsWithFilters() { + return enabledPluginsWithFilters_readOnlyCopy; + } + + @SuppressWarnings("unchecked") + public F.Option> composeFilters() { + // Copy list of plugins here in case the list changes in the midst of + // doing composition... + // (Is it really necessary to do this?) + List pluginsWithFilters = new ArrayList<>(this.getEnabledPluginsWithFilters()); + + if (pluginsWithFilters.isEmpty()) { + return F.Option.None(); + } else { + Iterator itr = pluginsWithFilters.iterator(); + PlayPlugin.Filter ret = itr.next().getFilter(); + while (itr.hasNext()) { + ret = ret. decorate(itr.next().getFilter()); + } + return F.Option.Some(ret); + } + } + /** * Returns readonly view of all enabled plugins in reversed order + * * @return Collection of plugins */ public Collection getReversedEnabledPlugins() { return new AbstractCollection() { - - @Override public Iterator iterator() { - final ListIterator enabledPluginsListIt = enabledPlugins.listIterator(size() - 1); - return new Iterator() { - - @Override - public boolean hasNext() { - return enabledPluginsListIt.hasPrevious(); - } - - @Override - public PlayPlugin next() { - return enabledPluginsListIt.previous(); - } - - @Override - public void remove() { - enabledPluginsListIt.remove(); - }}; - } - - @Override public int size() { - return enabledPlugins.size(); - } - - - }; + + @Override + public Iterator iterator() { + final ListIterator enabledPluginsListIt = enabledPlugins.listIterator(size() - 1); + return new Iterator() { + + @Override + public boolean hasNext() { + return enabledPluginsListIt.hasPrevious(); + } + + @Override + public PlayPlugin next() { + return enabledPluginsListIt.previous(); + } + + @Override + public void remove() { + enabledPluginsListIt.remove(); + } + }; + } + + @Override + public int size() { + return enabledPlugins.size(); + } + + }; } /** * Returns new readonly list of all plugins + * * @return List of plugins */ public List getAllPlugins() { return allPlugins_readOnlyCopy; } - /** - * + * Indicate if a plugin is enabled + * * @param plugin + * The given plugin * @return true if plugin is enabled */ public boolean isEnabled(PlayPlugin plugin) { @@ -459,18 +572,18 @@ public void invocationFinally() { } public void beforeInvocation() { - for( PlayPlugin plugin : getEnabledPlugins()) { + for (PlayPlugin plugin : getEnabledPlugins()) { plugin.beforeInvocation(); } } - public void afterInvocation(){ + public void afterInvocation() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.afterInvocation(); } } - public void onInvocationSuccess(){ + public void onInvocationSuccess() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.onInvocationSuccess(); } @@ -481,59 +594,58 @@ public void onInvocationException(Throwable e) { try { plugin.onInvocationException(e); } catch (Throwable ex) { - //nop + Logger.error(ex, "Failed to handle invocation exception by plugin %s", plugin.getClass().getName()); } } } - public void beforeDetectingChanges(){ + public void beforeDetectingChanges() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.beforeDetectingChanges(); } } - public void detectChange(){ + public void detectChange() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.detectChange(); } } - public void onApplicationReady(){ + public void onApplicationReady() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.onApplicationReady(); } } - public void onConfigurationRead(){ + public void onConfigurationRead() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.onConfigurationRead(); } } - public void onApplicationStart(){ + public void onApplicationStart() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.onApplicationStart(); } } - public void afterApplicationStart(){ + public void afterApplicationStart() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.afterApplicationStart(); } } - public void onApplicationStop(){ + public void onApplicationStop() { for (PlayPlugin plugin : getReversedEnabledPlugins()) { try { - plugin.onApplicationStop(); - } - catch (Throwable t) { - if (t.getMessage() == null) - Logger.error(t, "Error while stopping %s", plugin); - else if (Logger.isDebugEnabled()) - Logger.debug(t, "Error while stopping %s", plugin); - else - Logger.info("Error while stopping %s: %s", plugin, t.toString()); + plugin.onApplicationStop(); + } catch (Throwable t) { + if (t.getMessage() == null) + Logger.error(t, "Error while stopping %s", plugin); + else if (Logger.isDebugEnabled()) + Logger.debug(t, "Error while stopping %s", plugin); + else + Logger.info("Error while stopping %s: %s", plugin, t.toString()); } } } @@ -559,8 +671,8 @@ public void enhance(ApplicationClasses.ApplicationClass applicationClass) { } @Deprecated - public List onClassesChange(List modified){ - List modifiedWithDependencies = new ArrayList(); + public List onClassesChange(List modified) { + List modifiedWithDependencies = new ArrayList<>(); for (PlayPlugin plugin : getEnabledPlugins()) { modifiedWithDependencies.addAll(plugin.onClassesChange(modified)); } @@ -574,7 +686,7 @@ public void compileAll(List classes) { } } - public Object bind(RootParamNode rootParamNode, String name, Class clazz, Type type, Annotation[] annotations){ + public Object bind(RootParamNode rootParamNode, String name, Class clazz, Type type, Annotation[] annotations) { for (PlayPlugin plugin : getEnabledPlugins()) { Object result = plugin.bind(rootParamNode, name, clazz, type, annotations); if (result != null) { @@ -617,7 +729,7 @@ public Object willBeValidated(Object value) { public Model.Factory modelFactory(Class modelClass) { for (PlayPlugin plugin : getEnabledPlugins()) { Model.Factory factory = plugin.modelFactory(modelClass); - if(factory != null) { + if (factory != null) { return factory; } } @@ -627,7 +739,7 @@ public Model.Factory modelFactory(Class modelClass) { public String getMessage(String locale, Object key, Object... args) { for (PlayPlugin plugin : getEnabledPlugins()) { String message = plugin.getMessage(locale, key, args); - if(message != null) { + if (message != null) { return message; } } @@ -646,12 +758,18 @@ public void onActionInvocationResult(Result result) { } } - public void afterActionInvocation(){ + public void afterActionInvocation() { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.afterActionInvocation(); } } + public void onActionInvocationFinally() { + for (PlayPlugin plugin : getEnabledPlugins()) { + plugin.onActionInvocationFinally(); + } + } + public void routeRequest(Http.Request request) { for (PlayPlugin plugin : getEnabledPlugins()) { plugin.routeRequest(request); @@ -679,7 +797,6 @@ public boolean rawInvocation(Http.Request request, Http.Response response) throw return false; } - public boolean serveStatic(VirtualFile file, Http.Request request, Http.Response response) { for (PlayPlugin plugin : getEnabledPlugins()) { if (plugin.serveStatic(file, request, response)) { @@ -690,7 +807,7 @@ public boolean serveStatic(VirtualFile file, Http.Request request, Http.Response } public List addTemplateExtensions() { - List list = new ArrayList(); + List list = new ArrayList<>(); for (PlayPlugin plugin : getEnabledPlugins()) { list.addAll(plugin.addTemplateExtensions()); } @@ -700,7 +817,7 @@ public List addTemplateExtensions() { public String overrideTemplateSource(BaseTemplate template, String source) { for (PlayPlugin plugin : getEnabledPlugins()) { String newSource = plugin.overrideTemplateSource(template, source); - if(newSource != null) { + if (newSource != null) { source = newSource; } } @@ -710,7 +827,7 @@ public String overrideTemplateSource(BaseTemplate template, String source) { public Template loadTemplate(VirtualFile file) { for (PlayPlugin plugin : getEnabledPlugins()) { Template pluginProvided = plugin.loadTemplate(file); - if(pluginProvided != null) { + if (pluginProvided != null) { return pluginProvided; } } @@ -734,26 +851,26 @@ public TestEngine.TestResults runTest(Class clazz) { } public Collection getUnitTests() { - Set allPluginTests = new HashSet(); + Set allPluginTests = new HashSet<>(); for (PlayPlugin plugin : getEnabledPlugins()) { Collection unitTests = plugin.getUnitTests(); - if(unitTests != null) { + if (unitTests != null) { allPluginTests.addAll(unitTests); } } - + return allPluginTests; } - + public Collection getFunctionalTests() { - Set allPluginTests = new HashSet(); + Set allPluginTests = new HashSet<>(); for (PlayPlugin plugin : getEnabledPlugins()) { Collection funcTests = plugin.getFunctionalTests(); - if(funcTests != null) { + if (funcTests != null) { allPluginTests.addAll(funcTests); } } - + return allPluginTests; } } diff --git a/framework/src/play/server/FileChannelBuffer.java b/framework/src/play/server/FileChannelBuffer.java index 5251be3b5f..f1b0a6ca9e 100644 --- a/framework/src/play/server/FileChannelBuffer.java +++ b/framework/src/play/server/FileChannelBuffer.java @@ -34,30 +34,37 @@ public InputStream getInputStream() { return is; } + @Override public ChannelBuffer unwrap() { throw new RuntimeException(); } + @Override public ChannelBufferFactory factory() { throw new RuntimeException(); } + @Override public ByteOrder order() { throw new RuntimeException(); } + @Override public boolean isDirect() { return true; } + @Override public boolean hasArray() { return false; } + @Override public byte[] array() { throw new RuntimeException(); } + @Override public int arrayOffset() { throw new RuntimeException(); } @@ -71,14 +78,17 @@ public void setByte(int index, byte value) { throw new RuntimeException(); } + @Override public void setBytes(int index, ChannelBuffer src, int srcIndex, int length) { throw new RuntimeException(); } + @Override public void setBytes(int index, byte[] src, int srcIndex, int length) { throw new RuntimeException(); } + @Override public void setBytes(int index, ByteBuffer src) { throw new RuntimeException(); } @@ -87,34 +97,40 @@ public void setShort(int index, short value) { throw new RuntimeException(); } + @Override public void setMedium(int index, int value) { throw new RuntimeException(); } + @Override public void setInt(int index, int value) { throw new RuntimeException(); } + @Override public void setLong(int index, long value) { throw new RuntimeException(); } + @Override public int setBytes(int index, InputStream in, int length) throws IOException { throw new RuntimeException(); } + @Override public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { throw new RuntimeException(); } + @Override public int readerIndex() { return 0; } - + @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { byte[] b = new byte[length]; @@ -123,10 +139,12 @@ public int getBytes(int index, GatheringByteChannel out, int length) return out.write(bb); } + @Override public void setByte(int i, int i1) { throw new RuntimeException(); } + @Override public void getBytes(int index, OutputStream out, int length) throws IOException { byte[] b = new byte[length]; @@ -134,6 +152,7 @@ public void getBytes(int index, OutputStream out, int length) out.write(b, index, length); } + @Override public void getBytes(int index, byte[] dst, int dstIndex, int length) { try { byte[] b = new byte[length]; @@ -144,6 +163,7 @@ public void getBytes(int index, byte[] dst, int dstIndex, int length) { } } + @Override public void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) { try { byte[] b = new byte[length]; @@ -154,6 +174,7 @@ public void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) { } } + @Override public void getBytes(int index, ByteBuffer dst) { try { byte[] b = new byte[is.available() - index]; @@ -164,18 +185,22 @@ public void getBytes(int index, ByteBuffer dst) { } } + @Override public ChannelBuffer duplicate() { throw new RuntimeException(); } + @Override public ChannelBuffer copy(int index, int length) { throw new RuntimeException(); } + @Override public ChannelBuffer slice(int index, int length) { throw new RuntimeException(); } + @Override public byte getByte(int index) { // try { // byte[] b = new byte[1]; @@ -187,25 +212,30 @@ public byte getByte(int index) { throw new RuntimeException(); } + @Override public short getShort(int index) { throw new RuntimeException(); } + @Override public int getUnsignedMedium(int index) { throw new RuntimeException(); } + @Override public int getInt(int index) { throw new RuntimeException(); } + @Override public long getLong(int index) { throw new RuntimeException(); } + @Override public ByteBuffer toByteBuffer(int index, int length) { throw new RuntimeException(); } @@ -220,6 +250,7 @@ public ByteBuffer[] toByteBuffers(int index, int length) { throw new RuntimeException(); } + @Override public int capacity() { try { return is.available(); @@ -229,6 +260,7 @@ public int capacity() { } + @Override public ChannelBuffer readBytes(int length) { // ChannelBuffer buf = ChannelBuffers.buffer(length); // getBytes(0, buf); @@ -240,6 +272,7 @@ public ChannelBuffer readBytes(ChannelBufferIndexFinder endIndexFinder) { throw new RuntimeException(); } + @Override public ChannelBuffer readSlice(int length) { throw new RuntimeException(); } @@ -248,19 +281,23 @@ public ChannelBuffer readSlice(ChannelBufferIndexFinder endIndexFinder) { throw new RuntimeException(); } + @Override public void readBytes(byte[] dst, int dstIndex, int length) { checkReadableBytes(length); getBytes(0, dst, dstIndex, length); } + @Override public void readBytes(byte[] dst) { readBytes(dst, 0, dst.length); } + @Override public void readBytes(ChannelBuffer dst) { readBytes(dst, dst.writableBytes()); } + @Override public void readBytes(ChannelBuffer dst, int length) { if (length > dst.writableBytes()) { throw new IndexOutOfBoundsException(); @@ -269,16 +306,19 @@ public void readBytes(ChannelBuffer dst, int length) { dst.writerIndex(dst.writerIndex() + length); } + @Override public void readBytes(ChannelBuffer dst, int dstIndex, int length) { getBytes(0, dst, dstIndex, length); } + @Override public void readBytes(ByteBuffer dst) { int length = dst.remaining(); checkReadableBytes(length); getBytes(0, dst); } + @Override public int readBytes(GatheringByteChannel out, int length) throws IOException { checkReadableBytes(length); @@ -286,6 +326,7 @@ public int readBytes(GatheringByteChannel out, int length) return readBytes; } + @Override public void readBytes(OutputStream out, int length) throws IOException { checkReadableBytes(length); getBytes(0, out, length); @@ -295,6 +336,7 @@ public String toString(int q, int a, java.lang.String b) { throw new RuntimeException(); } + @Override public void setShort(int a, int b) { throw new RuntimeException(); } diff --git a/framework/src/play/server/FileService.java b/framework/src/play/server/FileService.java index 3a79b179bf..9ece656da0 100644 --- a/framework/src/play/server/FileService.java +++ b/framework/src/play/server/FileService.java @@ -29,7 +29,7 @@ public class FileService { public static void serve(File localFile, HttpRequest nettyRequest, HttpResponse nettyResponse, ChannelHandlerContext ctx, Request request, Response response, Channel channel) throws FileNotFoundException { - final RandomAccessFile raf = new RandomAccessFile(localFile, "r"); + RandomAccessFile raf = new RandomAccessFile(localFile, "r"); try { long fileLength = raf.length(); @@ -145,7 +145,7 @@ public void prepareNettyResponse(HttpResponse nettyResponse) { } long length = 0; for(ByteRange range: byteRanges) { - length += range.computeTotalLengh(); + length += range.computeTotalLength(); } nettyResponse.headers().set("Content-length", length); } @@ -201,7 +201,7 @@ private void initRanges() { try { String headerValue = request.headers().get("range").trim().substring("bytes=".length()); String[] rangesValues = headerValue.split(","); - ArrayList ranges = new ArrayList(rangesValues.length); + ArrayList ranges = new ArrayList<>(rangesValues.length); for(int i = 0; i < rangesValues.length; i++) { String rangeValue = rangesValues[i]; long start, end; @@ -252,11 +252,12 @@ private static long[][] reduceRanges(long[]... chunks) { return new long[0][]; long[][] sortedChunks = Arrays.copyOf(chunks, chunks.length); Arrays.sort(sortedChunks, new Comparator() { + @Override public int compare(long[] t1, long[] t2) { return new Long(t1[0]).compareTo(t2[0]); } }); - ArrayList result = new ArrayList(); + ArrayList result = new ArrayList<>(); result.add(sortedChunks[0]); for (int i = 1; i < sortedChunks.length; i++) { long[] c1 = sortedChunks[i]; @@ -289,7 +290,7 @@ public long remaining() { return end - start + 1 - servedRange; } - public long computeTotalLengh() { + public long computeTotalLength() { return length() + header.length; } diff --git a/framework/src/play/server/FlashPolicyHandler.java b/framework/src/play/server/FlashPolicyHandler.java index 82e96b4a2b..a3cc386af9 100644 --- a/framework/src/play/server/FlashPolicyHandler.java +++ b/framework/src/play/server/FlashPolicyHandler.java @@ -9,8 +9,8 @@ import org.jboss.netty.util.CharsetUtil; public class FlashPolicyHandler extends FrameDecoder { - - private static final String XML = ""; + + private static final String XML = ""; private ChannelBuffer policyResponse = ChannelBuffers.copiedBuffer(XML, CharsetUtil.UTF_8); /** @@ -29,13 +29,14 @@ public FlashPolicyHandler(ChannelBuffer policyResponse) { this.policyResponse = policyResponse; } + @Override protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { if (buffer.readableBytes() < 2) { return null; } - final int magic1 = buffer.getUnsignedByte(buffer.readerIndex()); - final int magic2 = buffer.getUnsignedByte(buffer.readerIndex() + 1); + int magic1 = buffer.getUnsignedByte(buffer.readerIndex()); + int magic2 = buffer.getUnsignedByte(buffer.readerIndex() + 1); boolean isFlashPolicyRequest = (magic1 == '<' && magic2 == 'p'); if (isFlashPolicyRequest) { diff --git a/framework/src/play/server/HttpServerPipelineFactory.java b/framework/src/play/server/HttpServerPipelineFactory.java index 577aef6233..2e4244679e 100644 --- a/framework/src/play/server/HttpServerPipelineFactory.java +++ b/framework/src/play/server/HttpServerPipelineFactory.java @@ -14,8 +14,9 @@ public class HttpServerPipelineFactory implements ChannelPipelineFactory { private String pipelineConfig = Play.configuration.getProperty("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"); - protected static Map classes = new HashMap(); + protected static Map classes = new HashMap<>(); + @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = pipeline(); diff --git a/framework/src/play/server/PlayHandler.java b/framework/src/play/server/PlayHandler.java index 6f6e324f37..1152bcee07 100644 --- a/framework/src/play/server/PlayHandler.java +++ b/framework/src/play/server/PlayHandler.java @@ -1,11 +1,54 @@ package play.server; +import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CACHE_CONTROL; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.COOKIE; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.DATE; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ETAG; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.EXPIRES; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.HOST; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.IF_MODIFIED_SINCE; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.IF_NONE_MATCH; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SERVER; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBufferInputStream; import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.*; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.handler.codec.frame.TooLongFrameException; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpChunkAggregator; @@ -20,7 +63,14 @@ import org.jboss.netty.handler.codec.http.cookie.DefaultCookie; import org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder; import org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder; -import org.jboss.netty.handler.codec.http.websocketx.*; +import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; +import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import org.jboss.netty.handler.stream.ChunkedInput; import org.jboss.netty.handler.stream.ChunkedStream; import org.jboss.netty.handler.stream.ChunkedWriteHandler; @@ -37,9 +87,13 @@ import play.libs.F.Action; import play.libs.F.Promise; import play.libs.MimeTypes; -import play.mvc.*; +import play.mvc.ActionInvoker; +import play.mvc.Http; import play.mvc.Http.Request; import play.mvc.Http.Response; +import play.mvc.Router; +import play.mvc.Scope; +import play.mvc.WebSocketInvoker; import play.mvc.results.NotFound; import play.mvc.results.RenderStatic; import play.templates.JavaExtensions; @@ -48,38 +102,25 @@ import play.utils.Utils; import play.vfs.VirtualFile; -import java.io.*; -import java.net.InetSocketAddress; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.ParseException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; - public class PlayHandler extends SimpleChannelUpstreamHandler { /** - * If true (the default), Play will send the HTTP header "Server: Play! Framework; ....". - * This could be a security problem (old versions having publicly known security bugs), so you can - * disable the header in application.conf: http.exposePlayServer = false + * If true (the default), Play will send the HTTP header + * "Server: Play! Framework; ....". This could be a security problem (old + * versions having publicly known security bugs), so you can disable the + * header in application.conf: http.exposePlayServer = false */ - private final static String signature = "Play! Framework;" + Play.version + ";" + Play.mode.name().toLowerCase(); - private final static boolean exposePlayServer; + private static final String signature = "Play! Framework;" + Play.version + ";" + Play.mode.name().toLowerCase(); + private static final boolean exposePlayServer; private static final String ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private static final Charset ASCII = Charset.forName("ASCII"); private static final MessageDigest SHA_1; - - /** - * The Pipeline is given for a PlayHandler + + /** + * The Pipeline is given for a PlayHandler */ - public Map pipelines = new HashMap(); + public Map pipelines = new HashMap<>(); private WebSocketServerHandshaker handshaker; @@ -87,21 +128,21 @@ public class PlayHandler extends SimpleChannelUpstreamHandler { try { SHA_1 = MessageDigest.getInstance("SHA1"); } catch (NoSuchAlgorithmException e) { - throw new InternalError("SHA-1 not supported on this platform"); + throw new IllegalStateException("SHA-1 not supported on this platform", e); } - } - + } + static { exposePlayServer = !"false".equals(Play.configuration.getProperty("http.exposePlayServer")); } @Override - public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent messageEvent) throws Exception { + public void messageReceived(final ChannelHandlerContext ctx, MessageEvent messageEvent) throws Exception { if (Logger.isTraceEnabled()) { Logger.trace("messageReceived: begin"); } - final Object msg = messageEvent.getMessage(); + Object msg = messageEvent.getMessage(); // Http request if (msg instanceof HttpRequest) { @@ -116,11 +157,15 @@ public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent // Plain old HttpRequest try { - final Request request = parseRequest(ctx, nettyRequest, messageEvent); + // Reset request object and response object for the current + // thread. + Http.Request.current.set(new Http.Request()); final Response response = new Response(); Http.Response.current.set(response); + final Request request = parseRequest(ctx, nettyRequest, messageEvent); + // Buffered in memory output response.out = new ByteArrayOutputStream(); @@ -130,6 +175,7 @@ public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent // Streamed output (using response.writeChunk) response.onWriteChunk(new Action() { + @Override public void invoke(Object result) { writeChunk(request, response, ctx, nettyRequest, result); } @@ -141,13 +187,13 @@ public void invoke(Object result) { copyResponse(ctx, request, response, nettyRequest); } else { - // Deleguate to Play framework + // Delegate to Play framework Invoker.invoke(new NettyInvocation(request, response, ctx, nettyRequest, messageEvent)); } } catch (Exception ex) { - Logger.warn(ex, "Exception on request. serving 500 back"); + Logger.warn(ex, "Exception on request. serving 500 back"); serve500(ex, ctx, nettyRequest); } } @@ -163,7 +209,7 @@ public void invoke(Object result) { } } - private static final Map staticPathsCache = new HashMap(); + private static final Map staticPathsCache = new HashMap<>(); public class NettyInvocation extends Invoker.Invocation { @@ -190,19 +236,20 @@ public boolean init() { Request.current.set(request); Response.current.set(response); - + Scope.Params.current.set(request.params); Scope.RenderArgs.current.set(new Scope.RenderArgs()); Scope.RouteArgs.current.set(new Scope.RouteArgs()); Scope.Session.current.set(Scope.Session.restore()); Scope.Flash.current.set(Scope.Flash.restore()); CachedBoundActionMethodArgs.init(); - + try { if (Play.mode == Play.Mode.DEV) { Router.detectChanges(Play.ctxPath); } - if (Play.mode == Play.Mode.PROD && staticPathsCache.containsKey(request.domain + " " + request.method + " " + request.path)) { + if (Play.mode == Play.Mode.PROD + && staticPathsCache.containsKey(request.domain + " " + request.method + " " + request.path)) { RenderStatic rs = null; synchronized (staticPathsCache) { rs = staticPathsCache.get(request.domain + " " + request.method + " " + request.path); @@ -243,8 +290,7 @@ public boolean init() { @Override public InvocationContext getInvocationContext() { ActionInvoker.resolve(request, response); - return new InvocationContext(Http.invocationType, - request.invokedMethod.getAnnotations(), + return new InvocationContext(Http.invocationType, request.invokedMethod.getAnnotations(), request.invokedMethod.getDeclaringClass().getAnnotations()); } @@ -274,7 +320,8 @@ public void execute() throws Exception { return; } - // Check the exceeded size before re rendering so we can render the error if the size is exceeded + // Check the exceeded size before re rendering so we can render the + // error if the size is exceeded saveExceededSizeError(nettyRequest, request, response); ActionInvoker.invoke(request, response); } @@ -305,7 +352,8 @@ void saveExceededSizeError(HttpRequest nettyRequest, Request request, Response r try { StringBuilder error = new StringBuilder(); error.append("\u0000"); - // Cannot put warning which is play.netty.content.length.exceeded + // Cannot put warning which is + // play.netty.content.length.exceeded // as Key as it will result error when printing error error.append("play.netty.maxContentLength"); error.append(":"); @@ -319,7 +367,8 @@ void saveExceededSizeError(HttpRequest nettyRequest, Request request, Response r error.append("\u0001"); error.append(size); error.append("\u0000"); - if (request.cookies.get(Scope.COOKIE_PREFIX + "_ERRORS") != null && request.cookies.get(Scope.COOKIE_PREFIX + "_ERRORS").value != null) { + if (request.cookies.get(Scope.COOKIE_PREFIX + "_ERRORS") != null + && request.cookies.get(Scope.COOKIE_PREFIX + "_ERRORS").value != null) { error.append(request.cookies.get(Scope.COOKIE_PREFIX + "_ERRORS").value); } String errorData = URLEncoder.encode(error.toString(), "utf-8"); @@ -369,14 +418,15 @@ protected static void addToResponse(Response response, HttpResponse nettyRespons } - protected static void writeResponse(ChannelHandlerContext ctx, Response response, HttpResponse nettyResponse, HttpRequest nettyRequest) { + protected static void writeResponse(ChannelHandlerContext ctx, Response response, HttpResponse nettyResponse, + HttpRequest nettyRequest) { if (Logger.isTraceEnabled()) { Logger.trace("writeResponse: begin"); } byte[] content = null; - final boolean keepAlive = isKeepAlive(nettyRequest); + boolean keepAlive = isKeepAlive(nettyRequest); if (nettyRequest.getMethod().equals(HttpMethod.HEAD)) { content = new byte[0]; } else { @@ -397,7 +447,8 @@ protected static void writeResponse(ChannelHandlerContext ctx, Response response if (ctx.getChannel().isOpen()) { f = ctx.getChannel().write(nettyResponse); } else { - Logger.debug("Try to write on a closed channel[keepAlive:%s]: Remote host may have closed the connection", String.valueOf(keepAlive)); + Logger.debug("Try to write on a closed channel[keepAlive:%s]: Remote host may have closed the connection", + String.valueOf(keepAlive)); } // Decide whether to close the connection or not. @@ -423,14 +474,16 @@ public void copyResponse(ChannelHandlerContext ctx, Request request, Response re } if (response.contentType != null) { - nettyResponse.headers().set(CONTENT_TYPE, response.contentType + (response.contentType.startsWith("text/") && !response.contentType.contains("charset") ? "; charset=" + response.encoding : "")); + nettyResponse.headers().set(CONTENT_TYPE, + response.contentType + (response.contentType.startsWith("text/") && !response.contentType.contains("charset") + ? "; charset=" + response.encoding : "")); } else { nettyResponse.headers().set(CONTENT_TYPE, "text/plain; charset=" + response.encoding); } addToResponse(response, nettyResponse); - final Object obj = response.direct; + Object obj = response.direct; File file = null; ChunkedInput stream = null; InputStream is = null; @@ -443,8 +496,7 @@ public void copyResponse(ChannelHandlerContext ctx, Request request, Response re stream = (ChunkedInput) obj; } - - final boolean keepAlive = isKeepAlive(nettyRequest); + boolean keepAlive = isKeepAlive(nettyRequest); if (file != null && file.isFile()) { try { nettyResponse = addEtag(nettyRequest, nettyResponse, file); @@ -494,7 +546,6 @@ public void copyResponse(ChannelHandlerContext ctx, Request request, Response re } } - static String getRemoteIPAddress(MessageEvent e) { String fullAddress = ((InetSocketAddress) e.getRemoteAddress()).getAddress().getHostAddress(); if (fullAddress.matches("/[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[:][0-9]+")) { @@ -518,10 +569,10 @@ public Request parseRequest(ChannelHandlerContext ctx, HttpRequest nettyRequest, // Begins searching / after 9th character (last / of https://) int index = uri.indexOf("/", 9); // prevent the IndexOutOfBoundsException that was occurring - if (index >= 0){ - uri = uri.substring(index); + if (index >= 0) { + uri = uri.substring(index); } else { - uri = "/"; + uri = "/"; } } @@ -536,7 +587,7 @@ public Request parseRequest(ChannelHandlerContext ctx, HttpRequest nettyRequest, } } - final int i = uri.indexOf("?"); + int i = uri.indexOf("?"); String querystring = ""; String path = uri; if (i != -1) { @@ -573,7 +624,8 @@ public Request parseRequest(ChannelHandlerContext ctx, HttpRequest nettyRequest, String host = nettyRequest.headers().get(HOST); boolean isLoopback = false; try { - isLoopback = ((InetSocketAddress) messageEvent.getRemoteAddress()).getAddress().isLoopbackAddress() && host.matches("^127\\.0\\.0\\.1:?[0-9]*$"); + isLoopback = ((InetSocketAddress) messageEvent.getRemoteAddress()).getAddress().isLoopbackAddress() + && host.matches("^127\\.0\\.0\\.1:?[0-9]*$"); } catch (Exception e) { // ignore it } @@ -591,8 +643,7 @@ else if (host.startsWith("[")) { if (host.endsWith("]")) { domain = host; port = 80; - } - else { + } else { // There is a port so take from the last colon int portStart = host.lastIndexOf(':'); if (portStart > 0 && (portStart + 1) < host.length()) { @@ -603,7 +654,7 @@ else if (host.startsWith("[")) { } // Non IPv6 but has port else if (host.contains(":")) { - final String[] hosts = host.split(":"); + String[] hosts = host.split(":"); port = Integer.parseInt(hosts[1]); domain = hosts[0]; } else { @@ -613,22 +664,8 @@ else if (host.contains(":")) { boolean secure = false; - final Request request = Request.createRequest( - remoteAddress, - method, - path, - querystring, - contentType, - body, - uri, - host, - isLoopback, - port, - domain, - secure, - getHeaders(nettyRequest), - getCookies(nettyRequest)); - + Request request = Request.createRequest(remoteAddress, method, path, querystring, contentType, body, uri, host, isLoopback, + port, domain, secure, getHeaders(nettyRequest), getCookies(nettyRequest)); if (Logger.isTraceEnabled()) { Logger.trace("parseRequest: end"); @@ -637,12 +674,12 @@ else if (host.contains(":")) { } protected static Map getHeaders(HttpRequest nettyRequest) { - Map headers = new HashMap(16); + Map headers = new HashMap<>(16); for (String key : nettyRequest.headers().names()) { Http.Header hd = new Http.Header(); hd.name = key.toLowerCase(); - hd.values = new ArrayList(); + hd.values = new ArrayList<>(); for (String next : nettyRequest.headers().getAll(key)) { hd.values.add(next); } @@ -653,10 +690,10 @@ protected static Map getHeaders(HttpRequest nettyRequest) { } protected static Map getCookies(HttpRequest nettyRequest) { - Map cookies = new HashMap(16); + Map cookies = new HashMap<>(16); String value = nettyRequest.headers().get(COOKIE); if (value != null) { - Set cookieSet = ServerCookieDecoder.STRICT.decode(value); + Set cookieSet = ServerCookieDecoder.STRICT.decode(value); if (cookieSet != null) { for (Cookie cookie : cookieSet) { Http.Cookie playCookie = new Http.Cookie(); @@ -673,7 +710,6 @@ protected static Map getCookies(HttpRequest nettyRequest) { return cookies; } - @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { try { @@ -706,7 +742,6 @@ public static void serve404(NotFound e, ChannelHandlerContext ctx, Request reque } nettyResponse.headers().set(CONTENT_TYPE, (MimeTypes.getContentType("404." + format, "text/plain"))); - String errorHtml = TemplateLoader.load("errors/404." + format).render(binding); try { byte[] bytes = errorHtml.getBytes(Response.current().encoding); @@ -725,7 +760,7 @@ public static void serve404(NotFound e, ChannelHandlerContext ctx, Request reque protected static Map getBindingForErrors(Exception e, boolean isError) { - Map binding = new HashMap(); + Map binding = new HashMap<>(); if (!isError) { binding.put("result", e); } else { @@ -739,7 +774,7 @@ protected static Map getBindingForErrors(Exception e, boolean is try { binding.put("errors", Validation.errors()); } catch (Exception ex) { - //Logger.error(ex, "Error when getting Validation errors"); + // Logger.error(ex, "Error when getting Validation errors"); } return binding; @@ -796,7 +831,6 @@ public static void serve500(Exception e, ChannelHandlerContext ctx, HttpRequest format = "txt"; } - nettyResponse.headers().set("Content-Type", (MimeTypes.getContentType("500." + format, "text/plain"))); try { String errorHtml = TemplateLoader.load("errors/500." + format).render(binding); @@ -812,7 +846,7 @@ public static void serve500(Exception e, ChannelHandlerContext ctx, HttpRequest Logger.error(e, "Internal Server Error (500) for request %s", request.method + " " + request.url); Logger.error(ex, "Error during the 500 response generation"); try { - final String errorHtml = "Internal Error (check logs)"; + String errorHtml = "Internal Error (check logs)"; byte[] bytes = errorHtml.getBytes(encoding); ChannelBuffer buf = ChannelBuffers.copiedBuffer(bytes); setContentLength(nettyResponse, bytes.length); @@ -825,7 +859,7 @@ public static void serve500(Exception e, ChannelHandlerContext ctx, HttpRequest } } catch (Throwable exxx) { try { - final String errorHtml = "Internal Error (check logs)"; + String errorHtml = "Internal Error (check logs)"; byte[] bytes = errorHtml.getBytes(encoding); ChannelBuffer buf = ChannelBuffers.copiedBuffer(bytes); setContentLength(nettyResponse, bytes.length); @@ -845,7 +879,8 @@ public static void serve500(Exception e, ChannelHandlerContext ctx, HttpRequest } } - public void serveStatic(RenderStatic renderStatic, ChannelHandlerContext ctx, Request request, Response response, HttpRequest nettyRequest, MessageEvent e) { + public void serveStatic(RenderStatic renderStatic, ChannelHandlerContext ctx, Request request, Response response, + HttpRequest nettyRequest, MessageEvent e) { if (Logger.isTraceEnabled()) { Logger.trace("serveStatic: begin"); } @@ -869,8 +904,8 @@ public void serveStatic(RenderStatic renderStatic, ChannelHandlerContext ctx, Re if (raw) { copyResponse(ctx, request, response, nettyRequest); } else { - final File localFile = file.getRealFile(); - final boolean keepAlive = isKeepAlive(nettyRequest); + File localFile = file.getRealFile(); + boolean keepAlive = isKeepAlive(nettyRequest); nettyResponse = addEtag(nettyRequest, nettyResponse, localFile); if (nettyResponse.getStatus().equals(HttpResponseStatus.NOT_MODIFIED)) { @@ -892,7 +927,7 @@ public void serveStatic(RenderStatic renderStatic, ChannelHandlerContext ctx, Re Logger.error(ez, "serveStatic for request %s", request.method + " " + request.url); try { HttpResponse errorResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR); - final String errorHtml = "Internal Error (check logs)"; + String errorHtml = "Internal Error (check logs)"; byte[] bytes = errorHtml.getBytes(response.encoding); ChannelBuffer buf = ChannelBuffers.copiedBuffer(bytes); setContentLength(nettyResponse, bytes.length); @@ -900,7 +935,7 @@ public void serveStatic(RenderStatic renderStatic, ChannelHandlerContext ctx, Re ChannelFuture future = ctx.getChannel().write(errorResponse); future.addListener(ChannelFutureListener.CLOSE); } catch (Exception ex) { - Logger.error(ez, "serveStatic for request %s", request.method + " " + request.url); + Logger.error(ex, "serveStatic for request %s", request.method + " " + request.url); } } if (Logger.isTraceEnabled()) { @@ -911,7 +946,7 @@ public void serveStatic(RenderStatic renderStatic, ChannelHandlerContext ctx, Re public static boolean isModified(String etag, long last, HttpRequest nettyRequest) { if (nettyRequest.headers().contains(IF_NONE_MATCH)) { - final String browserEtag = nettyRequest.headers().get(IF_NONE_MATCH); + String browserEtag = nettyRequest.headers().get(IF_NONE_MATCH); if (browserEtag.equals(etag)) { return false; } @@ -919,7 +954,7 @@ public static boolean isModified(String etag, long last, HttpRequest nettyReques } if (nettyRequest.headers().contains(IF_MODIFIED_SINCE)) { - final String ifModifiedSince = nettyRequest.headers().get(IF_MODIFIED_SINCE); + String ifModifiedSince = nettyRequest.headers().get(IF_MODIFIED_SINCE); if (!StringUtils.isEmpty(ifModifiedSince)) { try { @@ -940,19 +975,19 @@ private static HttpResponse addEtag(HttpRequest nettyRequest, HttpResponse httpR if (Play.mode == Play.Mode.DEV) { httpResponse.headers().set(CACHE_CONTROL, "no-cache"); } else { - // Check if Cache-Control header is not set - if (httpResponse.headers().get(CACHE_CONTROL) == null) { - String maxAge = Play.configuration.getProperty("http.cacheControl", "3600"); - if (maxAge.equals("0")) { - httpResponse.headers().set(CACHE_CONTROL, "no-cache"); - } else { - httpResponse.headers().set(CACHE_CONTROL, "max-age=" + maxAge); - } - } + // Check if Cache-Control header is not set + if (httpResponse.headers().get(CACHE_CONTROL) == null) { + String maxAge = Play.configuration.getProperty("http.cacheControl", "3600"); + if (maxAge.equals("0")) { + httpResponse.headers().set(CACHE_CONTROL, "no-cache"); + } else { + httpResponse.headers().set(CACHE_CONTROL, "max-age=" + maxAge); + } + } } boolean useEtag = Play.configuration.getProperty("http.useETag", "true").equals("true"); long last = file.lastModified(); - final String etag = "\"" + last + "-" + file.hashCode() + "\""; + String etag = "\"" + last + "-" + file.hashCode() + "\""; if (!isModified(etag, last, nettyRequest)) { if (nettyRequest.getMethod().equals(HttpMethod.GET)) { httpResponse.setStatus(HttpResponseStatus.NOT_MODIFIED); @@ -978,16 +1013,17 @@ public static void setContentLength(HttpMessage message, long contentLength) { message.headers().set(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(contentLength)); } - static class LazyChunkedInput implements org.jboss.netty.handler.stream.ChunkedInput { private boolean closed = false; - private ConcurrentLinkedQueue nextChunks = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue nextChunks = new ConcurrentLinkedQueue<>(); + @Override public boolean hasNextChunk() throws Exception { return !nextChunks.isEmpty(); } + @Override public Object nextChunk() throws Exception { if (nextChunks.isEmpty()) { return null; @@ -995,10 +1031,12 @@ public Object nextChunk() throws Exception { return wrappedBuffer(nextChunks.poll()); } + @Override public boolean isEndOfInput() throws Exception { return closed && nextChunks.isEmpty(); } + @Override public void close() throws Exception { if (!closed) { nextChunks.offer("0\r\n\r\n".getBytes()); @@ -1012,8 +1050,8 @@ public void writeChunk(Object chunk) throws Exception { } byte[] bytes; - if ( chunk instanceof byte[]) { - bytes = (byte[])chunk; + if (chunk instanceof byte[]) { + bytes = (byte[]) chunk; } else { String message = chunk == null ? "" : chunk.toString(); bytes = message.getBytes(Response.current().encoding); @@ -1021,11 +1059,11 @@ public void writeChunk(Object chunk) throws Exception { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); byteStream.write(Integer.toHexString(bytes.length).getBytes()); - final byte[] crlf = new byte[]{(byte)'\r', (byte)'\n'}; + byte[] crlf = new byte[] { (byte) '\r', (byte) '\n' }; byteStream.write(crlf); byteStream.write(bytes); byteStream.write(crlf); - nextChunks.offer( byteStream.toByteArray()); + nextChunks.offer(byteStream.toByteArray()); } } @@ -1037,12 +1075,12 @@ public void writeChunk(Request playRequest, Response playResponse, ChannelHandle copyResponse(ctx, playRequest, playResponse, nettyRequest); } ((LazyChunkedInput) playResponse.direct).writeChunk(chunk); - + if (this.pipelines.get("ChunkedWriteHandler") != null) { - ((ChunkedWriteHandler)this.pipelines.get("ChunkedWriteHandler")).resumeTransfer(); + ((ChunkedWriteHandler) this.pipelines.get("ChunkedWriteHandler")).resumeTransfer(); } - if (this.pipelines.get("SslChunkedWriteHandler") != null) { - ((ChunkedWriteHandler)this.pipelines.get("SslChunkedWriteHandler")).resumeTransfer(); + if (this.pipelines.get("SslChunkedWriteHandler") != null) { + ((ChunkedWriteHandler) this.pipelines.get("SslChunkedWriteHandler")).resumeTransfer(); } } catch (Exception e) { throw new UnexpectedException(e); @@ -1053,10 +1091,10 @@ public void closeChunked(Request playRequest, Response playResponse, ChannelHand try { ((LazyChunkedInput) playResponse.direct).close(); if (this.pipelines.get("ChunkedWriteHandler") != null) { - ((ChunkedWriteHandler)this.pipelines.get("ChunkedWriteHandler")).resumeTransfer(); + ((ChunkedWriteHandler) this.pipelines.get("ChunkedWriteHandler")).resumeTransfer(); } - if (this.pipelines.get("SslChunkedWriteHandler") != null) { - ((ChunkedWriteHandler)this.pipelines.get("SslChunkedWriteHandler")).resumeTransfer(); + if (this.pipelines.get("SslChunkedWriteHandler") != null) { + ((ChunkedWriteHandler) this.pipelines.get("SslChunkedWriteHandler")).resumeTransfer(); } } catch (Exception e) { throw new UnexpectedException(e); @@ -1064,9 +1102,9 @@ public void closeChunked(Request playRequest, Response playResponse, ChannelHand } // ~~~~~~~~~~~ Websocket - final static Map channels = new ConcurrentHashMap(); + static final Map channels = new ConcurrentHashMap<>(); - private void websocketFrameReceived(final ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) { + private void websocketFrameReceived(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) { Http.Inbound inbound = channels.get(ctx); // Check for closing frame if (webSocketFrame instanceof CloseWebSocketFrame) { @@ -1076,38 +1114,41 @@ private void websocketFrameReceived(final ChannelHandlerContext ctx, WebSocketFr } else if (webSocketFrame instanceof BinaryWebSocketFrame) { inbound._received(new Http.WebSocketFrame(webSocketFrame.getBinaryData().array())); } else if (webSocketFrame instanceof TextWebSocketFrame) { - inbound._received(new Http.WebSocketFrame(((TextWebSocketFrame)webSocketFrame).getText())); + inbound._received(new Http.WebSocketFrame(((TextWebSocketFrame) webSocketFrame).getText())); } } - + private String getWebSocketLocation(HttpRequest req) { return "ws://" + req.headers().get(HttpHeaders.Names.HOST) + req.getUri(); } private void websocketHandshake(final ChannelHandlerContext ctx, HttpRequest req, MessageEvent messageEvent) throws Exception { - Integer max = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "65345")); - // Upgrade the pipeline as the handshaker needs the HttpStream Aggregator + // Upgrade the pipeline as the handshaker needs the HttpStream + // Aggregator ctx.getPipeline().addLast("fake-aggregator", new HttpChunkAggregator(max)); - try { - WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( - this.getWebSocketLocation(req), null, false); - this.handshaker = wsFactory.newHandshaker(req); - if (this.handshaker == null) { - wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); - } else { - try { - this.handshaker.handshake(ctx.getChannel(), req); - } catch(Exception e) { - e.printStackTrace(); + try { + WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(this.getWebSocketLocation(req), null, false); + this.handshaker = wsFactory.newHandshaker(req); + if (this.handshaker == null) { + wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); + } else { + try { + this.handshaker.handshake(ctx.getChannel(), req); + } catch (Exception e) { + e.printStackTrace(); + } } - } } finally { - // Remove fake aggregator in case handshake was not a sucess, it is still lying around - try { ctx.getPipeline().remove("fake-aggregator"); } catch(Exception e) {} + // Remove fake aggregator in case handshake was not a success, it is + // still lying around + try { + ctx.getPipeline().remove("fake-aggregator"); + } catch (Exception e) { + } } Http.Request request = parseRequest(ctx, req, messageEvent); @@ -1121,7 +1162,6 @@ private void websocketHandshake(final ChannelHandlerContext ctx, HttpRequest req return; } - // Inbound Http.Inbound inbound = new Http.Inbound(ctx) { @@ -1143,6 +1183,7 @@ synchronized void writeAndClose(ChannelFuture writeFuture) { writeFutures.add(writeFuture); writeFuture.addListener(new ChannelFutureListener() { + @Override public void operationComplete(ChannelFuture cf) throws Exception { writeFutures.remove(cf); futureClose(); @@ -1181,9 +1222,10 @@ public synchronized boolean isOpen() { @Override public synchronized void close() { - closeTask = new Promise(); + closeTask = new Promise<>(); closeTask.onRedeem(new Action>() { + @Override public void invoke(Promise completed) { writeFutures.clear(); ctx.getChannel().disconnect(); @@ -1216,7 +1258,8 @@ public static class WebSocketInvocation extends Invoker.Invocation { ChannelHandlerContext ctx; MessageEvent e; - public WebSocketInvocation(Map route, Http.Request request, Http.Inbound inbound, Http.Outbound outbound, ChannelHandlerContext ctx, MessageEvent e) { + public WebSocketInvocation(Map route, Http.Request request, Http.Inbound inbound, Http.Outbound outbound, + ChannelHandlerContext ctx, MessageEvent e) { this.route = route; this.request = request; this.inbound = inbound; @@ -1236,8 +1279,7 @@ public boolean init() { @Override public InvocationContext getInvocationContext() { WebSocketInvoker.resolve(request); - return new InvocationContext(Http.invocationType, - request.invokedMethod.getAnnotations(), + return new InvocationContext(Http.invocationType, request.invokedMethod.getAnnotations(), request.invokedMethod.getDeclaringClass().getAnnotations()); } diff --git a/framework/src/play/server/Server.java b/framework/src/play/server/Server.java index 3b17056630..f010a3a549 100644 --- a/framework/src/play/server/Server.java +++ b/framework/src/play/server/Server.java @@ -21,12 +21,12 @@ public class Server { public static int httpPort; public static int httpsPort; - public final static String PID_FILE = "server.pid"; + public static final String PID_FILE = "server.pid"; public Server(String[] args) { System.setProperty("file.encoding", "utf-8"); - final Properties p = Play.configuration; + Properties p = Play.configuration; httpPort = Integer.parseInt(getOpt(args, "http.port", p.getProperty("http.port", "-1"))); httpsPort = Integer.parseInt(getOpt(args, "https.port", p.getProperty("https.port", "-1"))); @@ -127,7 +127,7 @@ public Server(String[] args) { if (Play.mode == Mode.DEV || Play.runingInTestMode()) { // print this line to STDOUT - not using logger, so auto test runner will not block if logger is misconfigured (see #1222) System.out.println("~ Server is up and running"); - } + } } private String getOpt(String[] args, String arg, String defaultValue) { @@ -150,19 +150,26 @@ private static void writePID(File root) { } public static void main(String[] args) throws Exception { - File root = new File(System.getProperty("application.path")); - if (System.getProperty("precompiled", "false").equals("true")) { - Play.usePrecompiled = true; - } - if (System.getProperty("writepid", "false").equals("true")) { - writePID(root); + try { + File root = new File(System.getProperty("application.path", ".")); + if (System.getProperty("precompiled", "false").equals("true")) { + Play.usePrecompiled = true; + } + if (System.getProperty("writepid", "false").equals("true")) { + writePID(root); + } + + Play.init(root, System.getProperty("play.id", "")); + + if (System.getProperty("precompile") == null) { + new Server(args); + } else { + Logger.info("Done."); + } } - Play.init(root, System.getProperty("play.id", "")); - if (System.getProperty("precompile") == null) { - new Server(args); - } else { - Logger.info("Done."); + catch (Throwable e) { + Logger.fatal(e, "Failed to start"); + System.exit(1); } } - } diff --git a/framework/src/play/server/ServletWrapper.java b/framework/src/play/server/ServletWrapper.java index 7a51c10537..ad242ab9b5 100644 --- a/framework/src/play/server/ServletWrapper.java +++ b/framework/src/play/server/ServletWrapper.java @@ -65,12 +65,13 @@ public class ServletWrapper extends HttpServlet implements ServletContextListene private static boolean routerInitializedWithContext = false; + @Override public void contextInitialized(ServletContextEvent e) { Play.standalonePlayServer = false; ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); String appDir = e.getServletContext().getRealPath("/WEB-INF/application"); File root = new File(appDir); - final String playId = System.getProperty("play.id", e.getServletContext().getInitParameter("play.id")); + String playId = System.getProperty("play.id", e.getServletContext().getInitParameter("play.id")); if (StringUtils.isEmpty(playId)) { throw new UnexpectedException("Please define a play.id parameter in your web.xml file. Without that parameter, play! cannot start your application. Please add a context-param into the WEB-INF/web.xml file."); } @@ -91,6 +92,7 @@ public void contextInitialized(ServletContextEvent e) { Thread.currentThread().setContextClassLoader(oldClassLoader); } + @Override public void contextDestroyed(ServletContextEvent e) { Play.stop(); } @@ -157,8 +159,8 @@ protected void service(HttpServletRequest httpServletRequest, HttpServletRespons serveStatic(httpServletResponse, httpServletRequest, e); return; } catch(URISyntaxException e) { - serve404(httpServletRequest, httpServletResponse, new NotFound(e.toString())); - return; + serve404(httpServletRequest, httpServletResponse, new NotFound(e.toString())); + return; } catch (Throwable e) { throw new ServletException(e); } finally { @@ -245,8 +247,8 @@ private static boolean isValidTimeStamp(String lastDateString, String dateString } public static Request parseRequest(HttpServletRequest httpServletRequest) throws Exception { - - URI uri = new URI(httpServletRequest.getRequestURI()); + + URI uri = new URI(httpServletRequest.getRequestURI()); String method = httpServletRequest.getMethod().intern(); String path = uri.getPath(); String querystring = httpServletRequest.getQueryString() == null ? "" : httpServletRequest.getQueryString(); @@ -287,7 +289,7 @@ public static Request parseRequest(HttpServletRequest httpServletRequest) throws boolean isLoopback = host.matches("^127\\.0\\.0\\.1:?[0-9]*$"); - final Request request = Request.createRequest( + Request request = Request.createRequest( remoteAddress, method, path, @@ -311,16 +313,16 @@ public static Request parseRequest(HttpServletRequest httpServletRequest) throws } protected static Map getHeaders(HttpServletRequest httpServletRequest) { - Map headers = new HashMap(16); + Map headers = new HashMap<>(16); - Enumeration headersNames = httpServletRequest.getHeaderNames(); + Enumeration headersNames = httpServletRequest.getHeaderNames(); while (headersNames.hasMoreElements()) { Http.Header hd = new Http.Header(); - hd.name = (String) headersNames.nextElement(); - hd.values = new ArrayList(); - Enumeration enumValues = httpServletRequest.getHeaders(hd.name); + hd.name = headersNames.nextElement(); + hd.values = new ArrayList<>(); + Enumeration enumValues = httpServletRequest.getHeaders(hd.name); while (enumValues.hasMoreElements()) { - String value = (String) enumValues.nextElement(); + String value = enumValues.nextElement(); hd.values.add(value); } headers.put(hd.name.toLowerCase(), hd); @@ -330,7 +332,7 @@ protected static Map getHeaders(HttpServletRequest httpServ } protected static Map getCookies(HttpServletRequest httpServletRequest) { - Map cookies = new HashMap(16); + Map cookies = new HashMap<>(16); javax.servlet.http.Cookie[] cookiesViaServlet = httpServletRequest.getCookies(); if (cookiesViaServlet != null) { for (javax.servlet.http.Cookie cookie : cookiesViaServlet) { @@ -352,7 +354,7 @@ public void serve404(HttpServletRequest servletRequest, HttpServletResponse serv Logger.warn("404 -> %s %s (%s)", servletRequest.getMethod(), servletRequest.getRequestURI(), e.getMessage()); servletResponse.setStatus(404); servletResponse.setContentType("text/html"); - Map binding = new HashMap(); + Map binding = new HashMap<>(); binding.put("result", e); binding.put("session", Scope.Session.current()); binding.put("request", Http.Request.current()); @@ -362,7 +364,7 @@ public void serve404(HttpServletRequest servletRequest, HttpServletResponse serv try { binding.put("errors", Validation.errors()); } catch (Exception ex) { - // + Logger.error(ex, "Failed to bind errors"); } String format = Request.current().format; servletResponse.setStatus(404); @@ -384,7 +386,7 @@ public void serve404(HttpServletRequest servletRequest, HttpServletResponse serv public void serve500(Exception e, HttpServletRequest request, HttpServletResponse response) { try { - Map binding = new HashMap(); + Map binding = new HashMap<>(); if (!(e instanceof PlayException)) { e = new play.exceptions.UnexpectedException(e); } @@ -403,7 +405,7 @@ public void serve500(Exception e, HttpServletRequest request, HttpServletRespons } } } catch (Exception exx) { - // humm ? + Logger.error(exx, "Failed to flush cookies"); } binding.put("exception", e); binding.put("session", Scope.Session.current()); @@ -414,7 +416,7 @@ public void serve500(Exception e, HttpServletRequest request, HttpServletRespons try { binding.put("errors", Validation.errors()); } catch (Exception ex) { - // + Logger.error(ex, "Failed to bind errors"); } response.setStatus(500); String format = "html"; diff --git a/framework/src/play/server/StreamChunkAggregator.java b/framework/src/play/server/StreamChunkAggregator.java index ecf0df8ea9..98007dd04c 100644 --- a/framework/src/play/server/StreamChunkAggregator.java +++ b/framework/src/play/server/StreamChunkAggregator.java @@ -16,7 +16,7 @@ public class StreamChunkAggregator extends SimpleChannelUpstreamHandler { private volatile HttpMessage currentMessage; private volatile OutputStream out; - private final static int maxContentLength = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "-1")); + private static final int maxContentLength = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "-1")); private volatile File file; /** @@ -37,7 +37,7 @@ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Ex if (currentMessage == null) { HttpMessage m = (HttpMessage) msg; if (m.isChunked()) { - final String localName = UUID.randomUUID().toString(); + String localName = UUID.randomUUID().toString(); // A chunked message - remove 'Transfer-Encoding' header, // initialize the cumulative buffer, and wait for incoming chunks. List encodings = m.headers().getAll(HttpHeaders.Names.TRANSFER_ENCODING); @@ -55,7 +55,7 @@ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Ex } else { // TODO: If less that threshold then in memory // Merge the received chunk into the content of the current message. - final HttpChunk chunk = (HttpChunk) msg; + HttpChunk chunk = (HttpChunk) msg; if (maxContentLength != -1 && (localFile.length() > (maxContentLength - chunk.getContent().readableBytes()))) { currentMessage.headers().set(HttpHeaders.Names.WARNING, "play.netty.content.length.exceeded"); } else { @@ -72,7 +72,7 @@ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Ex currentMessage.setContent(new FileChannelBuffer(localFile)); this.out = null; this.currentMessage = null; - this.file.delete(); + this.file.delete(); this.file = null; Channels.fireMessageReceived(ctx, currentMessage, e.getRemoteAddress()); } diff --git a/framework/src/play/server/ssl/SslHttpServerContextFactory.java b/framework/src/play/server/ssl/SslHttpServerContextFactory.java index 7c05a6f4e2..7f0642462f 100644 --- a/framework/src/play/server/ssl/SslHttpServerContextFactory.java +++ b/framework/src/play/server/ssl/SslHttpServerContextFactory.java @@ -12,10 +12,9 @@ import java.net.Socket; import java.security.*; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; -import java.util.Vector; - -import static org.apache.commons.io.IOUtils.closeQuietly; public class SslHttpServerContextFactory { @@ -32,13 +31,12 @@ public class SslHttpServerContextFactory { SSLContext serverContext = null; KeyStore ks = null; try { - final Properties p = Play.configuration; + Properties p = Play.configuration; // Made sure play reads the properties // Look if we have key and cert files. If we do, we use our own keymanager if (Play.getFile(p.getProperty("certificate.key.file", "conf/host.key")).exists() - && Play.getFile(p.getProperty("certificate.file", "conf/host.cert")).exists()) - { + && Play.getFile(p.getProperty("certificate.file", "conf/host.cert")).exists()) { Security.addProvider(new BouncyCastleProvider()); // Initialize the SSLContext to work with our key managers. @@ -83,65 +81,67 @@ public static class PEMKeyManager extends X509ExtendedKeyManager { X509Certificate[] chain; public PEMKeyManager() { - PEMReader keyReader = null; - PEMReader reader = null; - try { - final Properties p = Play.configuration; - - keyReader = new PEMReader(new FileReader(Play.getFile(p.getProperty("certificate.key.file", - "conf/host.key"))), - new PasswordFinder() { - public char[] getPassword() { - return p.getProperty("certificate.password", "secret").toCharArray(); - } - }); - key = ((KeyPair) keyReader.readObject()).getPrivate(); + final Properties p = Play.configuration; + String keyFile = p.getProperty("certificate.key.file", "conf/host.key"); - reader = new PEMReader(new FileReader(Play.getFile(p.getProperty("certificate.file", "conf/host.cert")))); + try (PEMReader keyReader = new PEMReader(new FileReader(Play.getFile(keyFile)), new PEMPasswordFinder())) { + key = ((KeyPair) keyReader.readObject()).getPrivate(); - X509Certificate cert; - Vector chainVector = new Vector(); + try (PEMReader reader = new PEMReader(new FileReader(Play.getFile(p.getProperty("certificate.file", "conf/host.cert"))))) { + X509Certificate cert; + List chainVector = new ArrayList<>(); - while ((cert = (X509Certificate) reader.readObject()) != null) { - chainVector.add(cert); - } - chain = (X509Certificate[])chainVector.toArray(new X509Certificate[1]); + while ((cert = (X509Certificate) reader.readObject()) != null) { + chainVector.add(cert); + } + chain = chainVector.toArray(new X509Certificate[1]); + } } catch (Exception e) { - e.printStackTrace(); - Logger.error(e, ""); - } finally { - closeQuietly(keyReader); - closeQuietly(reader); + Logger.error(e, "Failed to initialize PEMKeyManager from file %s", keyFile); } } + @Override public String chooseEngineServerAlias(String s, Principal[] principals, SSLEngine sslEngine) { return ""; } + @Override public String[] getClientAliases(String s, Principal[] principals) { return new String[]{""}; } + @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { return ""; } + @Override public String[] getServerAliases(String s, Principal[] principals) { return new String[]{""}; } + @Override public String chooseServerAlias(String s, Principal[] principals, Socket socket) { return ""; } + @Override public X509Certificate[] getCertificateChain(String s) { return chain; } + @Override public PrivateKey getPrivateKey(String s) { return key; } } + + private static class PEMPasswordFinder implements PasswordFinder { + @Override + public char[] getPassword() { + return Play.configuration.getProperty("certificate.password", "secret").toCharArray(); + } + } } diff --git a/framework/src/play/server/ssl/SslHttpServerPipelineFactory.java b/framework/src/play/server/ssl/SslHttpServerPipelineFactory.java index db31965179..295d6643e0 100644 --- a/framework/src/play/server/ssl/SslHttpServerPipelineFactory.java +++ b/framework/src/play/server/ssl/SslHttpServerPipelineFactory.java @@ -1,26 +1,28 @@ package play.server.ssl; +import static org.jboss.netty.channel.Channels.pipeline; + import javax.net.ssl.SSLEngine; +import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.handler.ssl.SslHandler; -import play.Play; -import play.server.HttpServerPipelineFactory; - -import org.jboss.netty.channel.ChannelHandler; import play.Logger; -import static org.jboss.netty.channel.Channels.pipeline; - +import play.Play; +import play.server.HttpServerPipelineFactory; public class SslHttpServerPipelineFactory extends HttpServerPipelineFactory { - private String pipelineConfig = Play.configuration.getProperty("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.stream.ChunkedWriteHandler,play.server.ssl.SslPlayHandler"); + private String pipelineConfig = Play.configuration.getProperty("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.stream.ChunkedWriteHandler,play.server.ssl.SslPlayHandler"); + @Override public ChannelPipeline getPipeline() throws Exception { String mode = Play.configuration.getProperty("play.netty.clientAuth", "none"); String enabledCiphers = Play.configuration.getProperty("play.ssl.enabledCiphers", ""); + String enabledProtocols = Play.configuration.getProperty("play.ssl.enabledProtocols", ""); ChannelPipeline pipeline = pipeline(); @@ -31,24 +33,28 @@ public ChannelPipeline getPipeline() throws Exception { if (enabledCiphers != null && enabledCiphers.length() > 0) { engine.setEnabledCipherSuites(enabledCiphers.replaceAll(" ", "").split(",")); } - + if ("want".equalsIgnoreCase(mode)) { engine.setWantClientAuth(true); } else if ("need".equalsIgnoreCase(mode)) { engine.setNeedClientAuth(true); } - + + if (enabledProtocols != null && enabledProtocols.trim().length() > 0) { + engine.setEnabledProtocols(enabledProtocols.replaceAll(" ", "").split(",")); + } + engine.setEnableSessionCreation(true); pipeline.addLast("ssl", new SslHandler(engine)); - + // Get all the pipeline. Give the user the opportunity to add their own String[] handlers = pipelineConfig.split(","); - if(handlers.length <= 0){ + if (handlers.length <= 0) { Logger.error("You must defined at least the SslPlayHandler in \"play.netty.pipeline\""); return pipeline; } - + // Create the play Handler (always the last one) String handler = handlers[handlers.length - 1]; ChannelHandler instance = getInstance(handler); @@ -56,18 +62,18 @@ public ChannelPipeline getPipeline() throws Exception { if (instance == null || !(instance instanceof SslPlayHandler) || sslPlayHandler == null) { Logger.error("The last handler must be the SslPlayHandler in \"play.netty.pipeline\""); return pipeline; - } - + } + for (int i = 0; i < handlers.length - 1; i++) { handler = handlers[i]; try { String name = getName(handler.trim()); instance = getInstance(handler); if (instance != null) { - pipeline.addLast(name, instance); + pipeline.addLast(name, instance); sslPlayHandler.pipelines.put("Ssl" + name, instance); } - } catch(Throwable e) { + } catch (Throwable e) { Logger.error(" error adding " + handler, e); } @@ -76,9 +82,8 @@ public ChannelPipeline getPipeline() throws Exception { if (sslPlayHandler != null) { pipeline.addLast("handler", sslPlayHandler); sslPlayHandler.pipelines.put("SslHandler", sslPlayHandler); - } - + } + return pipeline; } } - diff --git a/framework/src/play/server/ssl/SslPlayHandler.java b/framework/src/play/server/ssl/SslPlayHandler.java index 30fcee266e..ac2f38cc68 100644 --- a/framework/src/play/server/ssl/SslPlayHandler.java +++ b/framework/src/play/server/ssl/SslPlayHandler.java @@ -26,7 +26,7 @@ public Request parseRequest(ChannelHandlerContext ctx, HttpRequest nettyRequest, public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { ctx.setAttachment(e.getValue()); // Get the SslHandler in the current pipeline. - final SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class); + SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class); sslHandler.setEnableRenegotiation(false); // Get notified when SSL handshake is done. ChannelFuture handshakeFuture = sslHandler.handshake(); @@ -35,6 +35,7 @@ public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) thr private static final class SslListener implements ChannelFutureListener { + @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { Logger.debug(future.getCause(), "Invalid certificate"); diff --git a/framework/src/play/templates/BaseTemplate.java b/framework/src/play/templates/BaseTemplate.java index 3978368bf4..dae9a543c0 100644 --- a/framework/src/play/templates/BaseTemplate.java +++ b/framework/src/play/templates/BaseTemplate.java @@ -16,14 +16,11 @@ import play.libs.Codec; import play.libs.IO; -/** - * A template - */ public abstract class BaseTemplate extends Template { public String compiledSource; - public Map linesMatrix = new HashMap(); - public Set doBodyLines = new HashSet(); + public Map linesMatrix = new HashMap<>(); + public Set doBodyLines = new HashSet<>(); public Class compiledTemplate; public String compiledTemplateName; public Long timestamp = System.currentTimeMillis(); @@ -44,7 +41,7 @@ public void loadPrecompiled() { byte[] code = IO.readContent(file); directLoad(code); } catch (Exception e) { - throw new RuntimeException("Cannot load precompiled template " + name); + throw new RuntimeException("Cannot load precompiled template " + name, e); } } @@ -94,11 +91,11 @@ void throwException(Throwable e) { } protected abstract Throwable cleanStackTrace(Throwable e); - public static ThreadLocal layout = new ThreadLocal(); - public static ThreadLocal> layoutData = new ThreadLocal>(); - public static ThreadLocal currentTemplate = new ThreadLocal(); + public static final ThreadLocal layout = new ThreadLocal<>(); + public static final ThreadLocal> layoutData = new ThreadLocal<>(); + public static final ThreadLocal currentTemplate = new ThreadLocal<>(); - public static class RawData { + public static final class RawData { public String data; diff --git a/framework/src/play/templates/FastTags.java b/framework/src/play/templates/FastTags.java index 4aaca917f0..cf1aa0cc91 100644 --- a/framework/src/play/templates/FastTags.java +++ b/framework/src/play/templates/FastTags.java @@ -33,9 +33,6 @@ import play.templates.GroovyTemplate.ExecutableTemplate; import play.utils.HTML; -/** - * Fast tags implementation - */ public class FastTags { public static void _cache(Map args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) { @@ -84,12 +81,12 @@ public static void _jsAction(Map args, Closure body, PrintWriter out, Exec } public static void _jsRoute(Map args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) { - final Object arg = args.get("arg"); + Object arg = args.get("arg"); if (!(arg instanceof ActionDefinition)) { throw new TemplateExecutionException(template.template, fromLine, "Wrong parameter type, try #{jsRoute @Application.index() /}", new TagInternalException("Wrong parameter type")); } - final ActionDefinition action = (ActionDefinition) arg; + ActionDefinition action = (ActionDefinition) arg; out.print("{"); if (action.args.isEmpty()) { out.print("url: function() { return '" + action.url.replace("&", "&") + "'; },"); @@ -130,7 +127,16 @@ public static void _option(Map args, Closure body, PrintWriter out, Execut * template line number where the tag is defined */ public static void _form(Map args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) { - ActionDefinition actionDef = (ActionDefinition) args.get("arg"); + ActionDefinition actionDef = null; + Object arg = args.get("arg"); + if (arg instanceof ActionDefinition) { + actionDef = (ActionDefinition) arg; + } + else if (arg != null) { + actionDef = new ActionDefinition(); + actionDef.url = arg.toString(); + actionDef.method = "POST"; + } if (actionDef == null) { actionDef = (ActionDefinition) args.get("action"); } @@ -154,7 +160,7 @@ public static void _form(Map args, Closure body, PrintWriter out, Executab actionDef.method = "POST"; } String encoding = Http.Response.current().encoding; - out.print("
    "); if (!("GET".equals(actionDef.method))) { @@ -179,7 +185,7 @@ public static void _form(Map args, Closure body, PrintWriter out, Executab * template line number where the tag is defined */ public static void _field(Map args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) { - Map field = new HashMap(); + Map field = new HashMap<>(); String _arg = args.get("arg").toString(); field.put("name", _arg); field.put("id", _arg.replace('.', '_')); @@ -408,7 +414,7 @@ public static void _include(Map args, Closure body, PrintWriter out, Execu name = ct + name.substring(1); } BaseTemplate t = (BaseTemplate) TemplateLoader.load(name); - Map newArgs = new HashMap(); + Map newArgs = new HashMap<>(); newArgs.putAll(template.getBinding().getVariables()); newArgs.put("_isInclude", true); t.internalRender(newArgs); @@ -435,7 +441,7 @@ public static void _render(Map args, Closure body, PrintWriter out, Execut } args.remove("arg"); BaseTemplate t = (BaseTemplate) TemplateLoader.load(name); - Map newArgs = new HashMap(); + Map newArgs = new HashMap<>(); newArgs.putAll((Map) args); newArgs.put("_isInclude", true); newArgs.put("out", out); @@ -450,7 +456,7 @@ public static void _embeddedImage(Map args, Closure body, PrintWriter out, String src = (args.containsKey("arg") && args.get("arg") != null) ? args.get("arg").toString() : args.get("src").toString(); if (src != null) { String name = (args.containsKey("name")) ? args.get("name").toString() : null; - out.print(""); + out.print(""); } } else { throw new TemplateExecutionException(template.template, fromLine, "Specify a file name", new TagInternalException( diff --git a/framework/src/play/templates/GroovyTemplate.java b/framework/src/play/templates/GroovyTemplate.java index 2d5b4920c1..012d812263 100644 --- a/framework/src/play/templates/GroovyTemplate.java +++ b/framework/src/play/templates/GroovyTemplate.java @@ -1,25 +1,55 @@ package play.templates; -import com.jamonapi.Monitor; -import com.jamonapi.MonitorFactory; -import groovy.lang.*; +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + import org.apache.commons.io.FileUtils; -import org.codehaus.groovy.control.*; +import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilationUnit.GroovyClassOperation; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.MultipleCompilationErrorsException; +import org.codehaus.groovy.control.Phases; +import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.messages.ExceptionMessage; import org.codehaus.groovy.control.messages.Message; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.syntax.SyntaxException; import org.codehaus.groovy.tools.GroovyClass; + +import com.jamonapi.Monitor; +import com.jamonapi.MonitorFactory; + +import groovy.lang.Binding; +import groovy.lang.Closure; +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyObjectSupport; +import groovy.lang.GroovyShell; +import groovy.lang.MissingPropertyException; +import groovy.lang.Script; import play.Logger; import play.Play; import play.Play.Mode; import play.classloading.BytecodeCache; -import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer; import play.data.binding.Unbinder; -import play.exceptions.*; +import play.exceptions.ActionNotFoundException; +import play.exceptions.NoRouteFoundException; +import play.exceptions.PlayException; +import play.exceptions.TagInternalException; +import play.exceptions.TemplateCompilationException; +import play.exceptions.TemplateExecutionException; import play.exceptions.TemplateExecutionException.DoBodyException; +import play.exceptions.TemplateNotFoundException; +import play.exceptions.UnexpectedException; import play.i18n.Lang; import play.i18n.Messages; import play.libs.Codec; @@ -33,20 +63,10 @@ import play.utils.HTML; import play.utils.Java; -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.*; - -/** - * A template - */ public class GroovyTemplate extends BaseTemplate { - static final Map safeFormatters = new HashMap(); - + static final Map safeFormatters = new HashMap<>(); + static { safeFormatters.put("csv", new SafeCSVFormatter()); safeFormatters.put("html", new SafeHTMLFormatter()); @@ -60,7 +80,7 @@ public static void registerFormatter(String format, SafeFormatter formatter) static { new GroovyShell().evaluate("java.lang.String.metaClass.if = { condition -> if(condition) delegate; else '' }"); } - + public GroovyTemplate(String name, String source) { super(name, source); } @@ -74,13 +94,14 @@ public static class TClassLoader extends GroovyClassLoader { public TClassLoader() { super(Play.classloader); } - + public Class defineTemplate(String name, byte[] byteCode) { return defineClass(name, byteCode, 0, byteCode.length, Play.classloader.protectionDomain); } } @SuppressWarnings("unchecked") + @Override void directLoad(byte[] code) throws Exception { TClassLoader tClassLoader = new TClassLoader(); String[] lines = new String(code, "utf-8").split("\n"); @@ -95,20 +116,17 @@ void directLoad(byte[] code) throws Exception { } } } - - /** - * Define the Compiler configuration - * @return the compiler Configuration - */ - protected CompilerConfiguration setUpCompilerConfiguration(){ - CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); + + protected CompilerConfiguration setUpCompilerConfiguration() { + CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); compilerConfiguration.setSourceEncoding("utf-8"); // ouf return compilerConfiguration; } - - protected void onCompileEnd(){ + + protected void onCompileEnd() { } + @Override public void compile() { if (compiledTemplate == null) { try { @@ -116,25 +134,27 @@ public void compile() { TClassLoader tClassLoader = new TClassLoader(); // Let's compile the groovy source - final List groovyClassesForThisTemplate = new ArrayList(); + final List groovyClassesForThisTemplate = new ArrayList<>(); // ~~~ Please ! CompilerConfiguration compilerConfiguration = this.setUpCompilerConfiguration(); - + CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration); - compilationUnit.addSource(new SourceUnit(name, compiledSource, compilerConfiguration, tClassLoader, compilationUnit.getErrorCollector())); + compilationUnit.addSource( + new SourceUnit(name, compiledSource, compilerConfiguration, tClassLoader, compilationUnit.getErrorCollector())); Field phasesF = compilationUnit.getClass().getDeclaredField("phaseOperations"); phasesF.setAccessible(true); LinkedList[] phases = (LinkedList[]) phasesF.get(compilationUnit); - LinkedList output = new LinkedList(); + LinkedList output = new LinkedList<>(); phases[Phases.OUTPUT] = output; output.add(new GroovyClassOperation() { + @Override public void call(GroovyClass gclass) { groovyClassesForThisTemplate.add(gclass); } }); compilationUnit.compile(); - // ouf + // ouf // Define script classes StringBuilder sb = new StringBuilder(); @@ -157,7 +177,8 @@ public void call(GroovyClass gclass) { if (System.getProperty("precompile") != null) { try { // emit bytecode to standard class layout as well - File f = Play.getFile("precompiled/templates/" + name.replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent")); + File f = Play.getFile("precompiled/templates/" + + name.replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent")); f.getParentFile().mkdirs(); FileUtils.write(f, sb.toString(), "utf-8"); } catch (Exception e) { @@ -166,7 +187,8 @@ public void call(GroovyClass gclass) { } if (Logger.isTraceEnabled()) { - Logger.trace("%sms to compile template %s to %d classes", System.currentTimeMillis() - start, name, groovyClassesForThisTemplate.size()); + Logger.trace("%sms to compile template %s to %d classes", System.currentTimeMillis() - start, name, + groovyClassesForThisTemplate.size()); } } catch (MultipleCompilationErrorsException e) { @@ -184,18 +206,18 @@ public void call(GroovyClass gclass) { message = message.substring(0, message.lastIndexOf("@")); } throw new TemplateCompilationException(this, line, message); - } else{ - ExceptionMessage errorMessage = (ExceptionMessage ) e.getErrorCollector().getLastError(); + } else { + ExceptionMessage errorMessage = (ExceptionMessage) e.getErrorCollector().getLastError(); Exception exception = errorMessage.getCause(); Integer line = 0; String message = exception.getMessage(); - throw new TemplateCompilationException(this, line, message); + throw new TemplateCompilationException(this, line, message); } } throw new UnexpectedException(e); } catch (Exception e) { throw new UnexpectedException(e); - } finally{ + } finally { this.onCompileEnd(); } } @@ -211,20 +233,20 @@ public String render(Map args) { } } - protected Binding setUpBindingVariables(Map args){ + protected Binding setUpBindingVariables(Map args) { Binding binding = new Binding(args); binding.setVariable("play", new Play()); binding.setVariable("messages", new Messages()); binding.setVariable("lang", Lang.get()); return binding; } - + @Override protected String internalRender(Map args) { compile(); Binding binding = this.setUpBindingVariables(args); - + // If current response-object is present, add _response_encoding' Http.Response currentResponse = Http.Response.current(); if (currentResponse != null) { @@ -248,7 +270,7 @@ protected String internalRender(Map args) { currentTemplate.set(this); } if (!args.containsKey("_body") && !args.containsKey("_isLayout") && !args.containsKey("_isInclude")) { - layoutData.set(new HashMap()); + layoutData.set(new HashMap<>()); TagContext.init(); } ExecutableTemplate t = (ExecutableTemplate) InvokerHelper.createScript(compiledTemplate, binding); @@ -289,20 +311,20 @@ protected String internalRender(Map args) { } } if (applyLayouts && layout.get() != null) { - Map layoutArgs = new HashMap(args); + Map layoutArgs = new HashMap<>(args); layoutArgs.remove("out"); layoutArgs.put("_isLayout", true); String layoutR = layout.get().internalRender(layoutArgs); // Must replace '____%LAYOUT%____' inside the string layoutR with the content from writer.. - final String whatToFind = "____%LAYOUT%____"; - final int pos = layoutR.indexOf(whatToFind); - if (pos >=0) { + String whatToFind = "____%LAYOUT%____"; + int pos = layoutR.indexOf(whatToFind); + if (pos >= 0) { // prepending and appending directly to writer/buffer to prevent us // from having to duplicate the string. // this makes us use half of the memory! - writer.getBuffer().insert(0,layoutR.substring(0,pos)); - writer.append(layoutR.substring(pos+whatToFind.length())); + writer.getBuffer().insert(0, layoutR.substring(0, pos)); + writer.append(layoutR.substring(pos + whatToFind.length())); return writer.toString().trim(); } return layoutR; @@ -313,18 +335,19 @@ protected String internalRender(Map args) { return null; } + @Override protected Throwable cleanStackTrace(Throwable e) { - List cleanTrace = new ArrayList(); + List cleanTrace = new ArrayList<>(); for (StackTraceElement se : e.getStackTrace()) { - //Here we are parsing the classname to find the file on disk the template was generated from. - //See GroovyTemplateCompiler.head() for more info. + // Here we are parsing the classname to find the file on disk the template was generated from. + // See GroovyTemplateCompiler.head() for more info. if (se.getClassName().startsWith("Template_")) { String tn = se.getClassName().substring(9); if (tn.indexOf("$") > -1) { tn = tn.substring(0, tn.indexOf("$")); } BaseTemplate template = TemplateLoader.templates.get(tn); - if( template != null ) { + if (template != null) { Integer line = template.linesMatrix.get(se.getLineNumber()); if (line != null) { String ext = ""; @@ -337,7 +360,9 @@ protected Throwable cleanStackTrace(Throwable e) { } } } - if (!se.getClassName().startsWith("org.codehaus.groovy.") && !se.getClassName().startsWith("groovy.") && !se.getClassName().startsWith("sun.reflect.") && !se.getClassName().startsWith("java.lang.reflect.") && !se.getClassName().startsWith("Template_")) { + if (!se.getClassName().startsWith("org.codehaus.groovy.") && !se.getClassName().startsWith("groovy.") + && !se.getClassName().startsWith("sun.reflect.") && !se.getClassName().startsWith("java.lang.reflect.") + && !se.getClassName().startsWith("Template_")) { cleanTrace.add(se); } } @@ -348,7 +373,7 @@ protected Throwable cleanStackTrace(Throwable e) { /** * Groovy template */ - public static abstract class ExecutableTemplate extends Script { + public abstract static class ExecutableTemplate extends Script { // Leave this field public to allow custom creation of TemplateExecutionException from different pkg public GroovyTemplate template; @@ -361,7 +386,7 @@ public void init(GroovyTemplate t) { extension = template.name.substring(index + 1); } } - + @Override public Object getProperty(String property) { try { @@ -380,19 +405,20 @@ public void invokeTag(Integer fromLine, String tag, Map attrs, C BaseTemplate tagTemplate = null; try { - tagTemplate = (BaseTemplate)TemplateLoader.load("tags/" + templateName + "." + callerExtension); + tagTemplate = (BaseTemplate) TemplateLoader.load("tags/" + templateName + "." + callerExtension); } catch (TemplateNotFoundException e) { try { - tagTemplate = (BaseTemplate)TemplateLoader.load("tags/" + templateName + ".tag"); + tagTemplate = (BaseTemplate) TemplateLoader.load("tags/" + templateName + ".tag"); } catch (TemplateNotFoundException ex) { if (callerExtension.equals("tag")) { throw new TemplateNotFoundException("tags/" + templateName + ".tag", template, fromLine); } - throw new TemplateNotFoundException("tags/" + templateName + "." + callerExtension + " or tags/" + templateName + ".tag", template, fromLine); + throw new TemplateNotFoundException( + "tags/" + templateName + "." + callerExtension + " or tags/" + templateName + ".tag", template, fromLine); } } TagContext.enterTag(tag); - Map args = new HashMap(); + Map args = new HashMap<>(); args.put("session", getBinding().getVariables().get("session")); args.put("flash", getBinding().getVariables().get("flash")); args.put("request", getBinding().getVariables().get("request")); @@ -421,19 +447,27 @@ public void invokeTag(Integer fromLine, String tag, Map attrs, C } /** + * @param className + * The class name + * @return The given class + * @throws Exception + * if problem occured when loading the class * @deprecated '_' should not be used as an identifier, since it is a reserved keyword from source level 1.8 on - * use {@link #__loadClass} instead + * use {@link #__loadClass} instead */ @Deprecated public Class _(String className) throws Exception { return __loadClass(className); } - + /** * Load the class from Pay Class loader - * @param className : the class name + * + * @param className + * the class name * @return the given class * @throws Exception + * if problem occured when loading the class */ public Class __loadClass(String className) throws Exception { try { @@ -444,12 +478,15 @@ public Class __loadClass(String className) throws Exception { } /** - * This method is faster to call from groovy than __safe() since we only evaluate val.toString() - * if we need to + * This method is faster to call from groovy than __safe() since we only evaluate val.toString() if we need to + * + * @param val + * the object to evaluate + * @return The evaluating string */ public String __safeFaster(Object val) { if (val instanceof RawData) { - return ((RawData)val).data; + return ((RawData) val).data; } else if (extension != null) { SafeFormatter formatter = safeFormatters.get(extension); if (formatter != null) { @@ -460,18 +497,17 @@ public String __safeFaster(Object val) { } public String __getMessage(Object[] val) { - if (val==null) { - throw new NullPointerException("You are trying to resolve a message with an expression " + - "that is resolved to null - " + - "have you forgotten quotes around the message-key?"); + if (val == null) { + throw new NullPointerException("You are trying to resolve a message with an expression " + "that is resolved to null - " + + "have you forgotten quotes around the message-key?"); } if (val.length == 1) { return Messages.get(val[0]); } else { // extract args from val - Object[] args = new Object[val.length-1]; - for( int i=1;i r = new HashMap(); + Map r = new HashMap<>(); Method actionMethod = (Method) ActionInvoker.getActionMethod(action)[1]; - String[] names = (String[]) actionMethod.getDeclaringClass().getDeclaredField("$" + actionMethod.getName() + LocalVariablesNamesTracer.computeMethodHash(actionMethod.getParameterTypes())).get(null); + String[] names = Java.parameterNames(actionMethod); if (param instanceof Object[]) { - if(((Object[])param).length == 1 && ((Object[])param)[0] instanceof Map) { - r = (Map)((Object[])param)[0]; + if (((Object[]) param).length == 1 && ((Object[]) param)[0] instanceof Map) { + r = (Map) ((Object[]) param)[0]; } else { // too many parameters versus action, possibly a developer error. we must warn him. if (names.length < ((Object[]) param).length) { @@ -554,13 +590,16 @@ public Object invokeMethod(String name, Object param) { } for (int i = 0; i < ((Object[]) param).length; i++) { if (((Object[]) param)[i] instanceof Router.ActionDefinition && ((Object[]) param)[i] != null) { - Unbinder.unBind(r, ((Object[]) param)[i].toString(), i < names.length ? names[i] : "", actionMethod.getAnnotations()); + Unbinder.unBind(r, ((Object[]) param)[i].toString(), i < names.length ? names[i] : "", + actionMethod.getAnnotations()); } else if (isSimpleParam(actionMethod.getParameterTypes()[i])) { if (((Object[]) param)[i] != null) { - Unbinder.unBind(r, ((Object[]) param)[i].toString(), i < names.length ? names[i] : "", actionMethod.getAnnotations()); + Unbinder.unBind(r, ((Object[]) param)[i].toString(), i < names.length ? names[i] : "", + actionMethod.getAnnotations()); } } else { - Unbinder.unBind(r, ((Object[]) param)[i], i < names.length ? names[i] : "", actionMethod.getAnnotations()); + Unbinder.unBind(r, ((Object[]) param)[i], i < names.length ? names[i] : "", + actionMethod.getAnnotations()); } } } diff --git a/framework/src/play/templates/GroovyTemplateCompiler.java b/framework/src/play/templates/GroovyTemplateCompiler.java index 62a0b33fcf..15513e01a9 100644 --- a/framework/src/play/templates/GroovyTemplateCompiler.java +++ b/framework/src/play/templates/GroovyTemplateCompiler.java @@ -1,10 +1,5 @@ package play.templates; -import groovy.lang.Closure; -import play.Play; -import play.exceptions.TemplateCompilationException; -import play.templates.GroovyInlineTags.CALL; - import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.ArrayList; @@ -15,29 +10,31 @@ import java.util.Map; import java.util.regex.Pattern; -/** - * The template compiler - */ +import groovy.lang.Closure; +import play.Logger; +import play.Play; +import play.exceptions.TemplateCompilationException; +import play.templates.GroovyInlineTags.CALL; + public class GroovyTemplateCompiler extends TemplateCompiler { - protected List extensionsClassnames = new ArrayList(); + protected List extensionsClassnames = new ArrayList<>(); // [#714] The groovy-compiler complaints if a line is more than 65535 unicode units long.. // Have to split it if it is really that big protected static final int maxPlainTextLength = 60000; - @Override public BaseTemplate compile(BaseTemplate template) { try { extensionsClassnames.clear(); - extensionsClassnames.addAll( Play.pluginCollection.addTemplateExtensions()); + extensionsClassnames.addAll(Play.pluginCollection.addTemplateExtensions()); List extensionsClasses = Play.classloader.getAssignableClasses(JavaExtensions.class); for (Class extensionsClass : extensionsClasses) { extensionsClassnames.add(extensionsClass.getName()); } } catch (Throwable e) { - // + Logger.error(e, "Failed to compile template %s", template.getName()); } return super.compile(template); } @@ -48,21 +45,30 @@ protected String source() { // If a plugin has something to change in the template before the compilation source = Play.pluginCollection.overrideTemplateSource(template, source); - - if(Boolean.parseBoolean(Play.configuration.getProperty("groovy.template.check.scala.comptatibility", "false"))){ - source = this.checkScalaComptability(source); + + if (Boolean.parseBoolean(Play.configuration.getProperty("groovy.template.check.scala.comptatibility", "false"))) { + source = this.checkScalaCompatibility(source); } - + return source; } - + + @Deprecated + protected String checkScalaComptability(String source) { + return checkScalaCompatibility(source); + } + /** * Makes the code scala compatible (for the scala module). + * + * @param source + * The string representation of the code + * @return The scala compatible source */ - protected String checkScalaComptability(String source){ + protected String checkScalaCompatibility(String source) { // Static access - List names = new ArrayList(); - Map originalNames = new HashMap(); + List names = new ArrayList<>(); + Map originalNames = new HashMap<>(); for (Class clazz : Play.classloader.getAllClasses()) { if (clazz.getName().endsWith("$")) { String name = clazz.getName().substring(0, clazz.getName().length() - 1).replace('$', '.') + '$'; @@ -75,7 +81,7 @@ protected String checkScalaComptability(String source){ } } Collections.sort(names, new Comparator() { - + @Override public int compare(String o1, String o2) { return o2.length() - o1.length(); } @@ -92,22 +98,22 @@ public int compare(String o1, String o2) { if (!names.isEmpty()) { - if (names.size() <= 1 || source.indexOf("new ") >= 0) { + if (names.size() <= 1 || source.contains("new ")) { for (String cName : names) { // dynamic class binding - source = source.replaceAll("new " + Pattern.quote(cName) + "(\\([^)]*\\))", "_('" - + originalNames.get(cName).replace("$", "\\$") + "').newInstance$1"); + source = source.replaceAll("new " + Pattern.quote(cName) + "(\\([^)]*\\))", + "_('" + originalNames.get(cName).replace("$", "\\$") + "').newInstance$1"); } } - if (names.size() <= 1 || source.indexOf("instanceof") >= 0) { + if (names.size() <= 1 || source.contains("instanceof")) { for (String cName : names) { // dynamic class binding - source = source.replaceAll("([a-zA-Z0-9.-_$]+)\\s+instanceof\\s+" + Pattern.quote(cName), "_('" - + originalNames.get(cName).replace("$", "\\$") + "').isAssignableFrom($1.class)"); + source = source.replaceAll("([a-zA-Z0-9.-_$]+)\\s+instanceof\\s+" + Pattern.quote(cName), + "_('" + originalNames.get(cName).replace("$", "\\$") + "').isAssignableFrom($1.class)"); } } - if (names.size() <= 1 || source.indexOf(".class") >= 0) { + if (names.size() <= 1 || source.contains(".class")) { for (String cName : names) { // dynamic class binding source = source.replaceAll("([^.])" + Pattern.quote(cName) + ".class", "$1_('" + originalNames.get(cName).replace("$", "\\$") + "')"); @@ -119,8 +125,8 @@ public int compare(String o1, String o2) { // quick indexOf-check for this one, // so we have to run all the replaceAll-calls for (String cName : names) { // dynamic class binding - source = source.replaceAll("([^'\".])" + Pattern.quote(cName) + "([.][^'\"])", "$1_('" - + originalNames.get(cName).replace("$", "\\$") + "')$2"); + source = source.replaceAll("([^'\".])" + Pattern.quote(cName) + "([.][^'\"])", + "$1_('" + originalNames.get(cName).replace("$", "\\$") + "')$2"); } @@ -131,10 +137,10 @@ public int compare(String o1, String o2) { @Override protected void head() { print("class "); - //This generated classname is parsed when creating cleanStackTrace. - //The part after "Template_" is used as key when - //looking up the file on disk this template-class is generated from. - //cleanStackTrace is looking in TemplateLoader.templates + // This generated classname is parsed when creating cleanStackTrace. + // The part after "Template_" is used as key when + // looking up the file on disk this template-class is generated from. + // cleanStackTrace is looking in TemplateLoader.templates String uniqueNumberForTemplateFile = TemplateLoader.getUniqueNumberForTemplateFile(template.name); @@ -157,12 +163,9 @@ protected void end() { println("}"); } - /** - * Interesting performance observation: - * Calling print(); from java (in ExecutableTemplate) called from groovy is MUCH slower than - * java returning string to groovy - * which then prints with out.print(); + * Interesting performance observation: Calling print(); from java (in ExecutableTemplate) called from groovy is + * MUCH slower than java returning string to groovy which then prints with out.print(); */ @Override @@ -177,34 +180,35 @@ protected void plain() { // [#714] The groovy-compiler complaints if a line is more than 65535 unicode units long.. // Have to split it if it is really that big - if (text.length() text.length()) { + int endPos = offset + maxPlainTextLength; + if (endPos > text.length()) { endPos = text.length(); } else { - // #869 If the last char (at endPos-1) is \, we're dealing with escaped char - must include the next one also.. - if ( text.charAt(endPos-1) == '\\') { + // #869 If the last char (at endPos-1) is \, we're dealing with escaped char - must include the next + // one also.. + if (text.charAt(endPos - 1) == '\\') { // use one more char so the escaping is not broken. Don't have to check length, since // all '\' is used in escaping, ref replaceAll above.. endPos++; } } - println("out.print(\""+text.substring(offset, endPos)+"\");"); - offset+= (endPos - offset); - }while(offset < text.length()); + println("out.print(\"" + text.substring(offset, endPos) + "\");"); + offset += (endPos - offset); + } while (offset < text.length()); } } @Override protected void script() { String text = parser.getToken(); - if (text.indexOf("\n") > -1) { + if (text.contains("\n")) { String[] lines = parser.getToken().split("\n"); for (int i = 0; i < lines.length; i++) { print(lines[i]); @@ -222,7 +226,7 @@ protected void script() { @Override protected void expr() { String expr = parser.getToken().trim(); - print(";out.print(__safeFaster("+expr+"))"); + print(";out.print(__safeFaster(" + expr + "))"); markLine(parser.getLine()); println(); } @@ -230,7 +234,7 @@ protected void expr() { @Override protected void message() { String expr = parser.getToken().trim(); - print(";out.print(__getMessage("+expr+"))"); + print(";out.print(__getMessage(" + expr + "))"); markLine(parser.getLine()); println(); } @@ -240,9 +244,9 @@ protected void action(boolean absolute) { String action = parser.getToken().trim(); if (action.trim().matches("^'.*'$")) { if (absolute) { - print("\tout.print(__reverseWithCheck_absolute_true("+action+"));"); + print("\tout.print(__reverseWithCheck_absolute_true(" + action + "));"); } else { - print("\tout.print(__reverseWithCheck_absolute_false("+action+"));"); + print("\tout.print(__reverseWithCheck_absolute_false(" + action + "));"); } } else { if (!action.endsWith(")")) { @@ -262,8 +266,8 @@ protected void action(boolean absolute) { protected void startTag() { tagIndex++; String tagText = parser.getToken().trim().replaceAll("\r", "").replaceAll("\n", " "); - String tagName = ""; - String tagArgs = ""; + String tagName; + String tagArgs; boolean hasBody = !parser.checkNext().endsWith("/"); if (tagText.indexOf(" ") > 0) { tagName = tagText.substring(0, tagText.indexOf(" ")); @@ -273,7 +277,7 @@ protected void startTag() { } // We only have to try to replace the following if we find at least one // @ in tagArgs.. - if (tagArgs.indexOf('@')>=0) { + if (tagArgs.indexOf('@') >= 0) { tagArgs = tagArgs.replaceAll("[:]\\s*[@]{2}", ":actionBridge._abs()."); tagArgs = tagArgs.replaceAll("(\\s)[@]{2}", "$1actionBridge._abs()."); tagArgs = tagArgs.replaceAll("[:]\\s*[@]{1}", ":actionBridge."); @@ -297,14 +301,16 @@ protected void startTag() { try { Method m = GroovyInlineTags.class.getDeclaredMethod("_" + tag.name, int.class, CALL.class); print("play.templates.TagContext.enterTag('" + tag.name + "');"); - print((String) m.invoke(null, new Object[]{tagIndex, CALL.START})); + print((String) m.invoke(null, tagIndex, CALL.START)); tag.hasBody = false; markLine(parser.getLine()); println(); skipLineBreak = true; return; + } catch (NoSuchMethodException e) { + // We find a tag that is not defined in GroovyInlineTags, lets see if it is defined somewhere else } catch (Exception e) { - // do nothing here + Logger.debug(e, "Failed to start tag %s in template %s", tag.name, template.getName()); } if (!tag.name.equals("doBody") && hasBody) { print("body" + tagIndex + " = {"); @@ -316,7 +322,6 @@ protected void startTag() { println(); } skipLineBreak = true; - } @Override @@ -339,7 +344,8 @@ protected void endTag() { print("attrs" + tagIndex + "['vars'].each() {"); print("if(toExecute.getProperty(it.key) == null) {toUnset.add(it.key);}; toExecute.setProperty(it.key, it.value);"); print("}};"); - print("if(attrs" + tagIndex + "['as']) { setProperty(attrs" + tagIndex + "['as'], toExecute.toString()); } else { out.print(toExecute.toString()); }; toUnset.each() {toExecute.setProperty(it, null)} };"); + print("if(attrs" + tagIndex + "['as']) { setProperty(attrs" + tagIndex + + "['as'], toExecute.toString()); } else { out.print(toExecute.toString()); }; toUnset.each() {toExecute.setProperty(it, null)} };"); markLine(tag.startLine); template.doBodyLines.add(currentLine); println(); @@ -351,17 +357,14 @@ protected void endTag() { // Use inlineTag if exists try { Method m = GroovyInlineTags.class.getDeclaredMethod("_" + tag.name, int.class, CALL.class); - println((String) m.invoke(null, new Object[]{tagIndex, CALL.END})); + println((String) m.invoke(null, tagIndex, CALL.END)); print("play.templates.TagContext.exitTag();"); } catch (Exception e) { // Use fastTag if exists - List fastClasses = new ArrayList(); - try { - fastClasses = Play.classloader.getAssignableClasses(FastTags.class); - } catch (Exception xe) { - // - } - fastClasses.add(0, FastTags.class); + List fastClasses = new ArrayList<>(); + fastClasses.add(FastTags.class); + fastClasses.addAll(Play.classloader.getAssignableClasses(FastTags.class)); + Method m = null; String tName = tag.name; String tSpace = ""; @@ -373,18 +376,22 @@ protected void endTag() { if (!c.isAnnotationPresent(FastTags.Namespace.class) && tSpace.length() > 0) { continue; } - if (c.isAnnotationPresent(FastTags.Namespace.class) && !c.getAnnotation(FastTags.Namespace.class).value().equals(tSpace)) { + if (c.isAnnotationPresent(FastTags.Namespace.class) + && !c.getAnnotation(FastTags.Namespace.class).value().equals(tSpace)) { continue; } try { - m = c.getDeclaredMethod("_" + tName, Map.class, Closure.class, PrintWriter.class, GroovyTemplate.ExecutableTemplate.class, int.class); + m = c.getDeclaredMethod("_" + tName, Map.class, Closure.class, PrintWriter.class, + GroovyTemplate.ExecutableTemplate.class, int.class); + break; } catch (NoSuchMethodException ex) { - continue; + // continue looking for this method in other *FastTags implementations } } if (m != null) { print("play.templates.TagContext.enterTag('" + tag.name + "');"); - print("_('" + m.getDeclaringClass().getName() + "')._" + tName + "(attrs" + tagIndex + ",body" + tagIndex + ", out, this, " + tag.startLine + ");"); + print("_('" + m.getDeclaringClass().getName() + "')._" + tName + "(attrs" + tagIndex + ",body" + tagIndex + + ", out, this, " + tag.startLine + ");"); print("play.templates.TagContext.exitTag();"); } else { print("invokeTag(" + tag.startLine + ",'" + tagName + "',attrs" + tagIndex + ",body" + tagIndex + ");"); @@ -397,5 +404,3 @@ protected void endTag() { skipLineBreak = true; } } - - diff --git a/framework/src/play/templates/JavaExtensions.java b/framework/src/play/templates/JavaExtensions.java index f056a9e9a9..138afbba21 100644 --- a/framework/src/play/templates/JavaExtensions.java +++ b/framework/src/play/templates/JavaExtensions.java @@ -1,27 +1,31 @@ package play.templates; -import groovy.lang.Closure; -import groovy.util.XmlSlurper; -import groovy.util.slurpersupport.GPathResult; - import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.text.*; -import java.util.Currency; -import java.util.Date; -import java.util.Locale; -import java.util.Arrays; -import java.util.List; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.Normalizer; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Currency; +import java.util.Date; import java.util.Iterator; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TimeZone; import org.apache.commons.lang.StringEscapeUtils; +import groovy.lang.Closure; +import groovy.util.XmlSlurper; +import groovy.util.slurpersupport.GPathResult; import play.Logger; import play.i18n.Lang; import play.i18n.Messages; @@ -52,7 +56,7 @@ public static GPathResult asXml(String xml) { try { return (new XmlSlurper()).parseText(xml); } catch (Exception e) { - throw new RuntimeException("invalid XML"); + throw new RuntimeException("invalid XML", e); } } @@ -64,7 +68,7 @@ public static String[] add(String[] array, String o) { } public static String[] remove(String[] array, String s) { - List temp = new ArrayList(Arrays.asList(array)); + List temp = new ArrayList<>(Arrays.asList(array)); temp.remove(s); return temp.toArray(new String[temp.size()]); } @@ -80,7 +84,7 @@ public static String toString(Closure closure) { public static String capitalizeWords(String source) { char prevc = ' '; // first char of source is capitalized - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < source.length(); i++) { char c = source.charAt(i); if (c != ' ' && prevc == ' ') { @@ -128,9 +132,9 @@ public static RawData asAttr(Map attributes, Object condition) { } public static RawData asAttr(Map attributes) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); for (Object key : attributes.keySet()) { - buf.append(key + "=\"" + attributes.get(key) + "\" "); + buf.append(key).append("=\"").append(attributes.get(key)).append("\" "); } return new RawData(buf); } @@ -222,7 +226,7 @@ public static String since(Date date, Boolean stopAtMonth) { public static String asdate(Long timestamp) { return asdate(timestamp, I18N.getDateFormat()); } - + public static String asdate(Long timestamp, String pattern) { return asdate(timestamp, pattern, Lang.get()); } @@ -358,7 +362,17 @@ public static String pluralize(Collection n, String[] forms) { } public static String noAccents(String string) { - return Normalizer.normalize(string, Normalizer.Form.NFKC).replaceAll("[àáâãäåāąă]", "a").replaceAll("[çćčĉċ]", "c").replaceAll("[ďđð]", "d").replaceAll("[èéêëēęěĕė]", "e").replaceAll("[ƒſ]", "f").replaceAll("[ĝğġģ]", "g").replaceAll("[ĥħ]", "h").replaceAll("[ìíîïīĩĭįı]", "i").replaceAll("[ijĵ]", "j").replaceAll("[ķĸ]", "k").replaceAll("[łľĺļŀ]", "l").replaceAll("[ñńňņʼnŋ]", "n").replaceAll("[òóôõöøōőŏœ]", "o").replaceAll("[Þþ]", "p").replaceAll("[ŕřŗ]", "r").replaceAll("[śšşŝș]", "s").replaceAll("[ťţŧț]", "t").replaceAll("[ùúûüūůűŭũų]", "u").replaceAll("[ŵ]", "w").replaceAll("[ýÿŷ]", "y").replaceAll("[žżź]", "z").replaceAll("[æ]", "ae").replaceAll("[ÀÁÂÃÄÅĀĄĂ]", "A").replaceAll("[ÇĆČĈĊ]", "C").replaceAll("[ĎĐÐ]", "D").replaceAll("[ÈÉÊËĒĘĚĔĖ]", "E").replaceAll("[ĜĞĠĢ]", "G").replaceAll("[ĤĦ]", "H").replaceAll("[ÌÍÎÏĪĨĬĮİ]", "I").replaceAll("[Ĵ]", "J").replaceAll("[Ķ]", "K").replaceAll("[ŁĽĹĻĿ]", "L").replaceAll("[ÑŃŇŅŊ]", "N").replaceAll("[ÒÓÔÕÖØŌŐŎ]", "O").replaceAll("[ŔŘŖ]", "R").replaceAll("[ŚŠŞŜȘ]", "S").replaceAll("[ÙÚÛÜŪŮŰŬŨŲ]", "U").replaceAll("[Ŵ]", "W").replaceAll("[ÝŶŸ]", "Y").replaceAll("[ŹŽŻ]", "Z").replaceAll("[ß]", "ss"); + return Normalizer.normalize(string, Normalizer.Form.NFKC).replaceAll("[àáâãäåāąă]", "a").replaceAll("[çćčĉċ]", "c") + .replaceAll("[ďđð]", "d").replaceAll("[èéêëēęěĕė]", "e").replaceAll("[ƒſ]", "f").replaceAll("[ĝğġģ]", "g") + .replaceAll("[ĥħ]", "h").replaceAll("[ìíîïīĩĭįı]", "i").replaceAll("[ijĵ]", "j").replaceAll("[ķĸ]", "k") + .replaceAll("[łľĺļŀ]", "l").replaceAll("[ñńňņʼnŋ]", "n").replaceAll("[òóôõöøōőŏœ]", "o").replaceAll("[Þþ]", "p") + .replaceAll("[ŕřŗ]", "r").replaceAll("[śšşŝș]", "s").replaceAll("[ťţŧț]", "t").replaceAll("[ùúûüūůűŭũų]", "u") + .replaceAll("[ŵ]", "w").replaceAll("[ýÿŷ]", "y").replaceAll("[žżź]", "z").replaceAll("[æ]", "ae") + .replaceAll("[ÀÁÂÃÄÅĀĄĂ]", "A").replaceAll("[ÇĆČĈĊ]", "C").replaceAll("[ĎĐÐ]", "D").replaceAll("[ÈÉÊËĒĘĚĔĖ]", "E") + .replaceAll("[ĜĞĠĢ]", "G").replaceAll("[ĤĦ]", "H").replaceAll("[ÌÍÎÏĪĨĬĮİ]", "I").replaceAll("[Ĵ]", "J") + .replaceAll("[Ķ]", "K").replaceAll("[ŁĽĹĻĿ]", "L").replaceAll("[ÑŃŇŅŊ]", "N").replaceAll("[ÒÓÔÕÖØŌŐŎ]", "O") + .replaceAll("[ŔŘŖ]", "R").replaceAll("[ŚŠŞŜȘ]", "S").replaceAll("[ÙÚÛÜŪŮŰŬŨŲ]", "U").replaceAll("[Ŵ]", "W") + .replaceAll("[ÝŶŸ]", "Y").replaceAll("[ŹŽŻ]", "Z").replaceAll("[ß]", "ss"); } public static String slugify(String string) { @@ -396,20 +410,30 @@ public static String yesno(Object o, String[] values) { /** * return the last item of a list or null if the List is null + * + * @param items + * List of items + * @return the last item of a list or null if the List is null */ public static Object last(List items) { return (items == null) ? null : items.get(items.size() - 1); } /** - * concatenate items of a collection as a string separated with separator - * items toString() method should be implemented to provide a string representation + * Concatenate items of a collection as a string separated with separator items toString() method should be + * implemented to provide a string representation + * + * @param items + * List of items + * @param separator + * The separator to used + * @return The concatenate items of a collection as a string */ public static String join(Collection items, String separator) { if (items == null) { return ""; } - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); Iterator ite = items.iterator(); int i = 0; while (ite.hasNext()) { diff --git a/framework/src/play/templates/SafeFormatter.java b/framework/src/play/templates/SafeFormatter.java index 4b6ad7cc0e..e4cca9a800 100644 --- a/framework/src/play/templates/SafeFormatter.java +++ b/framework/src/play/templates/SafeFormatter.java @@ -2,8 +2,7 @@ /** * Supported type for formatting. This interface is used to implement custom formatters for templates. - * */ public interface SafeFormatter { - String format(Template template, Object value); + String format(Template template, Object value); } diff --git a/framework/src/play/templates/TagContext.java b/framework/src/play/templates/TagContext.java index 0349268f08..4b2177aaf4 100644 --- a/framework/src/play/templates/TagContext.java +++ b/framework/src/play/templates/TagContext.java @@ -9,10 +9,10 @@ */ public class TagContext { - static ThreadLocal> currentStack = new ThreadLocal>(); + private static final ThreadLocal> currentStack = new ThreadLocal<>(); public String tagName; - public Map data = new HashMap(); + public Map data = new HashMap<>(); public TagContext(String tagName) { this.tagName = tagName; @@ -61,12 +61,12 @@ public static TagContext parent(String name) { } public static TagContext parent(String... names) { - for(int i=currentStack.get().size()-2; i>=0; i--) { - for(String name : names) { - if(name.equals(currentStack.get().get(i).tagName)) { - return currentStack.get().get(i); - } - } + for (int i = currentStack.get().size() - 2; i >= 0; i--) { + for (String name : names) { + if (name.equals(currentStack.get().get(i).tagName)) { + return currentStack.get().get(i); + } + } } return null; } diff --git a/framework/src/play/templates/Template.java b/framework/src/play/templates/Template.java index 24b816bce5..7a9eb09cde 100644 --- a/framework/src/play/templates/Template.java +++ b/framework/src/play/templates/Template.java @@ -12,28 +12,32 @@ public abstract class Template { /** * Starts the rendering process without modifying the args-map - * @param args map containing data binding info + * + * @param args + * map containing data binding info * @return the result of the complete rendering */ - public String render( Map args ) { + public String render(Map args) { // starts the internal recursive rendering process with // a copy of the passed args-map. This is done - // to prevent us from polution the users map with + // to prevent us from pollution the users map with // template-rendering-specific internal data // - // Since the original args is not poluted it can be used as input + // Since the original args is not polluted it can be used as input // to another rendering operation later - return internalRender( new HashMap(args) ); + return internalRender(new HashMap<>(args)); } - /** - * The internal rendering method - When one templated calls another template, - * this method is used. The input args-map is constantly being modified, as different - * templates "communicate" with each other by storing info in the map + * The internal rendering method - When one template calls another template, this method is used. The input args-map + * is constantly being modified, as different templates "communicate" with each other by storing info in the map + * + * @param args + * List of arguments use in render + * @return The template result as string */ protected abstract String internalRender(Map args); - + public String render() { return internalRender(new HashMap()); } diff --git a/framework/src/play/templates/TemplateCompiler.java b/framework/src/play/templates/TemplateCompiler.java index 804f4bc540..c8e6d7d2cd 100644 --- a/framework/src/play/templates/TemplateCompiler.java +++ b/framework/src/play/templates/TemplateCompiler.java @@ -33,7 +33,7 @@ public BaseTemplate compile(VirtualFile file) { protected TemplateParser parser; protected boolean doNextScan = true; protected TemplateParser.Token state; - protected Stack tagsStack = new Stack(); + protected Stack tagsStack = new Stack<>(); protected int tagIndex; protected boolean skipLineBreak; protected int currentLine = 1; diff --git a/framework/src/play/templates/TemplateLoader.java b/framework/src/play/templates/TemplateLoader.java index 32aae28ee2..ab537d7fbd 100644 --- a/framework/src/play/templates/TemplateLoader.java +++ b/framework/src/play/templates/TemplateLoader.java @@ -1,50 +1,49 @@ package play.templates; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import java.util.Collections; import play.Logger; import play.Play; -import play.vfs.VirtualFile; import play.exceptions.TemplateCompilationException; import play.exceptions.TemplateNotFoundException; +import play.vfs.VirtualFile; -/** - * Load templates - */ public class TemplateLoader { - protected static Map templates = new HashMap(); + protected static Map templates = new HashMap<>(); /** * See getUniqueNumberForTemplateFile() for more info */ - private static AtomicLong nextUniqueNumber = new AtomicLong(1000);//we start on 1000 + private static AtomicLong nextUniqueNumber = new AtomicLong(1000);// we start on 1000 private static Map templateFile2UniqueNumber = Collections.synchronizedMap(new HashMap()); /** - * All loaded templates is cached in the templates-list using a key. - * This key is included as part of the classname for the generated class for a specific template. - * The key is included in the classname to make it possible to resolve the original template-file - * from the classname, when creating cleanStackTrace + * All loaded templates is cached in the templates-list using a key. This key is included as part of the classname + * for the generated class for a specific template. The key is included in the classname to make it possible to + * resolve the original template-file from the classname, when creating cleanStackTrace * * This method returns a unique representation of the path which is usable as part of a classname * * @param path + * Path of the template file * @return a unique representation of the path which is usable as part of a classname */ public static String getUniqueNumberForTemplateFile(String path) { - //a path cannot be a valid classname so we have to convert it somehow. - //If we did some encoding on the path, the result would be at least as long as the path. - //Therefor we assign a unique number to each path the first time we see it, and store it.. - //This way, all seen paths gets a unique number. This number is our UniqueValidClassnamePart.. + // a path cannot be a valid classname so we have to convert it somehow. + // If we did some encoding on the path, the result would be at least as long as the path. + // Therefor we assign a unique number to each path the first time we see it, and store it.. + // This way, all seen paths gets a unique number. This number is our UniqueValidClassnamePart.. String uniqueNumber = templateFile2UniqueNumber.get(path); if (uniqueNumber == null) { - //this is the first time we see this path - must assign a unique number to it. + // this is the first time we see this path - must assign a unique number to it. uniqueNumber = Long.toString(nextUniqueNumber.getAndIncrement()); templateFile2UniqueNumber.put(path, uniqueNumber); } @@ -53,7 +52,9 @@ public static String getUniqueNumberForTemplateFile(String path) { /** * Load a template from a virtual file - * @param file A VirtualFile + * + * @param file + * A VirtualFile * @return The executable template */ public static Template load(VirtualFile file) { @@ -64,17 +65,18 @@ public static Template load(VirtualFile file) { } // Use default engine - final String fileRelativePath = file.relativePath(); - final String key = getUniqueNumberForTemplateFile(fileRelativePath); + String fileRelativePath = file.relativePath(); + String key = getUniqueNumberForTemplateFile(fileRelativePath); if (!templates.containsKey(key) || templates.get(key).compiledTemplate == null) { if (Play.usePrecompiled) { - BaseTemplate template = new GroovyTemplate(fileRelativePath.replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent"), ""); + BaseTemplate template = new GroovyTemplate( + fileRelativePath.replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent"), ""); try { template.loadPrecompiled(); templates.put(key, template); return template; - } catch(Exception e) { - Logger.warn("Precompiled template %s not found, trying to load it dynamically...", file.relativePath()); + } catch (Exception e) { + Logger.warn(e, "Precompiled template %s not found, trying to load it dynamically...", file.relativePath()); } } BaseTemplate template = new GroovyTemplate(fileRelativePath, file.contentAsString()); @@ -97,8 +99,11 @@ public static Template load(VirtualFile file) { /** * Load a template from a String - * @param key A unique identifier for the template, used for retreiving a cached template - * @param source The template source + * + * @param key + * A unique identifier for the template, used for retrieving a cached template + * @param source + * The template source * @return A Template */ public static BaseTemplate load(String key, String source) { @@ -122,10 +127,14 @@ public static BaseTemplate load(String key, String source) { } /** - * Clean the cache for that key - * Then load a template from a String - * @param key A unique identifier for the template, used for retreiving a cached template - * @param source The template source + * Clean the cache for that key Then load a template from a String + * + * @param key + * A unique identifier for the template, used for retrieving a cached template + * @param source + * The template source + * @param reload + * : Indicate if we must clean the cache * @return A Template */ public static BaseTemplate load(String key, String source, boolean reload) { @@ -135,7 +144,9 @@ public static BaseTemplate load(String key, String source, boolean reload) { /** * Load template from a String, but don't cache it - * @param source The template source + * + * @param source + * The template source * @return A Template */ public static BaseTemplate loadString(String source) { @@ -152,7 +163,9 @@ public static void cleanCompiledCache() { /** * Cleans the specified key from the cache - * @param key The template key + * + * @param key + * The template key */ public static void cleanCompiledCache(String key) { templates.remove(key); @@ -160,7 +173,9 @@ public static void cleanCompiledCache(String key) { /** * Load a template - * @param path The path of the template (ex: Application/index.html) + * + * @param path + * The path of the template (ex: Application/index.html) * @return The executable template */ public static Template load(String path) { @@ -181,16 +196,12 @@ public static Template load(String path) { } } /* - if (template == null) { - //When using the old 'key = (file.relativePath().hashCode() + "").replace("-", "M");', - //the next line never return anything, since all values written to templates is using the - //above key. - //when using just file.relativePath() as key, the next line start returning stuff.. - //therefor I have commented it out. - template = templates.get(path); - } + * if (template == null) { //When using the old 'key = (file.relativePath().hashCode() + "").replace("-", + * "M");', //the next line never return anything, since all values written to templates is using the //above + * key. //when using just file.relativePath() as key, the next line start returning stuff.. //therefor I have + * commented it out. template = templates.get(path); } */ - //TODO: remove ? + // TODO: remove ? if (template == null) { VirtualFile tf = Play.getVirtualFile(path); if (tf != null && tf.exists()) { @@ -204,10 +215,11 @@ public static Template load(String path) { /** * List all found templates + * * @return A list of executable templates */ public static List