Merge lp:~nicolas-lunatech/play/play-ssl into lp:play

Proposed by Nicolas Leroux
Status: Needs review
Proposed branch: lp:~nicolas-lunatech/play/play-ssl
Merge into: lp:play
Diff against target: 57126 lines (+15027/-21108)
416 files modified
.bzrignore (+30/-1)
documentation/commands/cmd-all.txt (+0/-34)
documentation/commands/cmd-install.txt (+0/-17)
documentation/manual/cobertura.textile (+0/-46)
documentation/manual/controllers.textile (+34/-2)
documentation/manual/emails.textile (+32/-6)
documentation/manual/faq.textile (+1/-2)
documentation/manual/home.textile (+27/-36)
documentation/manual/i18n.textile (+13/-0)
documentation/manual/lambdaj.textile (+129/-0)
documentation/manual/scala.textile (+208/-0)
documentation/manual/scguide1.textile (+216/-0)
documentation/manual/scguide2.textile (+409/-0)
documentation/manual/scguide3.textile (+329/-0)
documentation/manual/scguide4.textile (+245/-0)
documentation/manual/scguide5.textile (+137/-0)
documentation/manual/search.textile (+0/-78)
documentation/manual/tags.textile (+77/-0)
documentation/manual/templates.textile (+32/-1)
framework/build.xml (+26/-17)
framework/patches/hibernate-3.5.x.Final.patch (+114/-0)
framework/patches/hibernate-3.5.x.Final.patch.README (+1/-0)
framework/pym/play/application.py (+262/-0)
framework/pym/play/cmdloader.py (+40/-0)
framework/pym/play/commands/base.py (+273/-0)
framework/pym/play/commands/check.py (+55/-0)
framework/pym/play/commands/classpath.py (+16/-0)
framework/pym/play/commands/daemon.py (+147/-0)
framework/pym/play/commands/eclipse.py (+115/-0)
framework/pym/play/commands/help.py (+55/-0)
framework/pym/play/commands/intellij.py (+40/-0)
framework/pym/play/commands/javadoc.py (+43/-0)
framework/pym/play/commands/modulesrepo.py (+447/-0)
framework/pym/play/commands/netbeans.py (+50/-0)
framework/pym/play/commands/precompile.py (+31/-0)
framework/pym/play/commands/secret.py (+17/-0)
framework/pym/play/commands/status.py (+68/-0)
framework/pym/play/commands/war.py (+46/-0)
framework/pym/play/launchpad.py (+24/-0)
framework/pym/play/utils.py (+186/-0)
framework/src/ehcache.xml (+1/-1)
framework/src/play/CorePlugin.java (+17/-19)
framework/src/play/Invoker.java (+45/-29)
framework/src/play/Logger.java (+23/-13)
framework/src/play/Play.java (+44/-21)
framework/src/play/PlayConfiguration.java (+12/-0)
framework/src/play/PlayPlugin.java (+53/-6)
framework/src/play/cache/Cache.java (+9/-9)
framework/src/play/cache/CacheFor.java (+19/-0)
framework/src/play/classloading/ApplicationClasses.java (+59/-26)
framework/src/play/classloading/ApplicationClassloader.java (+70/-27)
framework/src/play/classloading/ApplicationCompiler.java (+25/-23)
framework/src/play/classloading/BytecodeCache.java (+5/-5)
framework/src/play/classloading/enhancers/ControllersEnhancer.java (+110/-47)
framework/src/play/classloading/enhancers/Enhancer.java (+4/-4)
framework/src/play/classloading/enhancers/JPAEnhancer.java (+14/-14)
framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java (+127/-94)
framework/src/play/classloading/enhancers/PropertiesEnhancer.java (+14/-9)
framework/src/play/classloading/enhancers/SigEnhancer.java (+13/-0)
framework/src/play/data/FileUpload.java (+0/-85)
framework/src/play/data/MemoryUpload.java (+0/-55)
framework/src/play/data/Upload.java (+9/-12)
framework/src/play/data/binding/AnnotationHelper.java (+54/-0)
framework/src/play/data/binding/As.java (+25/-0)
framework/src/play/data/binding/BeanWrapper.java (+0/-167)
framework/src/play/data/binding/Binder.java (+188/-81)
framework/src/play/data/binding/CalendarBinder.java (+0/-24)
framework/src/play/data/binding/DateBinder.java (+0/-66)
framework/src/play/data/binding/FileBinder.java (+0/-26)
framework/src/play/data/binding/Global.java (+13/-0)
framework/src/play/data/binding/LocaleBinder.java (+0/-23)
framework/src/play/data/binding/NoBinding.java (+16/-0)
framework/src/play/data/binding/SupportedType.java (+0/-10)
framework/src/play/data/binding/TypeBinder.java (+12/-0)
framework/src/play/data/binding/Unbinder.java (+0/-74)
framework/src/play/data/binding/UploadBinder.java (+0/-21)
framework/src/play/data/binding/types/BinaryBinder.java (+34/-0)
framework/src/play/data/binding/types/CalendarBinder.java (+35/-0)
framework/src/play/data/binding/types/DateBinder.java (+43/-0)
framework/src/play/data/binding/types/FileBinder.java (+29/-0)
framework/src/play/data/binding/types/LocaleBinder.java (+25/-0)
framework/src/play/data/binding/types/UploadBinder.java (+35/-0)
framework/src/play/data/parsing/ApacheMultipartParser.java (+0/-1376)
framework/src/play/data/parsing/DataParser.java (+1/-4)
framework/src/play/data/parsing/JsonParser.java (+0/-100)
framework/src/play/data/parsing/MultipartStream.java (+0/-978)
framework/src/play/data/parsing/TempFilePlugin.java (+0/-57)
framework/src/play/data/parsing/UrlEncodedParser.java (+0/-104)
framework/src/play/data/validation/Email.java (+1/-0)
framework/src/play/data/validation/EqualsCheck.java (+1/-1)
framework/src/play/data/validation/Error.java (+2/-2)
framework/src/play/data/validation/InFutureCheck.java (+1/-1)
framework/src/play/data/validation/InPastCheck.java (+1/-1)
framework/src/play/data/validation/RequiredCheck.java (+3/-3)
framework/src/play/data/validation/Validation.java (+8/-9)
framework/src/play/data/validation/ValidationPlugin.java (+12/-1)
framework/src/play/db/DBPlugin.java (+74/-51)
framework/src/play/db/Model.java (+75/-0)
framework/src/play/db/helper/JdbcResultFactories.java (+11/-4)
framework/src/play/db/helper/SqlQuery.java (+18/-0)
framework/src/play/db/helper/SqlSelect.java (+2/-2)
framework/src/play/db/helper/SqlUnion.java (+8/-0)
framework/src/play/db/jpa/Blob.java (+146/-0)
framework/src/play/db/jpa/FileAttachment.java (+0/-79)
framework/src/play/db/jpa/GenericModel.java (+428/-0)
framework/src/play/db/jpa/JPA.java (+5/-16)
framework/src/play/db/jpa/JPABase.java (+268/-0)
framework/src/play/db/jpa/JPAModel.java (+0/-10)
framework/src/play/db/jpa/JPAPlugin.java (+311/-40)
framework/src/play/db/jpa/JPASupport.java (+3/-763)
framework/src/play/db/jpa/JPQL.java (+205/-0)
framework/src/play/db/jpa/JPQLDialect.java (+0/-124)
framework/src/play/db/jpa/Model.java (+1/-1)
framework/src/play/db/jpa/StringList.java (+0/-89)
framework/src/play/exceptions/CompilationException.java (+59/-0)
framework/src/play/exceptions/JPAException.java (+1/-2)
framework/src/play/exceptions/JavaCompilationException.java (+0/-40)
framework/src/play/exceptions/JavaExecutionException.java (+1/-1)
framework/src/play/i18n/Lang.java (+25/-0)
framework/src/play/i18n/Messages.java (+11/-9)
framework/src/play/i18n/MessagesPlugin.java (+4/-9)
framework/src/play/inject/Injector.java (+2/-2)
framework/src/play/jobs/JobsPlugin.java (+14/-8)
framework/src/play/libs/Codec.java (+1/-1)
framework/src/play/libs/Crypto.java (+3/-3)
framework/src/play/libs/Expression.java (+40/-0)
framework/src/play/libs/I18N.java (+19/-1)
framework/src/play/libs/IO.java (+137/-72)
framework/src/play/libs/Images.java (+9/-9)
framework/src/play/libs/Mail.java (+85/-523)
framework/src/play/libs/MimeTypes.java (+32/-16)
framework/src/play/libs/OpenID.java (+3/-2)
framework/src/play/libs/Time.java (+3/-3)
framework/src/play/libs/WS.java (+209/-693)
framework/src/play/libs/XPath.java (+60/-9)
framework/src/play/libs/mime-types.properties (+6/-1)
framework/src/play/mvc/ActionInvoker.java (+176/-51)
framework/src/play/mvc/After.java (+2/-1)
framework/src/play/mvc/Before.java (+3/-2)
framework/src/play/mvc/Catch.java (+0/-1)
framework/src/play/mvc/Controller.java (+155/-39)
framework/src/play/mvc/Finally.java (+2/-1)
framework/src/play/mvc/Http.java (+105/-12)
framework/src/play/mvc/Mailer.java (+200/-155)
framework/src/play/mvc/Router.java (+200/-39)
framework/src/play/mvc/Scope.java (+85/-23)
framework/src/play/mvc/Util.java (+15/-0)
framework/src/play/mvc/results/BadRequest.java (+2/-1)
framework/src/play/mvc/results/Error.java (+1/-1)
framework/src/play/mvc/results/Forbidden.java (+1/-1)
framework/src/play/mvc/results/NotFound.java (+1/-1)
framework/src/play/mvc/results/NotModified.java (+2/-1)
framework/src/play/mvc/results/Ok.java (+2/-1)
framework/src/play/mvc/results/Redirect.java (+6/-5)
framework/src/play/mvc/results/RedirectToStatic.java (+2/-1)
framework/src/play/mvc/results/RenderBinary.java (+26/-6)
framework/src/play/mvc/results/RenderHtml.java (+27/-0)
framework/src/play/mvc/results/RenderStatic.java (+2/-0)
framework/src/play/mvc/results/RenderTemplate.java (+14/-7)
framework/src/play/mvc/results/Result.java (+1/-3)
framework/src/play/mvc/results/Status.java (+18/-0)
framework/src/play/mvc/results/Unauthorized.java (+2/-1)
framework/src/play/server/FileChannelBuffer.java (+301/-0)
framework/src/play/server/HttpHandler.java (+0/-595)
framework/src/play/server/HttpServerPipelineFactory.java (+31/-0)
framework/src/play/server/PlayHandler.java (+838/-0)
framework/src/play/server/Server.java (+33/-44)
framework/src/play/server/ServletWrapper.java (+2/-3)
framework/src/play/server/StreamChunkAggregator.java (+85/-0)
framework/src/play/server/ssl/SslHttpServerContextFactory.java (+121/-0)
framework/src/play/server/ssl/SslHttpServerPipelineFactory.java (+45/-0)
framework/src/play/templates/BaseTemplate.java (+116/-0)
framework/src/play/templates/FastTags.java (+26/-34)
framework/src/play/templates/GroovyInlineTags.java (+93/-0)
framework/src/play/templates/GroovyTemplate.java (+442/-0)
framework/src/play/templates/GroovyTemplateCompiler.java (+328/-0)
framework/src/play/templates/InlineTags.java (+0/-93)
framework/src/play/templates/JavaExtensions.java (+247/-363)
framework/src/play/templates/Template.java (+5/-514)
framework/src/play/templates/TemplateCompiler.java (+141/-587)
framework/src/play/templates/TemplateLoader.java (+77/-30)
framework/src/play/templates/TemplateParser.java (+158/-0)
framework/src/play/test/Fixtures.java (+109/-106)
framework/src/play/test/FunctionalTest.java (+65/-77)
framework/src/play/test/TestEngine.java (+24/-7)
framework/src/play/utils/Default.java (+16/-0)
framework/src/play/utils/HTML.java (+1/-1)
framework/src/play/utils/Java.java (+62/-23)
framework/src/play/utils/NoOpEntityResolver.java (+16/-0)
framework/src/play/utils/Properties.java (+1/-1)
framework/src/play/utils/Utils.java (+86/-0)
framework/src/play/utils/YesSSLSocketFactory.java (+1/-1)
framework/src/play/vfs/VirtualFile.java (+30/-0)
framework/templates/tags/field.tag (+1/-1)
framework/templates/tags/i18n.tag (+11/-13)
framework/templates/tags/script.tag (+1/-1)
framework/templates/tags/select.tag (+30/-0)
framework/templates/tags/selenium.html (+3/-3)
modules/console/app/controllers/DbConsole.java (+27/-0)
modules/console/app/controllers/InfoConsole.java (+19/-0)
modules/console/app/controllers/JavaConsole.java (+38/-0)
modules/console/app/controllers/ScalaConsole.scala (+56/-0)
modules/console/app/utils/InterpreterHelper.java (+53/-0)
modules/console/app/utils/ResultSetMapper.java (+66/-0)
modules/console/app/views/console/db.html (+54/-0)
modules/console/app/views/console/index.html (+82/-0)
modules/console/app/views/console/repl.html (+69/-0)
modules/console/app/views/tags/navigation.html (+7/-0)
modules/console/conf/application.conf (+3/-0)
modules/console/conf/messages (+3/-0)
modules/console/conf/routes (+17/-0)
modules/console/documentation/home.textile (+40/-0)
modules/console/public/javascripts/jquery-1.4.2.min.js (+154/-0)
modules/console/public/stylesheets/console_main.css (+96/-0)
modules/console/public/stylesheets/foo.html (+1/-0)
modules/crud/app/controllers/CRUD.java (+137/-272)
modules/crud/app/views/CRUD/show.html (+2/-2)
modules/crud/app/views/tags/crud/checkField.html (+9/-9)
modules/crud/app/views/tags/crud/dateField.html (+8/-8)
modules/crud/app/views/tags/crud/enumField.html (+8/-8)
modules/crud/app/views/tags/crud/fileField.html (+11/-11)
modules/crud/app/views/tags/crud/form.html (+97/-105)
modules/crud/app/views/tags/crud/longtextField.html (+8/-8)
modules/crud/app/views/tags/crud/navigation.html (+11/-11)
modules/crud/app/views/tags/crud/numberField.html (+8/-8)
modules/crud/app/views/tags/crud/pagination.html (+1/-1)
modules/crud/app/views/tags/crud/passwordField.html (+8/-8)
modules/crud/app/views/tags/crud/relationField.html (+50/-48)
modules/crud/app/views/tags/crud/search.html (+8/-8)
modules/crud/app/views/tags/crud/serializedTextField.html (+8/-8)
modules/crud/app/views/tags/crud/table.html (+67/-67)
modules/crud/app/views/tags/crud/textField.html (+8/-8)
modules/crud/app/views/tags/crud/types.tag (+13/-7)
modules/crud/commands.py (+44/-33)
modules/crud/conf/messages (+1/-1)
modules/crud/conf/routes (+10/-10)
modules/docviewer/app/DocViewerPlugin.java (+17/-6)
modules/docviewer/app/controllers/PlayDocumentation.java (+6/-2)
modules/docviewer/app/views/PlayDocumentation/page.html (+6/-1)
modules/secure/commands.py (+45/-30)
modules/testrunner/app/controllers/TestRunner.java (+21/-1)
modules/testrunner/app/views/TestRunner/index.html (+6/-6)
modules/testrunner/app/views/TestRunner/results.html (+1/-0)
modules/testrunner/app/views/TestRunner/selenium-results.html (+1/-0)
modules/testrunner/build.xml (+3/-0)
modules/testrunner/public/test-runner/selenium/TestRunner.html (+1/-1)
modules/testrunner/public/test-runner/selenium/scripts/selenium-api.js (+2/-0)
modules/testrunner/public/test-runner/selenium/scripts/selenium-browserbot.js (+3/-2)
modules/testrunner/public/test-runner/selenium/scripts/user-extensions.js (+8/-0)
modules/testrunner/src/play/modules/testrunner/FirePhoque.java (+118/-0)
modules/testrunner/src/play/modules/testrunner/TestRunnerPlugin.java (+1/-0)
nbproject/project.xml (+7/-4)
play (+113/-1513)
repositories (+13/-0)
resources/application-skel/conf/application.conf (+20/-9)
resources/module-skel/build.xml (+0/-45)
resources/module-skel/commands.py (+0/-15)
resources/module-skel/conf/messages (+0/-5)
resources/module-skel/conf/routes (+0/-8)
samples-and-tests/chat/app/views/Chat/index.html (+13/-7)
samples-and-tests/forum/app/models/Post.java (+4/-0)
samples-and-tests/forum/app/views/tags/showPost.html (+1/-1)
samples-and-tests/forum/conf/application.conf (+2/-1)
samples-and-tests/forum/conf/host.cert (+22/-0)
samples-and-tests/forum/conf/host.key (+18/-0)
samples-and-tests/i-am-a-developer/tests.py (+4/-4)
samples-and-tests/jobboard/app/controllers/Administration.java (+0/-54)
samples-and-tests/jobboard/app/controllers/Application.java (+0/-46)
samples-and-tests/jobboard/app/controllers/Categories.java (+0/-17)
samples-and-tests/jobboard/app/controllers/Companies.java (+0/-25)
samples-and-tests/jobboard/app/controllers/Jobs.java (+0/-41)
samples-and-tests/jobboard/app/controllers/Tags.java (+0/-14)
samples-and-tests/jobboard/app/models/Category.java (+0/-21)
samples-and-tests/jobboard/app/models/Company.java (+0/-32)
samples-and-tests/jobboard/app/models/Job.java (+0/-88)
samples-and-tests/jobboard/app/models/Tag.java (+0/-34)
samples-and-tests/jobboard/app/views/Administration/login.html (+0/-24)
samples-and-tests/jobboard/app/views/Application/index.html (+0/-51)
samples-and-tests/jobboard/app/views/Application/index.xml (+0/-22)
samples-and-tests/jobboard/app/views/Application/jobdetails.html (+0/-38)
samples-and-tests/jobboard/app/views/Application/joblist.html (+0/-20)
samples-and-tests/jobboard/app/views/Application/main.html (+0/-14)
samples-and-tests/jobboard/app/views/Application/search.html (+0/-25)
samples-and-tests/jobboard/app/views/CRUD/layout.html (+0/-36)
samples-and-tests/jobboard/app/views/Companies/list.html (+0/-23)
samples-and-tests/jobboard/app/views/Jobs/blank.html (+0/-19)
samples-and-tests/jobboard/app/views/Jobs/list.html (+0/-38)
samples-and-tests/jobboard/app/views/Jobs/show.html (+0/-23)
samples-and-tests/jobboard/app/views/errors/404.html (+0/-18)
samples-and-tests/jobboard/app/views/errors/500.html (+0/-18)
samples-and-tests/jobboard/app/views/main.html (+0/-38)
samples-and-tests/jobboard/conf/application.conf (+0/-137)
samples-and-tests/jobboard/conf/messages (+0/-17)
samples-and-tests/jobboard/conf/routes (+0/-28)
samples-and-tests/jobboard/public/stylesheets/admin.css (+0/-144)
samples-and-tests/jobboard/public/stylesheets/default.css (+0/-316)
samples-and-tests/jobboard/public/stylesheets/reset.css (+0/-17)
samples-and-tests/jobboard/test/ModelTests.java (+0/-65)
samples-and-tests/jobboard/test/data.yml (+0/-134)
samples-and-tests/jobboard/test/postANewJobAsCompany.test.html (+0/-193)
samples-and-tests/jobboard/test/superadmin.test.html (+0/-26)
samples-and-tests/jobboard/test/tryTheJobboard.test.html (+0/-78)
samples-and-tests/just-test-cases/app/controllers/Application.java (+44/-30)
samples-and-tests/just-test-cases/app/controllers/Binary.java (+15/-1)
samples-and-tests/just-test-cases/app/controllers/DataBinding.java (+80/-0)
samples-and-tests/just-test-cases/app/controllers/Errors.java (+23/-0)
samples-and-tests/just-test-cases/app/controllers/Factories.java (+12/-0)
samples-and-tests/just-test-cases/app/controllers/Rest.java (+21/-8)
samples-and-tests/just-test-cases/app/controllers/Users.java (+13/-4)
samples-and-tests/just-test-cases/app/controllers/UsingAfter.java (+42/-0)
samples-and-tests/just-test-cases/app/controllers/UsingBefore.java (+14/-7)
samples-and-tests/just-test-cases/app/controllers/UsingBeforeWithPriority.java (+0/-37)
samples-and-tests/just-test-cases/app/controllers/UsingFinally.java (+12/-1)
samples-and-tests/just-test-cases/app/controllers/Welcome.java (+4/-2)
samples-and-tests/just-test-cases/app/controllers/WithAuthenticity.java (+22/-0)
samples-and-tests/just-test-cases/app/controllers/admin/Items.java (+11/-0)
samples-and-tests/just-test-cases/app/models/Base.java (+13/-0)
samples-and-tests/just-test-cases/app/models/Bloc.java (+4/-3)
samples-and-tests/just-test-cases/app/models/Book.java (+1/-1)
samples-and-tests/just-test-cases/app/models/Child.java (+13/-0)
samples-and-tests/just-test-cases/app/models/Company.java (+5/-5)
samples-and-tests/just-test-cases/app/models/ErrorMember.java (+26/-0)
samples-and-tests/just-test-cases/app/models/ErrorModel.java (+32/-0)
samples-and-tests/just-test-cases/app/models/Factory.java (+33/-0)
samples-and-tests/just-test-cases/app/models/Item.java (+23/-0)
samples-and-tests/just-test-cases/app/models/Parent.java (+19/-0)
samples-and-tests/just-test-cases/app/models/Person.java (+17/-0)
samples-and-tests/just-test-cases/app/models/Referenced.java (+11/-0)
samples-and-tests/just-test-cases/app/models/User.java (+5/-2)
samples-and-tests/just-test-cases/app/models/UserWithAvatar.java (+3/-5)
samples-and-tests/just-test-cases/app/models/vendor/tag/Tag.java (+2/-0)
samples-and-tests/just-test-cases/app/notifiers/Welcome.java (+3/-0)
samples-and-tests/just-test-cases/app/utils/MyTags.java (+1/-1)
samples-and-tests/just-test-cases/app/utils/PointBinder.java (+17/-0)
samples-and-tests/just-test-cases/app/utils/TestBinder.java (+14/-0)
samples-and-tests/just-test-cases/app/views/Application/errors.html (+10/-0)
samples-and-tests/just-test-cases/app/views/Application/selectTag.html (+11/-0)
samples-and-tests/just-test-cases/app/views/Binary/index.html (+11/-0)
samples-and-tests/just-test-cases/app/views/Binary/upload.html (+2/-0)
samples-and-tests/just-test-cases/app/views/DataBinding/signinPage.html (+42/-0)
samples-and-tests/just-test-cases/app/views/Users/submit.html (+1/-1)
samples-and-tests/just-test-cases/app/views/WithAuthenticity/index.html (+16/-0)
samples-and-tests/just-test-cases/app/views/main.html (+1/-1)
samples-and-tests/just-test-cases/app/views/tags/hello.html (+0/-1)
samples-and-tests/just-test-cases/conf/application-context.xml (+7/-0)
samples-and-tests/just-test-cases/conf/application.conf (+11/-1)
samples-and-tests/just-test-cases/conf/routes (+13/-0)
samples-and-tests/just-test-cases/test/ApplicationTest.java (+2/-2)
samples-and-tests/just-test-cases/test/BinaryTest.java (+50/-19)
samples-and-tests/just-test-cases/test/CRUD.test.html (+52/-4)
samples-and-tests/just-test-cases/test/FixturesTest.java (+54/-14)
samples-and-tests/just-test-cases/test/FunctionalTestTest.java (+30/-1)
samples-and-tests/just-test-cases/test/JPABinding.test.html (+5/-0)
samples-and-tests/just-test-cases/test/RestTest.java (+47/-25)
samples-and-tests/just-test-cases/test/after.test.html (+14/-0)
samples-and-tests/just-test-cases/test/authenticity.test.html (+28/-0)
samples-and-tests/just-test-cases/test/before.test.html (+5/-0)
samples-and-tests/just-test-cases/test/binding.test.html (+63/-0)
samples-and-tests/just-test-cases/test/complexBinding.test.html (+24/-24)
samples-and-tests/just-test-cases/test/dumbSelenium.test.html (+11/-0)
samples-and-tests/just-test-cases/test/errors.test.html (+1/-1)
samples-and-tests/just-test-cases/test/escape.test.html (+8/-8)
samples-and-tests/just-test-cases/test/finally.test.html (+6/-1)
samples-and-tests/just-test-cases/test/list.test.html (+1/-1)
samples-and-tests/just-test-cases/test/mailer.test.html (+6/-1)
samples-and-tests/just-test-cases/test/pc.yml (+8/-0)
samples-and-tests/just-test-cases/test/refs.yml (+9/-0)
samples-and-tests/just-test-cases/test/selectTag.test.html (+18/-0)
samples-and-tests/just-test-cases/test/simpleBinding.test.html (+3/-13)
samples-and-tests/just-test-cases/test/vendor-data.yml (+3/-2)
samples-and-tests/validation/app/controllers/Application.java (+0/-14)
samples-and-tests/validation/app/controllers/Sample1.java (+0/-54)
samples-and-tests/validation/app/controllers/Sample2.java (+0/-52)
samples-and-tests/validation/app/controllers/Sample3.java (+0/-36)
samples-and-tests/validation/app/controllers/Sample4.java (+0/-27)
samples-and-tests/validation/app/controllers/Sample5.java (+0/-27)
samples-and-tests/validation/app/controllers/Sample6.java (+0/-27)
samples-and-tests/validation/app/controllers/Sample7.java (+0/-27)
samples-and-tests/validation/app/models/ComplicatedUser.java (+0/-17)
samples-and-tests/validation/app/models/User.java (+0/-19)
samples-and-tests/validation/app/models/UserInformation.java (+0/-13)
samples-and-tests/validation/app/views/Application/index.html (+0/-83)
samples-and-tests/validation/app/views/Sample1/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample1/index.html (+0/-85)
samples-and-tests/validation/app/views/Sample2/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample2/index.html (+0/-89)
samples-and-tests/validation/app/views/Sample3/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample3/index.html (+0/-89)
samples-and-tests/validation/app/views/Sample4/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample4/index.html (+0/-89)
samples-and-tests/validation/app/views/Sample5/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample5/index.html (+0/-89)
samples-and-tests/validation/app/views/Sample6/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample6/index.html (+0/-107)
samples-and-tests/validation/app/views/Sample7/handleSubmit.html (+0/-24)
samples-and-tests/validation/app/views/Sample7/index.html (+0/-109)
samples-and-tests/validation/app/views/main.html (+0/-18)
samples-and-tests/validation/app/views/tags/jQueryValidate.html (+0/-36)
samples-and-tests/validation/conf/application.conf (+0/-115)
samples-and-tests/validation/conf/messages (+0/-34)
samples-and-tests/validation/conf/routes (+0/-12)
samples-and-tests/validation/public/javascripts/jquery-latest.js (+0/-4241)
samples-and-tests/validation/public/javascripts/jquery.validate.js (+0/-1112)
samples-and-tests/validation/public/stylesheets/main.css (+0/-46)
samples-and-tests/validation/test/application/SomeTests.java (+0/-28)
samples-and-tests/yabe/app/controllers/Admin.java (+0/-8)
samples-and-tests/yabe/app/controllers/Application.java (+1/-3)
samples-and-tests/yabe/app/models/Post.java (+26/-25)
samples-and-tests/yabe/conf/application.conf (+1/-1)
samples-and-tests/yabe/conf/routes (+1/-1)
samples-and-tests/yabe/test/Admin.test.html (+3/-3)
samples-and-tests/yabe/test/Application.test.html (+1/-1)
samples-and-tests/zencontact/conf/application.conf (+2/-0)
samples-and-tests/zencontact/test/Application.test.html (+44/-43)
support/vim/html.snippets (+0/-256)
support/vim/java.snippets (+0/-140)
To merge this branch: bzr merge lp:~nicolas-lunatech/play/play-ssl
Reviewer Review Type Date Requested Status
play framework developers Pending
Review via email: mp+35750@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

1053. By Nicolas Leroux <email address hidden>

Merge

1052. By Nicolas Leroux <email address hidden>

SSL support for netty

1051. By Nicolas Leroux <email address hidden>

Reverted as this was not for this branch

1050. By Nicolas Leroux <email address hidden>

Missing file

1049. By Guillaume Bort

Hum

1048. By Erwan Loisant <erwan@coot>

[640637] Introduce Http.StatusCode, with constants for http codes

1047. By Erwan Loisant <erwan@coot>

Update the javadoc to match reality and remove dead code

1046. By Erwan Loisant <erwan@coot>

Include async v1.2.0

1045. By Erwan Loisant <erwan@coot>

Upgrade http-async-client to 1.2

1044. By Erwan Loisant <erwan@coot>

Fix test cases

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2010-06-15 14:04:19 +0000
3+++ .bzrignore 2010-09-16 22:21:08 +0000
4@@ -67,20 +67,49 @@
5 samples-and-tests/yabe/tmp
6 *.ser
7 modules/cobertura/lib
8+modules/scala/lib/play-scala.jar
9+samples-and-tests/with-scala/logs
10+samples-and-tests/yabe-with-scala/logs
11+samples-and-tests/yabe-with-scala/tmp
12 samples-and-tests/yabe/.amateras
13 samples-and-tests/yabe/.settings
14 samples-and-tests/yabe/eclipse
15 samples-and-tests/yabe/nbproject
16+samples-and-tests/with-scala/.amateras
17+samples-and-tests/with-scala/.externalToolBuilders
18+samples-and-tests/with-scala/.manager
19+samples-and-tests/with-scala/.settings
20+samples-and-tests/with-scala/eclipse
21+samples-and-tests/test-scala/tmp
22+samples-and-tests/test-scala/logs
23 samples-and-tests/i-am-a-developer/i-am-working-here
24 samples-and-tests/just-test-cases/attachments
25 samples-and-tests/booking/logs
26 samples-and-tests/booking/tmp
27+help
28+*.iml
29+*.ipr
30+*.iws
31+modules/router/lib/router.jar
32+modules/grizzly/lib/play-grizzly.jar
33 play.ipr
34 play.iws
35 *iml
36+modules/router-head
37+modules/gwt/samples-and-tests/logs
38+modules/gwt/samples-and-tests/test-result
39+modules/gwt/samples-and-tests/tmp
40+modules/siena-1.0
41 samples-and-tests/zencontact/logs
42 samples-and-tests/zencontact/test-result
43 samples-and-tests/zencontact/tmp
44+modules/scala
45 modules/*
46+build.classes
47+modules/crud/nbproject
48+nbproject
49+samples-and-tests/jobboard/attachments
50+samples-and-tests/jobboard/logs
51+samples-and-tests/jobboard/test-result
52+samples-and-tests/jobboard/tmp
53 samples-and-tests/validation/logs
54-samples-and-tests/validation/tmp
55
56=== removed file 'documentation/commands/cmd-all.txt'
57--- documentation/commands/cmd-all.txt 2010-04-12 10:11:35 +0000
58+++ documentation/commands/cmd-all.txt 1970-01-01 00:00:00 +0000
59@@ -1,34 +0,0 @@
60-~ For all commands, if the application is not specified, the current directory is used
61-~ Use 'play help cmd' to get more help on a specific command
62-~
63-~ Available commands are:
64-~ ~~~~~~~~~~~~~~~~~~~~~~~
65-~ auto-test Automatically run all application tests
66-~ build-module Build and package a module
67-~ classpath Display the computed classpath
68-~ clean Delete temporary files (including the bytecode cache)
69-~ eclipsify Create all Eclipse configuration files
70-~ help Display help on a specific command
71-~ id Define the framework ID
72-~ idealize Create all IntelliJ Idea configuration files
73-~ install Install a module
74-~ javadoc Generate your application Javadoc
75-~ list-modules List modules available from the central modules repository
76-~ modules Display the computed modules list
77-~ netbeansify Create all NetBeans configuration files
78-~ new Create a new application
79-~ new-module Create a new module
80-~ out Follow logs/system.out file
81-~ pid Show a running application's process ID
82-~ precompile Precompile all Java sources and templates to speed up application start-up
83-~ run Run the application in the current shell
84-~ restart Restart the running application
85-~ secret Generate a new secret key
86-~ status Display the running application's status
87-~ start Start the application in the background
88-~ stop Stop the running application
89-~ test Run the application in test mode in the current shell
90-~ war Export the application as a standalone WAR archive
91-~
92-~ Also refer to documentation at http://www.playframework.org/documentation
93-~
94\ No newline at end of file
95
96=== added file 'documentation/commands/cmd-install.txt'
97--- documentation/commands/cmd-install.txt 1970-01-01 00:00:00 +0000
98+++ documentation/commands/cmd-install.txt 2010-09-16 22:21:08 +0000
99@@ -0,0 +1,17 @@
100+~ Name:
101+~ ~~~~~
102+~ install -- Download and install a Play module
103+~
104+~ Synopsis:
105+~ ~~~~~~~~~
106+~ play install [module_name]
107+~
108+~ Description:
109+~ ~~~~~~~~~~~~
110+~ Modules are 3rd party software to extend Play's capabilities.
111+~ Available modules are listed with the play list-modules command,
112+~ or online at http://www.playframework.org/modules
113+~
114+~ Once installed, Play modules can be included in any Play application
115+~ by adding the correct line to conf/application.conf.
116+~
117\ No newline at end of file
118
119=== removed file 'documentation/commands/cmd-install.txt'
120--- documentation/commands/cmd-install.txt 2010-04-12 09:35:52 +0000
121+++ documentation/commands/cmd-install.txt 1970-01-01 00:00:00 +0000
122@@ -1,17 +0,0 @@
123-~ Name:
124-~ ~~~~~
125-~ install -- Download and install a Play module
126-~
127-~ Synopsis:
128-~ ~~~~~~~~~
129-~ play install [module_name]
130-~
131-~ Description:
132-~ ~~~~~~~~~~~~
133-~ Modules are third-party software to extend Play's capabilities.
134-~ Available modules are listed with the play list-modules command,
135-~ or on-line at http://www.playframework.org/modules
136-~
137-~ Once installed, Play modules can be included in any Play application
138-~ by adding the correct line to conf/application.conf.
139-~
140\ No newline at end of file
141
142=== added file 'documentation/images/scala-error.png'
143Binary files documentation/images/scala-error.png 1970-01-01 00:00:00 +0000 and documentation/images/scala-error.png 2010-09-16 22:21:08 +0000 differ
144=== added file 'documentation/images/scguide1-1.png'
145Binary files documentation/images/scguide1-1.png 1970-01-01 00:00:00 +0000 and documentation/images/scguide1-1.png 2010-09-16 22:21:08 +0000 differ
146=== added file 'documentation/images/scguide1-3.png'
147Binary files documentation/images/scguide1-3.png 1970-01-01 00:00:00 +0000 and documentation/images/scguide1-3.png 2010-09-16 22:21:08 +0000 differ
148=== removed file 'documentation/manual/cobertura.textile'
149--- documentation/manual/cobertura.textile 2009-11-12 11:56:33 +0000
150+++ documentation/manual/cobertura.textile 1970-01-01 00:00:00 +0000
151@@ -1,46 +0,0 @@
152-h1. Cobertura test coverage
153-
154-This module for the Play! framework provides a plugin to support the Cobertura test coverage tool.
155-
156-Cobertura is a free Java tool that calculates the percentage of code accessed by tests. It can be used to identify which parts of your Java program are lacking test coverage. It is based on jcoverage.
157-
158-See "http://cobertura.sourceforge.net/":http://cobertura.sourceforge.net/ for more information.
159-
160-Let's see a simple usage example.
161-
162-h2. <a>Enable the Cobertura module for the application</a>
163-
164-In the **/conf/application.conf** file, enable the Cobertura module by adding this line:
165-
166-bc. # The cobertura test coverage module
167-module.cobertura=${play.path}/modules/cobertura
168-
169-This plugin is intended for use in the 'test' mode, and will silently do nothing in other modes. Therefore, leaving it activated while running your application in other modes is not a problem.
170-
171-h2. <a>Run your tests</a>
172-
173-Run your tests as normal, for example:
174-
175-bc. $ play test
176-
177-or
178-
179-bc. $ play auto-test
180-
181-Code coverage is directly calculated when running the tests - each test you run will add coverage to your Java files. So at this step, it is important to run all the tests you want to analyze coverage for.
182-
183-h2. <a>Admire the results!</a>
184-
185-Once the test run is finished, stop the Play! framework. The cobertura module will generate an HTML report automatically in a directory named *test-result/code-coverage/* in your project home.
186-
187-bc. [...] INFO - Cobertura plugin: generating test coverage report
188-[...]
189-[...] INFO - Test coverage report has been generated: file:/path/to/test-result/code-coverage/index.html
190-
191-The module will output the URL to view this report on the console. You can view this in your web browser, with a URL like this: "file:/path/to/your/application/test-result/code-coverage/":file:/path/to/your/application/test-result/code-coverage/.
192-
193-!images/cobertura1!
194-
195-h2. <a>Continuous integration</a>
196-
197-Continuous integration tools, such as "Hudson":http://hudson.dev.java.net/, can often use Cobertura results to graph the test coverage. For this purpose, the Cobertura module also generates an XML format report, in *test-result/code-coverage/coverage.xml*.
198
199=== modified file 'documentation/manual/controllers.textile'
200--- documentation/manual/controllers.textile 2010-08-26 14:49:38 +0000
201+++ documentation/manual/controllers.textile 2010-09-16 22:21:08 +0000
202@@ -149,15 +149,33 @@
203 * MM-dd-yy
204 * MM'/'dd'/'yy
205
206+Using the @Bind annotation, you can specify the date format.
207+
208 For example:
209
210 bc. archives?from=21/12/1980
211
212-bc. public static void articlesSince(Date from) {
213+bc. public static void articlesSince(@Bind("dd/MM/yyyy") Date from) {
214 List<Article> articles = Article.findBy("date >= ?", from);
215 render(articles);
216 }
217
218+If no **@Bind** annotation is specified, then Play! uses the default date format according to your locale.
219+To set the default date format to use, edit your application.conf and set the following property:
220+
221+bc. date.format=yyy-MM-dd
222+ date.format.fr=dd/MM/yyyy
223+
224+Please note that the language fr in the application.conf must be enabled as well:
225+bc. application.langs=fr
226+
227+This property also affects how the dates are rendered in the templates using the ${date.format()}.
228+
229+
230+h3. Calendar
231+
232+The calendar binding works exactly as with the date, except that Play! is choosing the Calendar object according to your locale. The @Bind annotation can also be used.
233+
234 h3. Files
235
236 File upload is easy with Play. Use a **multipart/form-data** encoded request to post files to the server, and then use the **java.io.File** type to retrieve the file object:
237@@ -214,7 +232,7 @@
238 &client.address.zip=75009
239 &client.address.country=France
240
241-In order to update a list of model objects, use array notation and reference the object’s ID. For example imagine the Client model has a list of Customer models declared as **List<Customer> customers**. To update the list of Customers you would provide a query string like the following:
242+In order to update a list of model objects, use array notation and reference the object’s ID. For example imagine the Client model has a list of Customer models declared as **List Customer customers**. To update the list of Customers you would provide a query string like the following:
243
244 bc. ?client.customers[0].id=123
245 &client.customers[1].id=456
246@@ -427,6 +445,20 @@
247
248 }
249
250+Or if you want the @Before method to intercept a list of action calls, you can specify a only param :
251+
252+bc. public class Admin extends Application {
253+
254+ @Before(only={"login","logout"})
255+ static void doSomething() {
256+ ...
257+ }
258+
259+ ...
260+}
261+
262+unless and only params are available for @After @Before and @Finally
263+
264 h3. @After
265
266 Methods annotated with the **@After** annotation are executed after each action call for this Controller.
267
268=== modified file 'documentation/manual/emails.textile'
269--- documentation/manual/emails.textile 2010-07-28 19:25:39 +0000
270+++ documentation/manual/emails.textile 2010-09-16 22:21:08 +0000
271@@ -1,8 +1,31 @@
272 h1. Sending e-mail with Play
273
274-You can use the play.libs.Mail utility to send e-mail very easily:
275-
276-bc. Mail.send("sender@zenexity.fr","recipient@zenexity.fr","Subject","Message");
277+Email are using the "jakarta commons email":http://commons.apache.org/email/userguide.html library under the hoot. You can use the play.libs.Mail utility to send emails very easily:
278+
279+A simple email:
280+
281+bc. SimpleEmail email = new SimpleEmail();
282+email.setFrom("sender@zenexity.fr");
283+email.addTo("recipient@zenexity.fr");
284+email.setSubject("subject");
285+email.setMsg("Message");
286+Mail.send(email);
287+
288+An html email:
289+
290+bc. HtmlEmail email = new HtmlEmail();
291+email.addTo("info@lunatech.com");
292+email.setFrom(sender@lunatech.com", "Nicolas");
293+email.setSubject("Test email with inline image");
294+// embed the image and get the content id
295+URL url = new URL("http://www.zenexity.fr/wp-content/themes/zenexity/images/logo.png");
296+String cid = email.embed(url, "Zenexity logo");
297+// set the html message
298+email.setHtmlMsg("<html>Zenexity logo - <img src=\"cid:"+cid+"\"></html>");
299+// set the alternative message
300+email.setTextMsg("Your email client does not support HTML messages, too bad :(");
301+
302+More information about the commons email "here":http://commons.apache.org/email/userguide.html
303
304 h2. <a>Mail and MVC integration</a>
305
306@@ -25,14 +48,17 @@
307 public static void welcome(User user) {
308 setSubject("Welcome %s", user.name);
309 addRecipient(user.email);
310- setFrom("Me <me@me.com>");
311- addAttachment(Play.getFile("rules.pdf"));
312+ addFrom("Me <me@me.com>");
313+ EmailAttachment attachment = new EmailAttachment();
314+ attachment.setDescription("A pdf document");
315+ attachment.setPath(Play.getFile("rules.pdf").getPath());
316+ addAttachment(attachment);
317 send(user);
318 }
319
320 public static void lostPassword(User user) {
321 String newpassword = user.password;
322- setFrom("Robot <robot@thecompany.com>");
323+ addFrom("Robot <robot@thecompany.com>");
324 setSubject("Your password has been reset");
325 addRecipient(user.email);
326 send(user, newpassword);
327
328=== modified file 'documentation/manual/faq.textile'
329--- documentation/manual/faq.textile 2010-08-27 09:19:18 +0000
330+++ documentation/manual/faq.textile 2010-09-16 22:21:08 +0000
331@@ -1,6 +1,5 @@
332 h1. Frequently Asked Questions
333
334-
335 h2. <a>Where do I ask questions that are not answered here?</a>
336
337 The "Community":http://www.playframework.org/community page links to various places where you can read and post about Play. In general, the best place to ask questions is the "play-framework Google Group":http://groups.google.com/group/play-framework.
338@@ -51,4 +50,4 @@
339
340 We use "launchpad":https://launchpad.net/ as our main development tool, and launchpad itself is very open. You can just register a launchpad account and start to contribute. You can assign any open bug to yourself, fork any Play official branch to your own branch, and propose a merge to us if you have fixed something.
341
342-The documentation itself is kept as "Textile":http://en.wikipedia.org/wiki/Textile_(markup_language%29 files in the framework source repository, so you can edit it and contribute just as you can with code.
343+The documentation itself is kept as "Textile":http://en.wikipedia.org/wiki/Textile_(markup_language%29 files in the framework "source repository":http://bazaar.launchpad.net/~play-developers/play/1.1-unstable/files/head%3A/documentation/manual, so you can edit it and contribute just as you do with code.
344
345=== modified file 'documentation/manual/home.textile'
346--- documentation/manual/home.textile 2010-08-30 13:30:48 +0000
347+++ documentation/manual/home.textile 2010-09-16 22:21:08 +0000
348@@ -1,8 +1,8 @@
349 h1. Play framework documentation
350
351-Welcome to the Play framework documentation. This documentation is for the **1.0.3 release**, which differs from previous versions of the framework. Check the "version 1.0.3 release notes":releasenotes-1.0.3 for details.
352+Welcome to the play framework documentation. This documentation is intended for the *1.1 release* and may significantly differs from previous versions of the framework. Check the "version 1.1 release notes":releasenotes-1.1.
353
354-p(note). This is a work in progress, feel free to ask any question not covered into these documents or report any errors. You can contribute to the documentation the "same way you can contribute code":faq.
355+p(note). The 1.1 branch is in active development.
356
357 <div id="searchBox"><form action="http://www.google.com/cse" id="cse-search-box"><div><input type="hidden" name="cx" value="002614023023983855063:jn1mu_7bof0" /><input type="hidden" name="ie" value="UTF-8" /><input type="text" name="q" size="31" style="font-size:14px"/> <input type="submit" name="sa" value="Search with google" style="font-size:14px"/></div></form><script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse-search-box&lang=en"></script></div>
358
359@@ -47,9 +47,8 @@
360 ## "The MVC application model":main#mvc
361 ## "A request life cycle":main#request
362 ## "Application layout & organization":main#application
363-## "Development life cycle":main#lifecycle
364+## "Development lifecycle":main#lifecycle
365 # "HTTP routing":routes
366-## "About REST":routes#rest
367 ## "The routes file syntax":routes#syntax
368 ## "Routes priority":routes#priority
369 ## "Serving static resources":routes#static
370@@ -57,32 +56,24 @@
371 ## "Setting content types":routes#content-types
372 ## "Content negotiation":routes#content-negotiation
373 # "Controllers":controllers
374-## "Controller overview":controllers#overview
375-## "Retrieving HTTP parameters":controllers#params
376+## "A Controller overview":controllers#overview
377+## "Retrieve HTTP parameters":controllers#params
378 ## "Advanced HTTP to Java binding":controllers#binding
379-## "JPA object binding":controllers#objectbinding
380-## "Result types":controllers#result
381+## "Return a result":controllers#result
382 ## "Interceptions":controllers#interceptions
383 ## "Session and Flash scopes":controllers#session
384 # "The template engine":templates
385 ## "Template syntax":templates#syntax
386 ## "Template inheritance":templates#inheritance
387-## "Custom tags":templates#tags
388-## "Java template extensions":templates#extensions
389-## "Implicit objects available in a template":templates#implicits
390-# "Validating HTTP data (form validation)":validation
391-## "How validation works in Play":validation#how
392-## "Retrieving validation error messages":validation#messages
393-## "Displaying validation errors in the template":validation#display
394-## "Validation annotations":validation#annotations
395-## "Validating complex objects":validation#objects
396-## "Custom validation":validation#custom
397+## "Create tags":templates#tags
398+## "Extending the templates language":templates#extensions
399+## "Implicit objects":templates#implicits
400 # "The domain object model":model
401 ## "Properties simulation":model#properties
402-## "Set-up a database to persist your model objects":model#database
403+## "Setup a database to persist your model objects":model#database
404 ## "Persist your object model with Hibernate":model#hibernate
405 ## "The stateless model":model#stateless
406-# "A little asynchronism using jobs":jobs
407+# "A little of asynchronism using jobs":jobs
408 ## "Bootstrap jobs":jobs#concepts
409 ## "Scheduled jobs":jobs#scheduling
410 ## "Suspendable requests":jobs#suspendable
411@@ -94,7 +85,7 @@
412 # "Deployment options":deployment
413 ## "Standalone Play application":deployment#standalone
414 ## "Running on JEE application servers":deployment#appservers
415-## "Google Application Engine (GAE)":deployment#gae
416+## "Google Application Engine":deployment#gae
417 ## "Stax cloud hosting platform":deployment#stax
418
419
420@@ -102,41 +93,41 @@
421
422 Tips about everything and anything.
423
424-# "Play modules, and the modules repository":modules
425-# "Security guide":security
426-# "JPA support":jpa
427-# "Logging configuration":logs
428+# "Using Play modules, and the modules repository":modules
429+# "Security Guide":security
430+# "More about the JPA support":jpa
431+# "Using data validation":validation
432+# "How to configure logs":logs
433 # "Manage application.conf in several environments":ids
434-# "Internationalisation guide":i18n
435-# "Cache and Memcached configuration":cache
436-# "Putting your application in production":production
437+# "Setting up i18n":i18n
438+# "Use cache and configure Memcached":cache
439+# "Put your application in production":production
440 # "Sending e-mail":emails
441 # "OpenID integration":openid
442-# "Ajax with JQuery":ajax
443-# "YAML":yaml
444+# "Doing Ajax with JQuery":ajax
445
446-h2. <a name="modules">Standard modules</a>
447+h2. <a name="modules">Distribution Modules</a>
448
449 These optional modules are included with the standard distribution. "More modules":http://www.playframework.org/modules.
450
451 # "CRUD":crud
452 # "Secure":secure
453
454-h2. <a name="references">Reference documentation</a>
455+h2. <a name="references">References</a>
456
457 Extended references for day-to-day hacking.
458
459-# "API documentation (Javadoc)":/@api/index.html
460-# "Template tags":tags
461+# "The missing cheat sheet":http://download.playframework.org/miscellaneous/play-cheat-sheet.pdf
462+# "Browse API documentation":/@api/index.html
463+# "Template tags and extensions reference":tags
464 # "Java extensions":javaextensions
465-# "Built-in validations":validation-builtin
466-# "The missing cheat sheet":http://download.playframework.org/miscellaneous/play-cheat-sheet.pdf
467
468
469 h2. <a name="versionnotes">Version notes</a>
470
471 New versions of Play include certain changes.
472
473+# "Play 1.1":releasenotes-1.1
474 # "Play 1.0.3":releasenotes-1.0.3
475 # "Play 1.0.2":releasenotes-1.0.2
476 # "Play 1.0.1":releasenotes-1.0.1
477
478=== modified file 'documentation/manual/i18n.textile'
479--- documentation/manual/i18n.textile 2010-08-26 14:49:38 +0000
480+++ documentation/manual/i18n.textile 2010-09-16 22:21:08 +0000
481@@ -51,6 +51,19 @@
482
483 The new value will be saved back to the user’s language cookie.
484
485+h2. <a name="dateformat">Define date format according to your locale</a>
486+
487+To set the default date format to use, edit your application.conf and set the following property:
488+
489+bc. date.format=yyy-MM-dd
490+ date.format.fr=dd/MM/yyyy
491+
492+Please note that the language fr in the application.conf must be enabled as well (see above):
493+bc. application.langs=fr,en
494+
495+This property affects how the dates are rendered in the templates using the ${date.format()}.
496+It also set the default date format when binding a date parameter.
497+
498 h2. <a name="output">Retrieve localized messages</a>
499
500 From the application code, you can retrieve messages defined in message files. From Java, use the **play.i18n.Messages** object.
501
502=== added file 'documentation/manual/lambdaj.textile'
503--- documentation/manual/lambdaj.textile 1970-01-01 00:00:00 +0000
504+++ documentation/manual/lambdaj.textile 2010-09-16 22:21:08 +0000
505@@ -0,0 +1,129 @@
506+h1. Some functional programming techniques
507+
508+The 1.1 release will allow "support of the Scala programming language":scala. One great thing (but not the only one) about Scala is that it is a mixed imperative -- functional language that allow to solve a lot of problem in a functional way.
509+
510+But now, what if you prefer keep using Java with play ? Could we bring some of the nice features of Scala to Java as well ? So let's me introduce the new "LambdaJ":http://code.google.com/p/lambdaj/ support in play.
511+
512+The main purpose of lambdaj is to partially eliminate the burden to write (often nested and poorly readable) loops while iterating over collections. From the LambdaJ website:
513+
514+bq. How many times have you read or written the same two or three lines of code that frequently seem to go together, and even though they operate on different objects, feel like the same thing? And how often these repetitions involve some sort of collections iteration or more generically manipulation? These repetitions in the code is something that developers eventually learn to filter out and ignore when reading code, once they figure out where the interesting parts are placed. But even if the developers get used to it, it slows them down. Code like that is clearly written for computers to execute, not for developers to read.
515+
516+lambdaj is a library that makes easier to address this issue by allowing to manipulate collections in a pseudo-functional and statically typed way. In our experience to iterate over collection, especially in nested loops, is often error prone and makes the code less readable. The purpose of this library is to alleviate these problems employing some functional programming techniques but without losing the static typing of java. We impose this last constraint to make refactoring easier and safer and allow the compiler to do its job.
517+
518+h2. <a>Let's use lambdaj</a>
519+
520+We will start with a fresh application that allow to display a **Car** catalog. The **Car** model class will just be defined as:
521+
522+bc. package models;
523+
524+import play.*;
525+import play.db.jpa.*;
526+
527+import javax.persistence.*;
528+import java.util.*;
529+
530+@Entity
531+public class Car extends Model {
532+
533+ public String name;
534+ public String brand;
535+ public int numberOfViews;
536+ public Date lastViewed;
537+
538+ @Transient
539+ public double price;
540+
541+ public Car viewed() {
542+ lastViewed = new Date();
543+ numberOfViews++;
544+ return this;
545+ }
546+
547+}
548+
549+And let's write a simple action that retrieve all these cars:
550+
551+bc. public static index() {
552+ List<Car> cars = Car.find().fetch();
553+ render(cars);
554+}
555+
556+Now in the page it would be great to be able to order all these cars by brand. So we need to extract all the brand from the car list. Let's do it the lambdaj way:
557+
558+bc. List<String> brands = collect(cars, on(Car.class).brand);
559+
560+This line will iterate over all the retrieved cars, collect all the brands and feed them to the returned list. The very cool things is that we are able to express that stuff in a pure statically typed way.
561+
562+Now we need to filter this list to remove brand duplication:
563+
564+bc. Collection<String> brands = selectDistinct(
565+ collect(cars, on(Car.class).brand)
566+);
567+
568+Very easy.
569+
570+h2. <a>Batching method calls</a>
571+
572+We want to cound each time a Car has been viewed, and remember the last viewed time. As we already have the **viewed()** method that update the **Car** objects we just need to call this method on each retrieved car. Again, let's do it the lambdaj way:
573+
574+bc. forEach(cars).viewed();
575+
576+This line will iterate over each car object of the cars list and call the **viewed()** method on each.
577+
578+And because we modified the state of the persistent objects, we need to save them. So rewrite it as:
579+
580+bc. forEach(cars).viewed().save();
581+
582+h2. <a>Using closures</a>
583+
584+Hugh? But Java doesn't have closure! Wait, lambdaj partially fill this lack by a feature that allow to define, in its traditional DSL style, first-class functions with free variables.
585+
586+Let's say we have a **PriceWatcher** utility able to fetch in real time the price of each car.
587+
588+bc. package models;
589+
590+public class PriceWatcher {
591+
592+ public void setPrice(Car car) {
593+ // retrieve the correct price here
594+ car.price = currentPrice;
595+ }
596+
597+}
598+
599+Because this data need to be **in real time** we don't want store it in the database. Before displaying the car list we need to create a PriceWatcher object and ask it to resolve the current price of each car. Again, let's do it the lambdaj way:
600+
601+bc. PriceWatcher priceWatcher = new PriceWatcher();
602+Car.forEach(cars); {
603+ of(priceWatcher).setPrice(var(Car.class));
604+}
605+
606+We define a function with a free variable, and then ask to the Car class to call them for each element of the cars list.
607+
608+h2. <a>The final action code</a>
609+
610+Using all these good lambdaj stuff, we can finally write the **index** action in a very expressive fashion:
611+
612+bc. public static void index() {
613+ List<Car> cars = Car.find().fetch();
614+
615+ // Collect brands
616+ Collection<String> brands = selectDistinct(
617+ collect(cars, on(Car.class).brand)
618+ );
619+
620+ // Set all these cars viewed
621+ forEach(cars).viewed().save();
622+
623+ // Update the prices
624+ PriceWatcher priceWatcher = new PriceWatcher();
625+ Car.forEach(cars); {
626+ of(priceWatcher).update(var(Car.class));
627+ }
628+
629+ render(cars, brands);
630+}
631+
632+
633+
634+
635
636=== added file 'documentation/manual/scala.textile'
637--- documentation/manual/scala.textile 1970-01-01 00:00:00 +0000
638+++ documentation/manual/scala.textile 2010-09-16 22:21:08 +0000
639@@ -0,0 +1,208 @@
640+h1. Scala support
641+
642+The 1.1 release of play will include support for the "Scala":http://www.scala-lang.org programming language. Thanks to the flexibility of the play framework architecture, the Scala support is provided with a simple module. You just need to enable the scala module in the **conf/application.conf** file.
643+
644+bc. module.scala=${play.path}/modules/scala
645+
646+Then you can write all or parts of your play application using scala. You can of course mix it with Java.
647+
648+p(note). We are in very very active development on this stuff. You can try it for now as an experimental feature. Don't expect to write a complete play application in Scala right now.
649+
650+For a quick overview of the scala support, you can watch this "Scala screencast":http://vimeo.com/7731173
651+
652+h2. <a>Create a new application, with Scala support</a>
653+
654+You can automatically create a scala ready application, by using the **--with** option of the **play new** command. Just try:
655+
656+bc. play new myApp --with scala
657+
658+The play application will be created as usual, but if you look at the **controllers** package, the **Application.java** file is now replaced by a **Application.scala** file:
659+
660+bc. package controllers
661+
662+import play._
663+import play.mvc._
664+
665+object Application extends Controller {
666+
667+ def index = render()
668+
669+}
670+
671+It is very close to the Java version of the default Application controller.
672+
673+Now just run the application as usual using **play run** and it will display the standard welcome page. Now just edit the **Application.scala** file to replace the **render()** call:
674+
675+bc. def index = "Hello scala !"
676+
677+Refresh the page, and see the magic.
678+
679+As always, if you make a mistake, play will just show you the error in a perfect way; (it's just more difficult now to forget the trailing semicolon)
680+
681+!images/scala-error!
682+
683+h2. <a>Direct return types</a>
684+
685+As shown above, for simple action methods you can directly use the inferred return type to send the action result. For example using a String:
686+
687+bc. def index = "<h1>Hello world</h1>"
688+
689+And you can even use the built-in XML support to write XHTML in a literal way:
690+
691+bc. def index = <h1>Hello world</h1>
692+
693+If the return type looks like a binary stream, play will automatically use **renderBinary()**. So generating a captcha image using the built-in Captcha helper can be written as:
694+
695+bc. def index = Images.captcha
696+
697+h2. <a>Action parameters, and scala default arguments</a>
698+
699+You can declare some action parameter the same way you do it in Java:
700+
701+bc. def index(name: String) = <h1>Hello {name}</h1>
702+
703+To big plus of scala is the ability to define some default values to these parameters:
704+
705+bc. def index(name: String = "Guest") = <h1>Hello {name}</h1>
706+
707+This way if the **name** HTTP parameter is missing, play will use the default argument value.
708+
709+h2. <a>Controllers composition using traits</a>
710+
711+A controller can use several traits to combine several interceptor.
712+
713+Let's define a Secure trait:
714+
715+bc. package controllers
716+
717+import play.__
718+import play.mvc.__
719+
720+trait Secure extends Controller {
721+
722+ @Before
723+ def check {
724+ session("user") match {
725+ name: String => info("Logged as %s", name)
726+ _ => Security.login
727+ }
728+ }
729+
730+}
731+
732+And you can them use it in the Application controller:
733+
734+bc. package controllers
735+
736+object Application extends Controller with Secure {
737+
738+ def index = "Hello world"
739+
740+}
741+
742+
743+h2. <a>How to define and access Models</a>
744+
745+Models can be defined not only in java but in scala as well. Unfortunately, due to the differences between the two langauges the Model API somewhat differs from the java version.
746+
747+h3. Main differences
748+
749+* fields are passed as contructor arguments
750+* each and every class needs to extend Model[T]
751+* helper methods should be defined in the companion object
752+* companion objects need to extend Model[T]
753+here is an example:
754+
755+bc. @Entity
756+class User(
757+ //fields
758+ @Email
759+ @Required
760+ var email: String,
761+ @Required
762+ var password: String,
763+ var fullname: String
764+) extends Model[User] {
765+ //instance methods
766+ var isAdmin = false
767+ override def toString = email
768+}
769+//finder methods
770+object User extends Model[User] {
771+ def connect(email: String, password: String) = {
772+ User.find("byEmailAndPassword", email, password).first
773+ }
774+}
775+
776+h3. <a>Running queries against Scala Models from Scala classes</a>
777+
778+The following methods are available when running queries against scala models:
779+
780+bc. def count(implicit m: M[T]) = i.count(m)
781+def count(q: String, ps: AnyRef*)(implicit m: M[T])
782+def findAll(implicit m: M[T])
783+def findById(id: Any)(implicit m: M[T])
784+def findBy(q: String, ps: AnyRef*)(implicit m: M[T])
785+def find(q: String, ps: AnyRef*)(implicit m: M[T])
786+def all(implicit m: M[T])
787+def delete(q: String, ps: AnyRef*)(implicit m: M[T])
788+def deleteAll(implicit m: M[T]) =
789+def findOneBy(q: String, ps: AnyRef*)(implicit m: M[T]): T
790+def create(name: String, ps: play.mvc.Scope.Params)(implicit m: M[T]): T
791+
792+As you can see, it's really similar to the java API, so for example to count the number of users, you can just call count on the User class:
793+
794+bc. User.count
795+
796+p(note). One known limitation of the Scala Model API is that the *save* method is not working in a chained call fashion, so you always need to execute it on an instance, as you can see it later at the unit testing section
797+
798+h3. <a>Running queries against Java Models from Scala classes</a>
799+
800+In certain situations it might be desirable to query models written in java from scala.
801+Since java models are not extending from the scala Model trait, Play needs to provide an alternative query interface which comes in the form of *QueryRunner* trait and the corresponding companion object. In order to utilize this feature you either need to import the query methods like
802+
803+bc. import play.db.jpa.QueryRunner._
804+
805+or you can mix in the trait
806+
807+bc. class MyController extends Controller with QueryRunner {...}
808+
809+and the API is defined like this:
810+
811+bc. def count[T](implicit m: M[T]) = i.count(m)
812+def count[T](q: String, ps: AnyRef*)(implicit m: M[T])
813+def findAll[T](implicit m: M[T])
814+def findById[T](id: Any)(implicit m: M[T])
815+def findBy[T](q: String, ps: AnyRef*)(implicit m: M[T])
816+def find[T](q: String, ps: AnyRef*)(implicit m: M[T])
817+def all[T](implicit m: M[T])
818+def delete[T](q: String, ps: AnyRef*)(implicit m: M[T])
819+def deleteAll[T](implicit m: M[T]) =
820+def findOneBy[T <: JPASupport](q: String, ps: AnyRef*)(implicit m: M[T]): T
821+def create[T <: JPASupport](name: String, ps: play.mvc.Scope.Params)(implicit m: M[T]): T
822+
823+
824+Using the previous example *User.count* becomes *count[User]*
825+
826+
827+h2. <a>Unit Testing</a>
828+
829+ScalaTest support is integrated into Play, so one can easily write unit tests using ScalaTest, for example:
830+
831+bc. class SpecStyle extends UnitTest with FlatSpec with ShouldMatchers {
832+"Creating a user" should "be succesfull" in {
833+ val user = new User("bob@gmail.com", "secret", "Bob")
834+ user.save
835+ bob = User.find("byEmail", "bob@gmail.com").first
836+ bob should not be (null)
837+ bob.fullname should be ("Bob")
838+ }
839+}
840+
841+h2. <a>Tutorial</a>
842+
843+p(note). We are currently writing a version of the "play tutorial":guide1 for scala.
844+
845+Read "The play tutorial (Scala version)":scguide1.
846+
847+
848
849=== added file 'documentation/manual/scguide1.textile'
850--- documentation/manual/scguide1.textile 1970-01-01 00:00:00 +0000
851+++ documentation/manual/scguide1.textile 2010-09-16 22:21:08 +0000
852@@ -0,0 +1,216 @@
853+h1. Starting up the project (Scala version)
854+
855+h2. <a>Introduction</a>
856+
857+In this tutorial you will learn the play framework by coding a real web application, from start to finish. In this application, we will try to use everything you would need in a real project, while introducing good practices for play application development.
858+
859+We have split the tutorial into several independent parts. Each part will introduce more complex features, and provide everything that a real project needs: validation, error handling, security, an automated test suite, a shiny web interface, an administration area, etc.
860+
861+p(note). **All the code** included in this tutorial can be used for your projects. We encourage you to copy and paste snippets of code or steal whole chunks.
862+
863+h2. <a>The project</a>
864+
865+We chose to create yet another blog engine. It's not a very imaginative choice but it will allow to explore most of the functionality needed by a modern web application.
866+
867+To complicate things a bit we will manage several users with different roles (editor, admin).
868+
869+We will call this blog engine project **yabe**.
870+
871+!images/guide1-0!
872+
873+p(note). This application is distributed as a sample application as well. You can retrieve the final code in the **samples-and-tests/yabe-with-scala** directory of your play installation.
874+
875+h2. <a>Prerequisites</a>
876+
877+First of all, make sure that you have a working Java installation. Play requires **Java 5 or later**.
878+
879+As we will use the command line a lot, it's better to use a Unix-like OS. If you run a Windows system, it will also work fine; you'll just have to type a few commands in the command prompt.
880+
881+We will assume that you are familiar with Java and Web development (especially HTML, CSS and Javascript). Additionally, this version of the tutorial assumes a bit of familiarity with Scala, although we won't use many exotic features and will explain as we go. You don't need to have a deep knowledge of all the JEE components. Play is a 'full stack' Java framework and it provides or encapsulates all the Java APIs you will need. No need to know how to configure a JPA entity manager or deploy a JEE component.
882+
883+You will of course need a text editor. If you are accustomed to use a full featured Java IDE like Eclipse or Netbeans you can of course use it. However with play you can have fun working with a simple text editor like TextMate, Emacs or VI. This is because the framework manages the compilation and the deployment process itself. We will soon see that...
884+
885+Note that support for Scala in the major IDEs is far less mature than for Java, so we really suggest using a text editor to start with.
886+
887+Later in this tutorial we will use Lighttpd and MySql to show how to deploy a play application in a 'production' mode. But Play can work without these components so if you can't install them, it's not a problem.
888+
889+h2. <a>Installation of the play framework</a>
890+
891+Installation is very simple. Just download the latest binary package from the download page and unzip it to any path.
892+
893+p(note). If you're using windows, it is generally a good idea to avoid space characters in the path, so for example **c:\play** would be a better choice than **c:\Documents And Settings\user\play**.
894+
895+To work efficiently, you need to add the play directory to your working path. It allows to type just **'play'** at the command prompt to use the play utility. To check that the installation worked, just open a new command line and type **'play'**; it should show you the play basic usage help.
896+
897+h2. <a>Project creation</a>
898+
899+Now that play is correctly installed, it's time to create the blog application. Creating a play application is pretty easy and fully managed by the play command line utility. That allows for standard project layouts between all play applications.
900+
901+Open a new command line and type:
902+
903+bc. ~$ play new yabe-with-scala --with scala
904+
905+It will prompt you for the application full name. Type **'Yet Another Blog Engine'**.
906+
907+!images/scguide1-1!
908+
909+The **'play new'** command creates a new directory **yabe-with-scala/** and populates it with a series of files and directories, the most important being:
910+
911+**app/** contains the core of the application, split between models, controllers and views directories. It can contain other Java packages as well. This is the directory where *.java and *.scala source files live.
912+
913+**conf/** contains all the configuration files of the application, especially the main **application.conf** file, the **routes** definition files and the **messages** files used for internationalization.
914+
915+**lib/** contains all optional Java/Scala libraries packaged as standard .jar files.
916+
917+**public/** contains all the publicly available resources, which includes Javascript files, stylesheets and images directories.
918+
919+**test/** contains all the application tests. Tests are either written either as Java JUnit tests or as Selenium tests.
920+
921+p(note). Because **play uses UTF-8** as single encoding, it's very important that all text files hosted in these directories are encoded using this charset. Make sure to configure your text editor accordingly.
922+
923+Now if you're a seasoned Java developer, you may wonder where all the .class files go. The answer is nowhere: play doesn't use any class files but reads directly the java source files. Under the hood we use the Eclipse compiler to compile Java sources on the fly, as well as the standard Scala compiler for scala files.
924+
925+That allows two very important things in the development process. The first one is that play will detect changes you make to any Java/Scala source file and automatically reload them at runtime. The second is that when a Java exception occurs, play will create better error reports showing you the exact source code.
926+
927+p(note). In fact play can keep a bytecode cache in the application **/tmp** directory, but only to speed up things between restart on large applications. You can discard this cache using the **'play clean'** command if needed.
928+
929+h2. <a>Running the application</a>
930+
931+We can now test the newly created application. Just return to the command line, go to the newly created **yabe-with-scala/** directory and type **'play run'**. Play will now load the application and start a Web server on port 9000.
932+
933+You can see the new application by opening a browser to "http://localhost:9000":http://localhost:9000. A new application has a standard welcome page that just tells you that it was successfully created.
934+
935+!images/guide1-2!
936+
937+Let's see how the new application can display this page.
938+
939+The main entry point of your application is the **conf/routes** file. This file defines all accessible URL of the application. If you open the generated routes file you will see this first 'route':
940+
941+bc. GET / Application.index
942+
943+That simply tells play that when the web server receives a **GET** request for the **/** path, it must call the **Application.index** Scala method. In this case, **Application.index** is a shortcut for **controllers.Application.index**, because the controllers package is implicit.
944+
945+A play application has several entry points, one for each URL. We call these methods **'action'** methods. Action methods are defined in special objects which we call **'controllers'**.
946+
947+Let's see what the **controllers.Application** controller looks like. Open the **yabe-with-scala/app/controllers/Application.scala** source file:
948+
949+bc. package controllers
950+
951+import play._
952+import play.mvc._
953+
954+object Application extends Actions {
955+
956+ def index = render()
957+
958+}
959+
960+As you see, a controller is not a class but an object. It extends the **play.mvc.Actions** class. This class provides all useful methods for controllers, like the **render()** method we use in the index action.
961+
962+Scala objects are always single instances (singletons) and are automatically initialized the first time one of their methods is called. All their methods correspond with **static** methods in Java.
963+
964+The type signature of the index action is not visible here, it's __inferred__ from the body of the method. If we wanted to be explicit, we would write **def index(): Unit = {…}** which means: a method with no parameters and no return value (**Unit** is Scala's name for Java's **void**).
965+
966+The default index action is simple: it calls the **render()** method which tells play to render a template. Using a template is the most common way (but not the only one) to generate the HTTP response.
967+
968+Templates are simple text files that live in the **/app/views** directory. Because we didn't specify a template, the default one for this action will be used: **Application/index.html**
969+
970+To see what the template looks like, open the **/yabe/app/views/Application/index.html** file:
971+
972+bc. #{extends 'main.html' /}
973+#{set title:'Home' /}
974+
975+#{welcome /}
976+
977+The template content seems pretty light. In fact, all you see are play tags. Play tags are very close to JSP tags as defined in a taglib. It is the **#{welcome /}** tag that generates the welcome message you've seen in the browser.
978+
979+The **#{extends /}** tags tells play that this template inherits another template called **main.html**. Template inheritance is a powerful concept that allows you to create complex web pages by reusing common parts.
980+
981+Open the **/yabe/app/views/main.html** template:
982+
983+bc. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
984+
985+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
986+ <head>
987+ <title>#{get 'title' /}</title>
988+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
989+ <link rel="stylesheet" type="text/css" media="screen"
990+ href="@{'/public/stylesheets/main.css'}" />
991+ <link rel="shortcut icon" type="image/png"
992+ href="@{'/public/images/favicon.png'}" />
993+ </head>
994+ <body>
995+ #{doLayout /}
996+ </body>
997+</html>
998+
999+Do you see the **#{doLayout /}** tag? This is where the content of **Application/index.html** will be inserted.
1000+
1001+We can try to edit the controller file to see how play automatically reloads it. Open the **yabe/app/controllers/Application.scala** file in a text editor, and add a mistake by calling a non-existent method:
1002+
1003+bc. def index = reindeer()
1004+
1005+Go to the browser and refresh the page. You can see that play detected the change and tried to reload the Application controller. But because you made a mistake, you get a compilation error.
1006+
1007+!images/scguide1-3!
1008+
1009+Ok, let's correct the error, and make a real modification:
1010+
1011+bc. def index {
1012+ println("Yop")
1013+ render()
1014+}
1015+
1016+Notice that we've added curly braces now that the index action involves multiple statements and, because this method has no return value, we've removed the equals sign.
1017+
1018+This time, play has correctly reloaded the controller and replaced the old code in the JVM. Each request to the **'/'** URL will output the 'Yop' message to the console.
1019+
1020+You can remove this useless line, and now edit the **yabe/app/views/Application/index.html** template to replace the welcome message:
1021+
1022+bc. #{extends 'main.html' /}
1023+#{set title:'Home' /}
1024+
1025+<h1>A blog will be there</h1>
1026+
1027+Like for Scala code changes, just refresh the page in the browser to see the modification.
1028+
1029+p(note). We will now start to code the blog application. You can either continue to work with a text editor or open the project in a Scala IDE like Eclipse or Netbeans. If you want to set up a Scala IDE, please check "this page":scide.
1030+
1031+h2. <a>Setting up the database</a>
1032+
1033+One more thing before starting to code. For the blog engine, we will need a database. For development purposes, play comes with a standalone SQL database management system called HSQLDB. This is the best way to start a project before switching to a more robust database if needed. You can choose to have an in-memory database or a filesystem database that will keep your data between application restarts.
1034+
1035+At the beginning, we will do a lot of testing and changes in the application model. For that reason, it's better to use an in-memory database so we always start with a fresh data set.
1036+
1037+To set up the database, open the **yabe/conf/application.conf** file and uncomment this line:
1038+
1039+bc. db=mem
1040+
1041+As you see in the comments, you can easily set up any JDBC compliant database and even configure the connection pool.
1042+
1043+Now, go back to your browser and refresh the welcome page. Play will automatically start the database. Check for this line in the application logs:
1044+
1045+bc. INFO ~ Connected to jdbc:hsqldb:mem:playembed
1046+
1047+h2. <a>Using a version control system (VCS) to track changes</a>
1048+
1049+When you work on a project, it's highly recommended to store your source code in a VCS. It allows you to revert to a previous version if a change breaks something, work with multiple people and give access to all the successive versions of the application. Of course you can use any VCS to store your project, but here we will use Bazaar as an example. Bazaar is a distributed source version control system.
1050+
1051+Installing bazaar is out of the scope of this tutorial but it is very easy for any system. Once you have a working installation of bazaar, go to the yabe-with-scala directory and initialize the application versioning by typing :
1052+
1053+bc. $ bzr init
1054+
1055+When storing a play application in a VCS, it's important to exclude the **tmp/** and **logs/** directories.
1056+
1057+bc. $ bzr ignore tmp
1058+$ bzr ignore logs
1059+
1060+Now we can commit our first blog engine version :
1061+
1062+bc. $ bzr add
1063+$ bzr commit -m "YABE inital version"
1064+
1065+Version 1 is committed and we now have a solid foundation for our project.
1066+
1067+p(note). Go to the "next part":scguide2.
1068+
1069
1070=== added file 'documentation/manual/scguide2.textile'
1071--- documentation/manual/scguide2.textile 1970-01-01 00:00:00 +0000
1072+++ documentation/manual/scguide2.textile 2010-09-16 22:21:08 +0000
1073@@ -0,0 +1,409 @@
1074+h1. A first iteration for the data model (Scala version)
1075+
1076+Here we will start to write the model for our blog engine.
1077+
1078+h2. <a>Introduction to JPA</a>
1079+
1080+The model layer has a central position in a play application (and in fact in all well designed applications). It is the domain-specific representation of the information on which the application operates. As we want to create a blog engine, the model layer will certainly contain classes like User, Post and Comment.
1081+
1082+Because most model objects need to survive between application restarts, we have to save them in a persistent datastore. A common choice is to use a relational database. But because Java and Scala are object oriented languages, we will use an **'Object Relational Mapper'** to help reduce the impedance mismatch.
1083+
1084+JPA is a Java specification that defines a standard API for object relational mapping. As implementation of JPA, play uses the well-known "Hibernate":http://www.hibernate.org framework. One advantage of using JPA over the standard Hibernate API is that all the 'mapping' is declared directly in the Java objects.
1085+
1086+If you have ever used Hibernate or JPA before you will be surprised by the simplicity added by play. No need to configure anything; JPA just works out of the box with play.
1087+
1088+If you don't know JPA, you can read "some of these simple presentations":http://java.sun.com/javaee/5/docs/tutorial/doc/bnbpz.html before continuing.
1089+
1090+h2. <a>Starting with the User class</a>
1091+
1092+We will start to code the blog engine by creating the User class. Create a new file **/yabe/app/models/User.scala**, and declare a first implementation of the User class:
1093+
1094+bc. package models
1095+
1096+import java.util._
1097+import javax.persistence._
1098+
1099+import play.db.jpa._
1100+
1101+@Entity
1102+class User(
1103+ var email: String,
1104+ var password: String,
1105+ var fullname: String
1106+) extends Model {
1107+
1108+ var isAdmin: Boolean = false
1109+
1110+ def this() = this(null, null, null)
1111+}
1112+
1113+If you're new to Scala then this class definition might look a bit strange. What is happening here is that the definition of 3 fields is combined with the definition of the primary constructor. Then there is a fourth field which is set to false by default.
1114+
1115+Scala really takes care of a lot of boilerplate, here's what this code represents:
1116+
1117+* a public class
1118+* a public 3-argument constructor
1119+* a public 0-argument constructor
1120+* 4 private fields
1121+* 4 public accessors and mutators
1122+
1123+The **@Entity** annotation marks this class as a managed JPA entity, and the Model superclass automatically provides a set of useful JPA helpers that we will discover later. All fields of this class will be automatically persisted to the database.
1124+
1125+p(note). It's not required that your model objects extend the **play.db.jpa.Model** class. You can work as plain JPA as well. But extending this class is a good choice in most cases as it will ease a lot the JPA stuff.
1126+
1127+If you have already used JPA before, you know that every JPA entity must provide an **@Id** property. Here the Model superclass provides an automatically generated numeric id, in most cases this is good enough.
1128+
1129+p(note). Don't think about this provided **id** field as a **functional identifier** but as a **technical identifier**. It is generally a good idea to keep both concepts separated and to keep an automatically generated numeric id as technical identifier.
1130+
1131+You can now refresh the application homepage, to see the result. In fact, unless you made a mistake, you should not see any change: play has automatically compiled and loaded the User class, but this does not provide any feature to the application.
1132+
1133+h2. <a>Writing the first test</a>
1134+
1135+A good way to test the newly created User class is to write a JUnit test case. It will allow to incrementally complete the application model and ensure that all is fine.
1136+
1137+To run a test case, you need to start the application in a special 'test' mode. Stop the currently running application, open a command line and type:
1138+
1139+bc. ~$ play test
1140+
1141+!images/guide2-0!
1142+
1143+The **'play test'** command is almost the same than **'play run'**, except that it loads a test runner module that allows to run test suite directly from a browser.
1144+
1145+p(note). When you run a play application in **test mode**, play will automatically switch to the **test** framework id and load the **application.conf** file accordingly. Check "this page":ids for more informations.
1146+
1147+Open a browser to the "http://localhost:9000/@tests":http://localhost:9000/@tests URL to see the test runner. Try to select all the default tests and run them; all should be green... But these default tests don't really test anything.
1148+
1149+!images/guide2-1!
1150+
1151+To test the model part of the application we will use a JUnit test. As you see a default BasicTests.java already exists, so let's open it (**/yabe/test/BasicTest.scala**):
1152+
1153+bc. import org.junit._
1154+import org.junit.Assert._
1155+import play.test._
1156+import play.db.jpa.QueryFunctions._
1157+import models._
1158+
1159+class BasicTest extends UnitTest {
1160+
1161+ @Test
1162+ def aVeryImportantThingToTest() {
1163+
1164+ assertEquals(2, 1 + 1)
1165+ }
1166+
1167+}
1168+
1169+
1170+Remove the useless default test (aVeryImportantThingToTest) and create a test that tries to create a new user and retrieve it:
1171+
1172+bc. @Test
1173+def createAndRetrieveUser() {
1174+ // Create a new user and save it
1175+ new User("bob@gmail.com", "secret", "Bob").save()
1176+
1177+ // Retrieve the user with bob's email address
1178+ val bob = find[User]("byEmail", "bob@gmail.com").first
1179+
1180+ // Test
1181+ assertNotNull(bob)
1182+ assertEquals("Bob", bob.fullname)
1183+}
1184+
1185+
1186+As you can see, the Model superclass gives us the very useful **save()** method and the QueryFunctions import gives us the **find** function.
1187+Select **BasicTests** in the test runner, click start and check that all is green.
1188+
1189+We will need a method on User that checks if a user with a specified username and password exists. But in Scala, methods that don't act on a specific instance need to be defined in a companion object. Let's write it and test it.
1190+
1191+In the **User.scala** source, add an import statement (either at the top or just before the new code), the User **object** definition and the **connect()** method:
1192+
1193+bc. import play.db.jpa.QueryFunctions._
1194+
1195+object User {
1196+ def connect(email: String, password: String) = {
1197+ find[User]("byEmailAndPassword", email, password).first
1198+ }
1199+}
1200+
1201+And now the test case:
1202+
1203+bc. @Test
1204+def tryConnectAsUser() {
1205+ // Create a new user and save it
1206+ new User("bob@gmail.com", "secret", "Bob").save()
1207+
1208+ // Test
1209+ assertNotNull(User.connect("bob@gmail.com", "secret"))
1210+ assertNull(User.connect("bob@gmail.com", "badpassword"))
1211+ assertNull(User.connect("tom@gmail.com", "secret"))
1212+}
1213+
1214+Each time you make a modification you can run all the tests from the play test runner to make sure you didn't break anything.
1215+
1216+h2. <a>The Post class</a>
1217+
1218+The Post class will represent blog posts. Let's write a first implementation:
1219+
1220+bc. package models
1221+
1222+import java.util._
1223+import javax.persistence._
1224+import play.db.jpa._
1225+
1226+@Entity
1227+class Post(
1228+ @ManyToOne
1229+ var author: User,
1230+ var title: String,
1231+ @Lob
1232+ var content: String
1233+) extends Model {
1234+
1235+ var postedAt: Date = new Date()
1236+
1237+ def this() = this(null, null, null)
1238+}
1239+
1240+Here we use the **@Lob** annotation to tell JPA to use a large text database type to store the post content. We have declared the relation to the User class using **@ManyToOne**. That means that each Post is authored by a single User, and that each User can author several Posts.
1241+
1242+We will write a new test case to check that the Post class works as expected. But before to write more tests, we need to do something in the JUnit class. In the current test, the database content is never deleted, each new run creating more and more objects. It will become problematic soon when more advanced test will start to count objects to check that all is fine.
1243+
1244+So let's write a JUnit **setup()** method to delete the database before each test:
1245+
1246+bc. public class BasicTest extends UnitTest {
1247+
1248+ @Before
1249+ def setup() {
1250+ Fixtures.deleteAll()
1251+ }
1252+
1253+ ...
1254+
1255+}
1256+
1257+p(note). The **@Before** concept is a core concept of the JUnit testing tool.
1258+
1259+As you can see, the Fixtures class is a helper to deal with your database during tests. Run the test again to check that you don't have broken anything, and start to write the next test:
1260+
1261+bc. @Test
1262+def createPost() {
1263+ // Create a new user and save it
1264+ val bob: User = new User("bob@gmail.com", "secret", "Bob").save()
1265+
1266+ // Create a new post
1267+ new Post(bob, "My first post", "Hello world").save()
1268+
1269+ // Test that the post has been created
1270+ assertEquals(1L, count[Post])
1271+
1272+ // Retrieve all post created by bob
1273+ val bobPosts = find[Post]("byAuthor", bob).fetch
1274+
1275+ // Tests
1276+ assertEquals(1, bobPosts.length)
1277+ val firstPost = bobPosts.head
1278+ assertNotNull(firstPost)
1279+ assertEquals(bob, firstPost.author)
1280+ assertEquals("My first post", firstPost.title)
1281+ assertEquals("Hello world", firstPost.content)
1282+ assertNotNull(firstPost.postedAt)
1283+}
1284+
1285+
1286+h2. <a>Finish with Comment</a>
1287+
1288+The last thing that we need to add at this first model draft is the ability to attach comments to posts.
1289+
1290+The creation of the Comment class is pretty straightforward.
1291+
1292+bc. package models
1293+
1294+import java.util._
1295+import javax.persistence._
1296+import play.db.jpa._
1297+
1298+@Entity
1299+class Comment(
1300+ @ManyToOne
1301+ var post: Post,
1302+ var author: String,
1303+ @Lob
1304+ var content: String
1305+) extends Model {
1306+
1307+ var postedAt: Date = new Date()
1308+
1309+ def this() = this(null, null, null)
1310+}
1311+
1312+Let's write a first test case:
1313+
1314+bc. @Test
1315+def postComments() {
1316+ // Create a new user and save it
1317+ val bob: User = new User("bob@gmail.com", "secret", "Bob").save()
1318+
1319+ // Create a new post
1320+ val bobPost: Post = new Post(bob, "My first post", "Hello world").save()
1321+
1322+ // Post a first comment
1323+ new Comment(bobPost, "Jeff", "Nice post").save()
1324+ new Comment(bobPost, "Tom", "I knew that !").save()
1325+
1326+ // Retrieve all comments
1327+ val bobPostComments = find[Comment]("byPost", bobPost).fetch
1328+
1329+ // Tests
1330+ assertEquals(2, bobPostComments.length)
1331+
1332+ val firstComment = bobPostComments(0)
1333+ assertNotNull(firstComment)
1334+ assertEquals("Jeff", firstComment.author)
1335+ assertEquals("Nice post", firstComment.content)
1336+ assertNotNull(firstComment.postedAt)
1337+
1338+ val secondComment = bobPostComments(1)
1339+ assertNotNull(secondComment)
1340+ assertEquals("Tom", secondComment.author)
1341+ assertEquals("I knew that !", secondComment.content)
1342+ assertNotNull(secondComment.postedAt)
1343+}
1344+
1345+You see that the navigation between Post and Comments is not very easy: we need to use a query to retrieve all comments attached to Post. We can do better by setting up the other side of the relationship to the Post class.
1346+
1347+Add the comments field to the Post class:
1348+
1349+bc. ...
1350+ @OneToMany(mappedBy="post", cascade=Array(CascadeType.ALL))
1351+ var comments: List[Comment] = new ArrayList[Comment]
1352+...
1353+
1354+Note how we have used the **mappedBy** attribute to tell JPA that the Post class maintains the relationship. When you define bi-directional relation with JPA it is very important to tell which side will maintain the relationship. In this case, since the Comments belong to the Post it's better that the Comment class maintains the relationship.
1355+
1356+We have set the **casacade** property to tell JPA that we want that the Post deletion be cascaded to comments. This way, if you delete a post, all related comments will be deleted as well.
1357+
1358+With this new relationship, we will add a helper method to the Post class to simplify adding comments:
1359+
1360+bc. def addComment(author: String, content: String): Post = {
1361+ val newComment: Comment = new Comment(this, author, content).save()
1362+ this.comments.add(newComment)
1363+ return this
1364+}
1365+
1366+Let's write another test case to check that:
1367+
1368+bc. @Test
1369+def useTheCommentsRelation() {
1370+ // Create a new user and save it
1371+ val bob: User = new User("bob@gmail.com", "secret", "Bob").save()
1372+
1373+ // Create a new post
1374+ var bobPost: Post = new Post(bob, "My first post", "Hello world").save()
1375+
1376+ // Post a first comment
1377+ bobPost.addComment("Jeff", "Nice post")
1378+ bobPost.addComment("Tom", "I knew that !")
1379+
1380+ // Count things
1381+ assertEquals(1L, count[User])
1382+ assertEquals(1L, count[Post])
1383+ assertEquals(2L, count[Comment])
1384+
1385+ // Retrieve Bob's post
1386+ bobPost = find[Post]("byAuthor", bob).first
1387+ assertNotNull(bobPost)
1388+
1389+ // Navigate to comments
1390+ assertEquals(2, bobPost.comments.size())
1391+ assertEquals("Jeff", bobPost.comments.get(0).author)
1392+
1393+ // Delete the post
1394+ bobPost.delete();
1395+
1396+ // Check the all comments have been deleted
1397+ assertEquals(1L, count[User])
1398+ assertEquals(0L, count[Post])
1399+ assertEquals(0L, count[Comment])
1400+}
1401+
1402+Is it green?
1403+
1404+!images/guide2-2!
1405+
1406+h2. <a>Using Fixtures to write more complicated test</a>
1407+
1408+When you start to write more complex tests, you often need a set of data to test on. Fixtures lets you describe your model in a YAML file and load it at any time before a test.
1409+
1410+Edit the **/yabe/test/data.yml** file and start to describe a User:
1411+
1412+bc.
1413+User(bob):
1414+ email: bob@gmail.com
1415+ password: secret
1416+ fullname: Bob
1417+
1418+...
1419+
1420+
1421+Well, because the data.yml file is a litle big, you can "download it here":files/data.yml.
1422+
1423+Now we create create a test case that loads this data and run some assertions over it:
1424+
1425+bc. @Test
1426+def fullTest() {
1427+ Fixtures.load("data.yml")
1428+
1429+ // Count things
1430+ assertEquals(2L, count[User])
1431+ assertEquals(3L, count[Post])
1432+ assertEquals(3L, count[Comment])
1433+
1434+ // Try to connect as users
1435+ assertNotNull(User.connect("bob@gmail.com", "secret"))
1436+ assertNotNull(User.connect("jeff@gmail.com", "secret"))
1437+ assertNull(User.connect("jeff@gmail.com", "badpassword"))
1438+ assertNull(User.connect("tom@gmail.com", "secret"))
1439+
1440+ // Find all bob posts
1441+ val bobPosts = find[Post]("author.email", "bob@gmail.com").fetch
1442+ assertEquals(2, bobPosts.length)
1443+
1444+ // Find all comments related to bob posts
1445+ val bobComments = find[Comment]("post.author.email", "bob@gmail.com").fetch
1446+ assertEquals(3, bobComments.length)
1447+
1448+ // Find the most recent post
1449+ val frontPost = find[Post]("order by postedAt desc").first
1450+ assertNotNull(frontPost)
1451+ assertEquals("About the model layer", frontPost.title)
1452+
1453+ // Check that this post has two comments
1454+ assertEquals(2, frontPost.comments.size())
1455+
1456+ // Post a new comment
1457+ frontPost.addComment("Jim", "Hello guys")
1458+ assertEquals(3, frontPost.comments.size())
1459+ assertEquals(4L, count[Comment])
1460+}
1461+
1462+h2. <a>Save your work</a>
1463+
1464+We have now finished an huge part on the blog engine. With all this things created and tested, we can now start to develop the web application itself.
1465+
1466+But before continuing, it's time to save your work in bazaar. Open a command line an type **bzr st** to see the modifications made since the latest commit:
1467+
1468+bc. $ bzr st
1469+
1470+As you can see, some new files are not under version control. The **test-result** folder doesn't need to be versioned, so let's ignore it.
1471+
1472+bc. $ bzr ignore test-result
1473+
1474+Add other files to version control using **bzr add**.
1475+
1476+bc. $ bzr add
1477+
1478+You can now commit your project.
1479+
1480+bc. $ bzr commit -m "The model layer is ready"
1481+
1482+p(note). Go to the "next part":scguide3.
1483
1484=== added file 'documentation/manual/scguide3.textile'
1485--- documentation/manual/scguide3.textile 1970-01-01 00:00:00 +0000
1486+++ documentation/manual/scguide3.textile 2010-09-16 22:21:08 +0000
1487@@ -0,0 +1,329 @@
1488+h1. Building the first screen (Scala version)
1489+
1490+Now that we have built a first data model, it's time to start to create the first page of the application. This page will just show the most recent posts, as well as a list of older posts.
1491+
1492+Here is a mock of what we want to achieve:
1493+
1494+!images/guide-mock1!
1495+
1496+h2. <a>Bootstrapping with default data</a>
1497+
1498+In fact before coding the first screen we need one more thing. Working on a web application without test data is not fun. You can't even test what you're doing. But because we haven't developed the contribution screens yet, we can't populate the blog with posts ourselves.
1499+
1500+One way to inject default data into the blog is to load a fixture file at application load time. To do that we will create a Bootstrap Job. A play job is something that executes itself outside of any HTTP request, for example at the application start or at specific interval using a CRON job.
1501+
1502+Let's create the **/yabe/app/Bootstrap.java** job that will load a set of default data using fixture:
1503+
1504+bc. import play._
1505+import play.jobs._
1506+import play.test._
1507+import play.db.jpa.QueryFunctions._
1508+
1509+import models._
1510+
1511+@OnApplicationStart
1512+class Bootstrap extends Job {
1513+
1514+ override def doJob() {
1515+ // Check if the database is empty
1516+ if(count[User] == 0) {
1517+ Fixtures.load("initial-data.yml")
1518+ }
1519+ }
1520+
1521+}
1522+
1523+We have annotated this Job with the **@OnApplicationStart** annotation to tell play that we want to run this jobs synchronously at the application start.
1524+
1525+p(note). In fact this job will be run differently in DEV or PROD modes. In DEV mode, play waits for a first request to start. So this job will be executed synchronously at the first request. This way, if the job fail, you will get the error message in your browser. In PROD mode however, the job will be executed at application start (synchrously with the **'play run'** command) and will prevent the application to start in case of error.
1526+
1527+You have to create a initial-data.yml in the **/yabe/conf** directory. You can of course reuse the **data.yml** content that we just used for tests previously.
1528+
1529+Now run the application using **play run** and display the "http://localhost:9000":http://localhost:9000 page in the browser.
1530+
1531+h2. <a>The blog home page</a>
1532+
1533+This time, we can really start to code the home page.
1534+
1535+Do you remember how the first page is displayed? First the routes file defines that the **/** URL will invoke the **controllers.Application.index()** action method. Then this method calls **render()** and execute the **/yabe/app/views/Application/index.html** template.
1536+
1537+We will keep these components but add code to them to load the posts list and display them.
1538+
1539+Open the **/yabe/app/controllers/Application.java** controller and modify the **index()** action to load the posts list, as is:
1540+
1541+bc. package controllers
1542+
1543+import play._
1544+import play.mvc._
1545+import play.db.jpa.QueryFunctions._
1546+
1547+import models._
1548+
1549+object Application extends Actions {
1550+
1551+ def index() {
1552+ val frontPost = find[Post]("order by postedAt desc").first
1553+ val olderPosts = find[Post](
1554+ "order by postedAt desc"
1555+ ).from(1).fetch(10)
1556+
1557+ render(frontPost, olderPosts)
1558+ }
1559+
1560+}
1561+
1562+Can you see how we pass objects to the render method? It will allow us to access them from the template using the same name.
1563+
1564+Open the **/yabe/app/views/Application/index.html** and modify it to display these objects:
1565+
1566+bc. #{extends 'main.html' /}
1567+#{set title:'Home' /}
1568+
1569+#{if frontPost}
1570+ <div class="post">
1571+ <h2 class="post-title">
1572+ <a href="#">${frontPost.title}</a>
1573+ </h2>
1574+ <div class="post-metadata">
1575+ <span class="post-author">by ${frontPost.author.fullname}</span>
1576+ <span class="post-date">${frontPost.postedAt.format('MMM dd')}</span>
1577+ <span class="post-comments">
1578+ &nbsp;|&nbsp;
1579+ ${frontPost.comments.size() ?: 'no'}
1580+ comment${frontPost.comments.size().pluralize()}</a>
1581+ #{if frontPost.comments}
1582+ , latest by ${frontPost.comments[0].author}
1583+ #{/if}
1584+ </span>
1585+ </div>
1586+ <div class="post-content">
1587+ ${frontPost.content.nl2br()}
1588+ </div>
1589+ </div>
1590+
1591+ #{if olderPosts.size() > 1}
1592+ <div class="older-posts">
1593+ <h3>Older posts <span class="from">from this blog</span></h3>
1594+
1595+ #{list items:olderPosts, as:'oldPost'}
1596+ <div class="post">
1597+ <h2 class="post-title">
1598+ <a href="#">${oldPost.title}</a>
1599+ </h2>
1600+ <div class="post-metadata">
1601+ <span class="post-author">
1602+ by ${oldPost.author.fullname}
1603+ </span>
1604+ <span class="post-date">
1605+ ${oldPost.postedAt.format('dd MMM yy')}
1606+ </span>
1607+ <div class="post-comments">
1608+ ${oldPost.comments.size() ?: 'no'}
1609+ comment${oldPost.comments.size().pluralize()}
1610+ #{if oldPost.comments}
1611+ - latest by ${oldPost.comments[0].author}
1612+ #{/if}
1613+ </div>
1614+ </div>
1615+ </div>
1616+ #{/list}
1617+ </div>
1618+
1619+ #{/if}
1620+
1621+#{/if}
1622+
1623+#{else}
1624+ <div class="empty">
1625+ There is currently nothing to read here.
1626+ </div>
1627+#{/else}
1628+
1629+You can read more about the "template language here":templates. Basically, it allows you to access your java objects dynamically. Under the hood we use Groovy. Most of the pretty constructs you see (like the ?: operator) come from Groovy. But you don't really need to learn groovy to write play templates. If you're already familiar with another template language like JSP with JSTL you won't be lost.
1630+
1631+OK, now refresh the blog home page.
1632+
1633+!images/guide3-0!
1634+
1635+Not pretty but it works!
1636+
1637+However you see you have already started to duplicate code. Because we will display posts in several fashions (full, full with comment, teaser) we should create something like a function that we could call from several templates. This is exactly what a play tag does!
1638+
1639+To create a tag, just create the new **/yabe/app/views/tags/display.html** file. A tag is just another template. It has parameters (like a function). The **#{display /}** tag will have 2 parameters: the Post object to display and the display mode which will be one of 'home', 'teaser' or 'full'.
1640+
1641+bc. *{ Display a post in one of these modes: 'full', 'home' or 'teaser' }*
1642+
1643+<div class="post ${_as == 'teaser' ? 'teaser' : ''}">
1644+ <h2 class="post-title">
1645+ <a href="#">${_post.title}</a>
1646+ </h2>
1647+ <div class="post-metadata">
1648+ <span class="post-author">by ${_post.author.fullname}</span>,
1649+ <span class="post-date">${_post.postedAt.format('dd MMM yy')}</span>
1650+ #{if _as != 'full'}
1651+ <span class="post-comments">
1652+ &nbsp;|&nbsp; ${_post.comments.size() ?: 'no'}
1653+ comment${_post.comments.size().pluralize()}
1654+ #{if _post.comments}
1655+ , latest by ${_post.comments[0].author}
1656+ #{/if}
1657+ </span>
1658+ #{/if}
1659+ </div>
1660+ #{if _as != 'teaser'}
1661+ <div class="post-content">
1662+ <div class="about">Detail: </div>
1663+ ${_post.content.nl2br()}
1664+ </div>
1665+ #{/if}
1666+</div>
1667+
1668+#{if _as == 'full'}
1669+ <div class="comments">
1670+ <h3>
1671+ ${_post.comments.size() ?: 'no'}
1672+ comment${_post.comments.size().pluralize()}
1673+ </h3>
1674+
1675+ #{list items:_post.comments, as:'comment'}
1676+ <div class="comment">
1677+ <div class="comment-metadata">
1678+ <span class="comment-author">by ${comment.author},</span>
1679+ <span class="comment-date">
1680+ ${comment.postedAt.format('dd MMM yy')}
1681+ </span>
1682+ </div>
1683+ <div class="comment-content">
1684+ <div class="about">Detail: </div>
1685+ ${comment.content.escape().nl2br()}
1686+ </div>
1687+ </div>
1688+ #{/list}
1689+
1690+ </div>
1691+#{/if}
1692+
1693+Now using this tag we can rewrite the home page without code duplication :
1694+
1695+bc. #{extends 'main.html' /}
1696+#{set title:'Home' /}
1697+
1698+#{if frontPost}
1699+
1700+ #{display post:frontPost, as:'home' /}
1701+
1702+ #{if olderPosts.size()}
1703+
1704+ <div class="older-posts">
1705+ <h3>Older posts <span class="from">from this blog</span></h3>
1706+
1707+ #{list items:olderPosts, as:'oldPost'}
1708+ #{display post:oldPost, as:'teaser' /}
1709+ #{/list}
1710+ </div>
1711+
1712+ #{/if}
1713+
1714+#{/if}
1715+
1716+#{else}
1717+ <div class="empty">
1718+ There is currently nothing to read here.
1719+ </div>
1720+#{/else}
1721+
1722+Reload the page and check that all is fine.
1723+
1724+h2. <a>Improving the layout</a>
1725+
1726+As you can see, the **index.html** template extends **main.html**. Because we want to provide a common layout to all blog pages with the blog title and authentication links, we need to modify this file.
1727+
1728+Edit the **/yabe/app/views/main.html** file :
1729+
1730+bc. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1731+
1732+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
1733+ <head>
1734+ <title>#{get 'title' /}</title>
1735+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1736+ <link rel="stylesheet" type="text/css" media="screen"
1737+ href="@{'/public/stylesheets/main.css'}" />
1738+ <link rel="shortcut icon" type="image/png"
1739+ href="@{'/public/images/favicon.png'}" />
1740+ </head>
1741+ <body>
1742+
1743+ <div id="header">
1744+ <div id="logo">
1745+ yabe.
1746+ </div>
1747+ <ul id="tools">
1748+ <li>
1749+ <a href="#">Log in to write something</a>
1750+ </li>
1751+ </ul>
1752+ <div id="title">
1753+ <span class="about">About this blog</span>
1754+ <h1><a href="#">${blogTitle}</a></h1>
1755+ <h2>${blogBaseline}</h2>
1756+ </div>
1757+ </div>
1758+
1759+ <div id="main">
1760+ #{doLayout /}
1761+ </div>
1762+
1763+ <p id="footer">
1764+ Yabe is a (not that) powerful blog engine built with the
1765+ <a href="http://www.playframework.org">play framework</a>
1766+ as a tutorial application.
1767+ </p>
1768+
1769+ </body>
1770+</html>
1771+
1772+Refresh and check the result. It seems to work, except that the **blogTitle** and the **blogBaseLine** variables are not displayed. This is because we didn't pass them during the **render(...)** call. Of course we could add them to the **render()** call in the index action. But because the **main.html** file will be used as main template for all application actions, we don't want to add them every time.
1773+
1774+One way to execute the same code for each action of a controller (or a hierarchy of controllers) is to define a **@Before** interceptor.
1775+
1776+Let's add the **addDefaults()** method to the Application controller :
1777+
1778+bc. @Before
1779+def addDefaults() {
1780+ renderArgs.put("blogTitle", Play.configuration.getProperty("blog.title"))
1781+ renderArgs.put("blogBaseline", Play.configuration.getProperty("blog.baseline"))
1782+}
1783+
1784+All variables added to the **renderArgs** scope will be available from the templates. And you can see that the method reads the variable's values from the **Play.configuration** object. This object contains all configuration keys from the **/yabe/conf/application.conf** file.
1785+
1786+Add these 2 keys to the configuration file:
1787+
1788+bc. # Configuration of the blog engine
1789+# ~~~~~
1790+blog.title=Yet another blog
1791+blog.baseline=We won't write about anything
1792+
1793+Reload the home page and check that it works!
1794+
1795+!images/guide3-1!
1796+
1797+h2. <a>Adding some style</a>
1798+
1799+Now the blog home page is almost done, but it's not really pretty. We'll add some style to make it shinier. As you have seen, the main template file main.html includes the **/public/stylesheets/main.css** stylesheet. We'll keep it but add more style rules to it.
1800+
1801+You can "download it here":files/main.css, and copy it to the **/public/stylsheets/main.css** file.
1802+
1803+Refresh the home page and you should see a styled page now.
1804+
1805+!images/guide3-2!
1806+
1807+h2. <a>Commit your work</a>
1808+
1809+This time the blog home page is finished. As usual we can commit this blog version to bazaar:
1810+
1811+bc. $ bzr st
1812+$ bzr add
1813+$ bzr commit -m 'Home page'
1814+
1815+p(note). Go to the "next part":scguide4.
1816+
1817
1818=== added file 'documentation/manual/scguide4.textile'
1819--- documentation/manual/scguide4.textile 1970-01-01 00:00:00 +0000
1820+++ documentation/manual/scguide4.textile 2010-09-16 22:21:08 +0000
1821@@ -0,0 +1,245 @@
1822+h1. Viewing and posting comments (Scala version)
1823+
1824+The blog home page is now set, and we will continue by writing the post details page. This page will show all the comments about the current post, and will include a form to post new comments.
1825+
1826+h2. <a>Creating the 'show' action</a>
1827+
1828+To display the post details page, we will need a new action method to the Application controller. Let's call it **show()**:
1829+
1830+bc. def show(id: Long) {
1831+ val post = findById[Post](id)
1832+ render(post)
1833+}
1834+
1835+As you can see this action is pretty simple. We declare the **id** method parameter to automatically retrieve the HTTP **'id'** parameter as a Long object. This parameter will be extracted either from the queryString, from the URL path or from the request body.
1836+
1837+p(note). If we try to send an **id** HTTP parameter that is not a valid number, the id variable value will be null and play will automatically add a validation error to the **errors** container.
1838+
1839+This action will display the **/yabe/app/views/Application/show.html** template :
1840+
1841+bc. #{extends 'main.html' /}
1842+#{set title:post.title /}
1843+
1844+#{display post:post, as:'full' /}
1845+
1846+Because we've already written the display tag, this page is really simple to write.
1847+
1848+h2. <a>Adding links to the details page</a>
1849+
1850+In the display tag we've left all links empty (using #). It's now time to make these links pointing to the **Application.show** action. With play you can easily build links in a template using the **@{...} notation**. This syntax uses the router to 'reverse' the URL needed to call the specified action.
1851+
1852+Let's edit the **/yabe/app/views/tags/display.html** tag :
1853+
1854+bc. ...
1855+<h2 class="post-title">
1856+ <a href="@{Application.show(_post.id)}">${_post.title}</a>
1857+</h2>
1858+...
1859+
1860+You can new refresh the home page, and click on a post title to display it.
1861+
1862+!images/guide4-0!
1863+
1864+It's great, but it lacks a link to go back to the home page. Edit the **/yabe/app/views/main.html** template to complete the title link:
1865+
1866+bc. ...
1867+<div id="title">
1868+ <span class="about">About this blog</span>
1869+ <h1><a href="@{Application.index()}">${blogTitle}</a></h1>
1870+ <h2>${blogBaseline}</h2>
1871+</div>
1872+...
1873+
1874+We can now navigate between the home page and the post detail pages.
1875+
1876+h2. <a>Specifying a better URL</a>
1877+
1878+As you can see, the post detail page URL looks like
1879+
1880+bc. /application/show?id=1
1881+
1882+This is because play has used the default 'catch all' route.
1883+
1884+bc. * /{controller}/{action} {controller}.{action}
1885+
1886+We can have a better URL by specifying a custom path for the **Application.show** action. Edit the **/yabe/conf/routes** file and add this route after the first one:
1887+
1888+bc. GET /posts/{id} Application.show
1889+
1890+p(note). This way the id parameter will be extracted from the URL path.
1891+
1892+Refresh the browser and check that it now uses the correct URL.
1893+
1894+h2. <a>Adding some pagination</a>
1895+
1896+To allow users to navigate easily through posts, we will add a pagination mechanism. We'll extend the Post class to be able to fetch the previous and next post as required:
1897+
1898+bc. import play.db.jpa.QueryFunctions._
1899+...
1900+def previous(): Post = {
1901+ find[Post]("postedAt < ? order by postedAt desc", postedAt).first
1902+}
1903+
1904+def next(): Post = {
1905+ find[Post]("postedAt > ? order by postedAt asc", postedAt).first
1906+}
1907+
1908+Since we will call these methods several times during a request it could be optimized, but it's good enough for now. Also, add the pagination links in top of the show.html template (before the **#{display/}** tag):
1909+
1910+bc. <ul id="pagination">
1911+ #{if post.previous()}
1912+ <li id="previous">
1913+ <a href="@{Application.show(post.previous().id)}">
1914+ ${post.previous().title}
1915+ </a>
1916+ </li>
1917+ #{/if}
1918+ #{if post.next()}
1919+ <li id="next">
1920+ <a href="@{Application.show(post.next().id)}">
1921+ ${post.next().title}
1922+ </a>
1923+ </li>
1924+ #{/if}
1925+</ul>
1926+
1927+It's better now.
1928+
1929+h2. <a>Adding the comment form</a>
1930+
1931+Now it's time to set up a comments form. We'll start by adding the **postComment** action method to the Application controller.
1932+
1933+bc. def postComment(postId: Long, author: String, content: String) {
1934+ val post = findById[Post](postId)
1935+ post.addComment(author, content)
1936+ show(postId)
1937+}
1938+
1939+As you see we just reuse the **addComment()** method we previously added to the Post class.
1940+
1941+Let's write the HTML form in the show.html template (after the **#{display /}** tag in fact):
1942+
1943+bc. <h3>Post a comment</h3>
1944+
1945+#{form @Application.postComment(post.id)}
1946+ <p>
1947+ <label for="author">Your name: </label>
1948+ <input type="text" name="author" id="author" />
1949+ </p>
1950+ <p>
1951+ <label for="content">Your message: </label>
1952+ <textarea name="content" id="content"></textarea>
1953+ </p>
1954+ <p>
1955+ <input type="submit" value="Submit your comment" />
1956+ </p>
1957+#{/form}
1958+
1959+You can try to post a new comment. It should just work.
1960+
1961+!images/guide4-1!
1962+
1963+h2. <a>Adding validation</a>
1964+
1965+Currently we don't validate the form content before creating the comment. We would like to set both fields as required. We can easily use the play validation mechanism to ensure that HTTP parameters are correctly filled. Modify the **postComment** action to add validation annotations and check that no error occurs:
1966+
1967+bc. def postComment(postId: Long, @Required author: String, @Required content: String) {
1968+ val post = findById[Post](postId)
1969+ if (Validation.hasErrors()) {
1970+ render("Application/show.html", post)
1971+ }
1972+ post.addComment(author, content)
1973+ show(postId)
1974+}
1975+
1976+p(note). **Don't forget** to import play.data.validation._ as well.
1977+
1978+As you see, in case of validation errors, we re-display the post detail page. We have to modify the form code to display the error message:
1979+
1980+bc. <h3>Post a comment</h3>
1981+
1982+#{form @Application.postComment(post.id)}
1983+
1984+ #{ifErrors}
1985+ <p class="error">
1986+ All fields are required!
1987+ </p>
1988+ #{/ifErrors}
1989+
1990+ <p>
1991+ <label for="author">Your name: </label>
1992+ <input type="text" name="author" id="author" value="${params.author}" />
1993+ </p>
1994+ <p>
1995+ <label for="content">Your message: </label>
1996+ <textarea name="content" id="content">${params.content}</textarea>
1997+ </p>
1998+ <p>
1999+ <input type="submit" value="Submit your comment" />
2000+ </p>
2001+#{/form}
2002+
2003+Note that we reuse the posted parameters to fill the HTML input values.
2004+
2005+To make the UI feedback more pleasant for the poster, we will add a little Javascript to automatically focus the comment form in case of an error. As this script uses "JQuery":files/jquery-1.3.2.min.js and "JQuery Tools":files/jquery.tools.min.js as support libraries, you have to include them. Download these 2 libraries to the **/yabe/public/javascripts** directory and modify the **main.html** template to include them:
2006+
2007+bc. ...
2008+ <script src="@{'/public/javascripts/jquery-1.3.2.min.js'}"></script>
2009+ <script src="@{'/public/javascripts/jquery.tools.min.js'}"></script>
2010+</head>
2011+
2012+Now you can add this script to the **show.html** template (add it at the end of the page):
2013+
2014+bc. <script type="text/javascript" charset="utf-8">
2015+ $(function() {
2016+ // Expose the form
2017+ $('form').click(function() {
2018+ $('form').expose({api: true}).load();
2019+ });
2020+
2021+ // If there is an error, focus to form
2022+ if($('form .error').size()) {
2023+ $('form').expose({api: true, loadSpeed: 0}).load();
2024+ $('form input').get(0).focus();
2025+ }
2026+ });
2027+</script>
2028+
2029+!images/guide4-2!
2030+
2031+The comment form looks pretty cool now. We will add two more things.
2032+
2033+First we will display a success message after a comment is successfully posted. For that, we use the flash scope that allows to dispatch messages from one action call to the next one.
2034+
2035+Modify the **postComment** action to add a success message:
2036+
2037+bc. public static void postComment(Long postId, @Required String author, @Required String content) {
2038+ Post post = Post.findById(postId);
2039+ if(validation.hasErrors()) {
2040+ render("Application/show.html", post);
2041+ }
2042+ post.addComment(author, content);
2043+ flash.success("Thanks for posting, %s", author);
2044+ show(postId);
2045+}
2046+
2047+and display the success message in the **show.html** if present (add it at the top the page):
2048+
2049+bc. ...
2050+#{if flash.success}
2051+ <p class="success">${flash.success}</p>
2052+#{/if}
2053+
2054+#{display post:post, as:'full' /}
2055+...
2056+
2057+!images/guide4-3!
2058+
2059+The last thing we will adjust in this form is the URL used for the **postComment** action. As always it uses the default catch all route because we didn't define any specific route. So add this route to the application routes file:
2060+
2061+bc. POST /posts/{postId}/comments Application.postComment
2062+
2063+That's done. As always, commit the version to bazaar.
2064+
2065+p(note). Go to the "next part":scguide5.
2066+
2067
2068=== added file 'documentation/manual/scguide5.textile'
2069--- documentation/manual/scguide5.textile 1970-01-01 00:00:00 +0000
2070+++ documentation/manual/scguide5.textile 2010-09-16 22:21:08 +0000
2071@@ -0,0 +1,137 @@
2072+h1. Setting up a captcha (Scala version)
2073+
2074+Because anyone can post a comment to our blog engine, we should protect it a little to avoid automated spam. A simple way to protect a form from this is to add a captcha image.
2075+
2076+h2. <a>Generating the captcha image</a>
2077+
2078+We'll start to see how we can easily generate a captcha image using play. Basically we will just use another action, except that it will return a binary stream instead of HTML responses like what we've returned so far.
2079+
2080+Play being a **full-stack** web framework, we try to include built-in constructs for a web application's most typical needs; generating a captcha is one of them. We can use the **play.libs.Images** utility to simply generate a captcha image, and then write it to the HTTP response.
2081+
2082+As usual, we will start with a simple implementation. Add the **captcha** action to the **Application** controller:
2083+
2084+bc. def captcha() {
2085+ val captcha = Images.captcha()
2086+ renderBinary(captcha)
2087+}
2088+
2089+Note that we can pass the captcha object directly to the renderBinary() method because the Images.Captcha class implements java.io.InputStream.
2090+
2091+p(note). **Don't forget** to import play.libs._
2092+
2093+Now add a new route to the **/yabe/conf/routes** file:
2094+
2095+bc. GET /captcha Application.captcha
2096+
2097+And try the **captcha** action by opening "http://localhost:9000/captcha":http://localhost:9000/captcha.
2098+
2099+!images/guide5-1!
2100+
2101+It should generate a random text for each refresh.
2102+
2103+h2. <a>How to manage the state?</a>
2104+
2105+Until now it was easy, but the most complicated part is coming. To validate the captcha we need to save the random text written to the captcha image and then check it at the form submission time.
2106+
2107+Of course we could just put the text in the user session at image generation time and then retrieve it later. But this solution has two drawbacks:
2108+
2109+**First** the play session is stored as a cookie. It solves a lot of problems in terms of architecture but has a lot of implications. Data written to the session cookie are signed (so the user can't modify them) but not encrypted. If we write the captcha code to the session any bot could easily resolve it by reading the session cookie.
2110+
2111+**Then** remember that play is a **stateless** framework. We want to manage things in a pure stateless way. Typically, what happens if a user simultaneously opens two different blog pages with two different captcha images? We have to track the captcha code for each form.
2112+
2113+So to resolve the problem we need two things. We will store the captcha secret key on the server side. Because it is transient data we can easily use the play **Cache**. Moreover because cached data have a limited life time it will add one more security mechanism (let's say that a captcha code will be available for only 10mn). Then to resolve the code later we need to generate a **unique ID**. This unique ID will be added to each form as a hidden field and implicitly references a generated captcha code.
2114+
2115+This way we elegantly solve our state problem.
2116+
2117+Modify the **captcha** action as follows:
2118+
2119+bc. def captcha(id: String) {
2120+ val captcha = Images.captcha()
2121+ val code = captcha.getText("#E4EAFD")
2122+ Cache.set(id, code, "10mn")
2123+ renderBinary(captcha)
2124+}
2125+
2126+Note that the **getText()** method takes any color as parameter. It will use this color to draw the text.
2127+
2128+p(note). **Don't forget** to import play.cache._
2129+
2130+h2. <a>Adding the captcha image to the comment form</a>
2131+
2132+Now, before displaying a comment form we will generate a unique ID. Then we will modify the HTML form to integrate a captcha image using this ID, and add the ID to another hidden field.
2133+
2134+Let's rewrite the **Application.show** action:
2135+
2136+bc. public static void show(Long id) {
2137+ Post post = Post.findById(id);
2138+ String randomID = Codec.UUID();
2139+ render(post, randomID);
2140+}
2141+
2142+And now the form in the **/yable/app/views/Application/show.html** template:
2143+
2144+bc. ...
2145+<p>
2146+ <label for="content">Your message: </label>
2147+ <textarea name="content" id="content">${params.content}</textarea>
2148+</p>
2149+<p>
2150+ <label for="code">Please type the code below: </label>
2151+ <img src="@{Application.captcha(randomID)}" />
2152+ <br />
2153+ <input type="text" name="code" id="code" size="18" value="" />
2154+ <input type="hidden" name="randomID" value="${randomID}" />
2155+</p>
2156+<p>
2157+ <input type="submit" value="Submit your comment" />
2158+</p>
2159+...
2160+
2161+Good start. The comment form now has a captcha image.
2162+
2163+!images/guide5-2!
2164+
2165+h2. <a>Validating the captcha</a>
2166+
2167+Now we just have to validate the captcha. We have added the **randomID** as an hidden field right ? So we can retrieve it in the **postComment** action, then retrieve the actual code from Cache and finally compare it to the submitted code.
2168+
2169+Not that difficult. Let's modify the **postComment** action.
2170+
2171+bc. public static void postComment(
2172+ Long postId,
2173+ @Required(message="Author is required") String author,
2174+ @Required(message="A message is required") String content,
2175+ @Required(message="Please type the code") String code,
2176+ String randomID)
2177+{
2178+ Post post = Post.findById(postId);
2179+ validation.equals(
2180+ code, Cache.get(randomID)
2181+ ).message("Invalid code. Please type it again");
2182+ if(validation.hasErrors()) {
2183+ render("Application/show.html", post, randomID);
2184+ }
2185+ post.addComment(author, content);
2186+ flash.success("Thanks for posting %s", author);
2187+ show(postId);
2188+}
2189+
2190+Because we have now more error messages, modify the way we display errors in the **show.html** template (yes we will just display the first error, it's good enough):
2191+
2192+bc. ..
2193+#{ifErrors}
2194+ <p class="error">
2195+ ${errors[0]}
2196+ </p>
2197+#{/ifErrors}
2198+...
2199+
2200+p(note). Typically for more complex forms, error messages are not managed this way but externalized in the **messages** file and each error is printed in side of the corresponding field.
2201+
2202+Check that the captcha is now fully functional.
2203+
2204+!images/guide5-3!
2205+
2206+Great!
2207+
2208+p(note). Go to the "next part":guide6.
2209\ No newline at end of file
2210
2211=== removed file 'documentation/manual/search.textile'
2212--- documentation/manual/search.textile 2010-04-12 10:11:35 +0000
2213+++ documentation/manual/search.textile 1970-01-01 00:00:00 +0000
2214@@ -1,78 +0,0 @@
2215-h1. Search module
2216-
2217-The **Search** module allows you to have basic full text search functionality to your JPA Model. It is based on Lucene, and requires a real file system to store its indexes.
2218-
2219-h2. <a> Enable the module</a>
2220-
2221-In the **/conf/application.conf** file, enable the Search module by adding this line:
2222-
2223-bc. # The search module
2224-module.search=${play.path}/modules/search
2225-
2226-h2. <a> Indexing your objects </a>
2227-
2228-Use the **@Indexed** annotation to mark **your Model**, and then use the **@Field** to mark the fields to be indexed.
2229-
2230-bc. @Indexed
2231-public class Folder extends Model {
2232- @Field
2233- @Column(unique=true)
2234- public Integer poseidonNumber;
2235- @Field
2236- public String object;
2237-
2238-The **@Field** annotation currently supports only primitive types.
2239-
2240-h2. <a> Search the objects </a>
2241-
2242-Use the **Search** helper to build your queries:
2243-
2244-bc. Search.search("object:dogs", Folder.class)
2245-
2246-The first parameter is a lucene query and the second is your Model class. You may want to refer to the Lucene query documentation at this point, knowing that the **Search** maintains a separated index per class and adds the properties marked with **@Field** as a Lucene Field.
2247-
2248-The Search.search returns a query object that you can tweak:
2249-
2250-bc. Query q = Search.search("object:dogs", Folder.class);
2251-q.orderBy("object")
2252- .page(2,5)
2253- .reverse();
2254-
2255-
2256-To finish your query, if you wish to retrieve your **Model** objects, use
2257-
2258-bc. List<Folder> folders = q.fetch();
2259-
2260-
2261-or to get only ids (to use in a JPA query by example...):
2262-
2263-bc. List<Long> folderIds = q.fetchIds();
2264-
2265-
2266-To get full informations (like relevance), you would use:
2267-
2268-bc. List<QueryResult> results = q.executeQuery();
2269-
2270-
2271-h2. <a> Maintaining the indexes </a>
2272-
2273-Each time you create, update or delete your **Model** objects, the corresponding index is automatically updated.
2274-
2275-Should you need to re-index your objects (like if you have manually modified your database), you will reboot your application with:
2276-
2277-bc. play.search.reindex=enabled
2278-
2279-In your conf/application.conf. Don't forget to remove it after!
2280-
2281-h2. <a> Misc configuration </a>
2282-
2283-You can use the following properties in your conf/application.conf file:
2284-
2285-bc. play.search.path=/tmp/myDevApplication
2286-play.search.analyser=org.apache.lucene.analysis.standard.StandardAnalyzer
2287-
2288-**play.search.path** is where the module stores it's indexes
2289-**play.search.analyser** is the lucene analyzer class used for indexation.
2290-
2291-
2292-
2293
2294=== modified file 'documentation/manual/tags.textile'
2295--- documentation/manual/tags.textile 2010-07-29 13:08:05 +0000
2296+++ documentation/manual/tags.textile 2010-09-16 22:21:08 +0000
2297@@ -92,6 +92,26 @@
2298 #{/errors}
2299 </table>
2300
2301+h2. <a>ifErrors</a>
2302+
2303+If there's any validation error, tag's content will be rendered
2304+
2305+bc. #{ifErrors}
2306+ <p>Error(s) found!</p>
2307+#{/ifErrors}
2308+
2309+h2. <a>ifError</a>
2310+
2311+If corresponding input as error, tag's content will be rendered
2312+
2313+* **arg** (required) - input's name
2314+
2315+bc. #{ifError 'user.name'}
2316+ <p>
2317+ User name is invalid:
2318+ #{error 'user.name' /}
2319+ <p>
2320+#{/ifError}
2321
2322 h2. <a>extends</a>
2323
2324@@ -343,3 +363,60 @@
2325
2326 In this example, the first line outputs **&amp;** while the second line outputs an ampersand.
2327
2328+
2329+h2. <a>select</a>
2330+
2331+Insert a **select** tag in the template.
2332+
2333+* **name** (required) - attribute sets a name to the select element.
2334+
2335+Any unknow attribute will be considered as an HTML attribute, and rendered "as is"
2336+
2337+bc. #{select 'booking.beds', value:2, id:'select1'}
2338+ #{option 1}One king-size bed#{/option}
2339+ #{option 2}Two double beds#{/option}
2340+ #{option 3}Three beds#{/option}
2341+#{/select}
2342+
2343+Will output:
2344+
2345+bc. <select name="booking.beds" size="1" id="select1" >
2346+ <option value="1">One king-size bed</option>
2347+ <option value="2" selected="selected">Two double beds</option>
2348+ <option value="3">Three beds</option>
2349+</select>
2350+
2351+
2352+This tag can generate options using **items** attribute.
2353+
2354+* **items** (optional) - list of objects, used to create **options**
2355+* **value** (optional) - selected element in **items**
2356+* **labelProperty** (optional) - for each item, attribute used as option's label
2357+* **valueProperty** (optional) - for each item, attribute used as option's value. **id** is used by default.
2358+
2359+For example, giving a list of User, each having a name attribute:
2360+
2361+bc. #{select 'users', items:users, valueProperty:'id', labelProperty:'name', value:'User-5', class:"test", id:'select2' /}
2362+
2363+Will output:
2364+
2365+bc. <select name="users" size="1" class="test" id="select2" >
2366+ <option value="0" >User-0</option>
2367+ <option value="1" >User-1</option>
2368+ <option value="2" >User-2</option>
2369+ <option value="3" >User-3</option>
2370+ <option value="4" >User-4</option>
2371+ <option value="5" selected="selected">User-5</option>
2372+</select>
2373+
2374+h2. <a>option</a>
2375+
2376+Insert an **option** tag in the template.
2377+
2378+* **value** - option's value
2379+
2380+bc. #{option user.id} ${user.name} #{/option}
2381+
2382+Will output:
2383+
2384+bc. <option value="42">jto</option>
2385\ No newline at end of file
2386
2387=== modified file 'documentation/manual/templates.textile'
2388--- documentation/manual/templates.textile 2010-08-26 14:49:38 +0000
2389+++ documentation/manual/templates.textile 2010-09-16 22:21:08 +0000
2390@@ -22,7 +22,34 @@
2391
2392 bc. <h1>Client ${client?.name}</h1>
2393
2394-Which will only display the client name if the client is not null.
2395+Which will only display the client name if the client is not null.
2396+
2397+h3. Template decorators : #{extends /} and #{doLayout /}
2398+
2399+Decorators provide a clean solution to share a page layout (or design) across several templates.
2400+
2401+p(note). Use "#{get}":tags#ageta and "#{set}":tags#aseta tags to share variables between the template and the decorator.
2402+
2403+Embedding a page in a decorator is the matter of a one liner:
2404+
2405+bc. #{extends 'simpledesign.html' /}
2406+
2407+#{set title:'A decorated page' /}
2408+This content will be decorated.
2409+
2410+The decorator : **simpledesign.html**
2411+
2412+bc. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2413+<head>
2414+ <title>#{get 'title' /}</title>
2415+ <link rel="stylesheet" type="text/css" href="@{'/public/stylesheets/main.css'}" />
2416+</head>
2417+<body>
2418+ <h1>#{get 'title' /}</h1>
2419+ #{doLayout /}
2420+ <div class="footer">Built with the play! framework</div>
2421+</body>
2422+</html>
2423
2424 h3. Tags: #{tagName /}
2425
2426@@ -212,7 +239,11 @@
2427 #{/list}
2428 </ul>
2429
2430+<<<<<<< TREE
2431+The same applies to **java.util.Date**.
2432+=======
2433 You can find a list of available methods within the "API documentation":/@api/play/templates/JavaExtensions.html.
2434+>>>>>>> MERGE-SOURCE
2435
2436 h3. Create custom extensions
2437
2438
2439=== modified file 'framework/build.xml'
2440--- framework/build.xml 2010-05-11 20:47:09 +0000
2441+++ framework/build.xml 2010-09-16 22:21:08 +0000
2442@@ -8,21 +8,27 @@
2443 <include name="*.jar"/>
2444 </fileset>
2445 </path>
2446-
2447+
2448 <target name="clean" description="clean resources">
2449 <delete dir="classes" />
2450 <delete dir="dist" />
2451 <delete dir="tests-results" />
2452 <delete dir="tests-tmp" />
2453 <delete file="play.jar" />
2454- <delete file="src/play/version" />
2455+ <delete file="src/play/version" />
2456 <delete includeemptydirs="true">
2457+ <fileset dir="pym">
2458+ <include name="**/*.pyc"/>
2459+ </fileset>
2460 <fileset dir="../samples-and-tests">
2461 <include name="**/test-result/**"/>
2462 <include name="**/tmp/**"/>
2463 <include name="**/db/**"/>
2464 <include name="**/attachments/**"/>
2465 <include name="**/nbproject/**"/>
2466+ <include name="**/data/**"/>
2467+ <include name="**/logs/**"/>
2468+ <include name="**/i-am-working-here/**"/>
2469 </fileset>
2470 </delete>
2471 </target>
2472@@ -31,7 +37,7 @@
2473 <exec executable="bzr" outputproperty="bzrversion" errorproperty="bzrerror" failonerror="false" failifexecutionfails="false">
2474 <arg value="revno" />
2475 </exec>
2476- <condition property="version" value="1.0-r${bzrversion}" else="1.0-localbuild">
2477+ <condition property="version" value="1.1-unstable-r${bzrversion}" else="1.1-unstable-localbuild">
2478 <equals arg1="" arg2="${bzrerror}" trim="true" />
2479 </condition>
2480 <echo message="Version ${version}"></echo>
2481@@ -63,11 +69,15 @@
2482 windowtitle="Play! API">
2483 <classpath refid="project.classpath"/>
2484 <doctitle><![CDATA[<h1>Play! ${version}</h1>]]></doctitle>
2485- <bottom><![CDATA[<a href="http://guillaume.bort.fr">Guillaume Bort</a> & <a href="http://www.zenexity.fr">zenexity</a> - Distributed under <a href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache 2 licence</a>, without any warrantly]]></bottom>
2486+ <bottom><![CDATA[<a href="http://guillaume.bort.fr">Guillaume Bort</a> &amp; <a href="http://www.zenexity.fr">zenexity</a> - Distributed under <a href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache 2 licence</a>, without any warrantly]]></bottom>
2487 <tag name="todo" scope="all" description="To do:"/>
2488 <group title="Libs" packages="play.libs.*"/>
2489- <link offline="false" href="http://java.sun.com/j2se/1.5.0/docs/api/" />
2490- <link href="http://developer.java.sun.com/developer/products/xml/docs/api/"/>
2491+ <link offline="false" href="http://java.sun.com/javaee/5/docs/api" />
2492+ <link offline="false" href="http://commons.apache.org/fileupload/apidocs" />
2493+ <link offline="false" href="http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs" />
2494+ <link offline="false" href="https://www.hibernate.org/hib_docs/v3/api" />
2495+ <link offline="false" href="http://www.mchange.com/projects/c3p0/apidocs" />
2496+
2497 </javadoc>
2498 </target>
2499
2500@@ -94,13 +104,14 @@
2501 </target>
2502
2503 <target name="modules">
2504+ <property name="play.path" value="../.."/>
2505 <ant antfile="build.xml" target="build" dir="../modules/testrunner" />
2506 </target>
2507
2508 <!-- Netbeans support -->
2509
2510 <target name="nb-profile-application">
2511- <property name="application.path" value="/Users/guillaume/Desktop/webmail" />
2512+ <property name="application.path" value="" />
2513 <loadfile property="play.id" srcFile="../id" failonerror="false" quiet="true" />
2514 <loadproperties srcfile="${application.path}/conf/application.conf" />
2515 <property name="jpda.port" value="8000" />
2516@@ -194,7 +205,8 @@
2517
2518 <echo message="Testing development lifecycle (wait ...)" />
2519
2520- <exec executable="${basedir}/../samples-and-tests/i-am-a-developer/tests.py" failonerror="true">
2521+ <exec executable="python" failonerror="false">
2522+ <arg value="${basedir}/../samples-and-tests/i-am-a-developer/tests.py" />
2523 </exec>
2524
2525 <echo message="Using ${basedir}/../play${playExtension}" />
2526@@ -208,14 +220,14 @@
2527 </antcall>
2528
2529 <antcall target="play-test">
2530+ <param name="testAppPath" value="${basedir}/../samples-and-tests/zencontact"/>
2531+ </antcall>
2532+
2533+ <antcall target="play-test">
2534 <param name="testAppPath" value="${basedir}/../samples-and-tests/jobboard"/>
2535 </antcall>
2536
2537 <antcall target="play-test">
2538- <param name="testAppPath" value="${basedir}/../samples-and-tests/chat"/>
2539- </antcall>
2540-
2541- <antcall target="play-test">
2542 <param name="testAppPath" value="${basedir}/../samples-and-tests/yabe"/>
2543 </antcall>
2544
2545@@ -232,10 +244,6 @@
2546 <target name="play-test">
2547 <echo message="play auto-test ${testAppPath} (wait)" />
2548 <exec executable="${basedir}/../play${playExtension}" failonerror="true">
2549- <arg value="clean"/>
2550- <arg value="${testAppPath}"/>
2551- </exec>
2552- <exec executable="${basedir}/../play${playExtension}" failonerror="true">
2553 <arg value="auto-test"/>
2554 <arg value="${testAppPath}"/>
2555 </exec>
2556@@ -290,8 +298,9 @@
2557 <target name="package" depends="clean,version,jar,javadoc">
2558 <mkdir dir="dist" />
2559 <zip destfile="dist/play-${version}.zip" comment="Play! ${version}" update="false">
2560- <zipfileset prefix="play-${version}" dir=".." includes="**/*" excludes=".*,.*/*,framework/dist/**,id,play,nbproject/**,**/.bzr/**,*.bzrignore,support/textmate/**,framework/classes/**,framework/tests-results/**,samples-and-tests/**/test-result,samples-and-tests/**/tmp,samples-and-tests/**/db,samples-and-tests/**/attachments" />
2561+ <zipfileset prefix="play-${version}" dir=".." includes="**/*" excludes="**/cobertura.ser,**/*.pyc,hs_err*,.*,.*/*,framework/dist/**,id,play,nbproject/**,**/.bzr/**,*.bzrignore,support/textmate/**,framework/classes/**,framework/tests-results/**,samples-and-tests/**/test-result,samples-and-tests/**/i-am-working-here,samples-and-tests/**/data,samples-and-tests/**/logs,samples-and-tests/**/tmp,samples-and-tests/**/db,samples-and-tests/**/attachments,modules/**" />
2562 <zipfileset prefix="play-${version}" dir=".." includes="play" filemode="777" />
2563+ <zipfileset prefix="play-${version}" dir=".." includes="modules/crud/**,modules/secure/**,modules/docviewer/**,modules/testrunner/**" excludes="**/*.pyc" />
2564 </zip>
2565 </target>
2566
2567
2568=== removed file 'framework/lib/SnakeYAML-1.2.jar'
2569Binary files framework/lib/SnakeYAML-1.2.jar 2009-05-06 19:32:26 +0000 and framework/lib/SnakeYAML-1.2.jar 1970-01-01 00:00:00 +0000 differ
2570=== removed file 'framework/lib/WikiText.jar'
2571Binary files framework/lib/WikiText.jar 2009-08-07 16:01:53 +0000 and framework/lib/WikiText.jar 1970-01-01 00:00:00 +0000 differ
2572=== removed file 'framework/lib/ZDB.jar'
2573Binary files framework/lib/ZDB.jar 2009-10-15 16:51:49 +0000 and framework/lib/ZDB.jar 1970-01-01 00:00:00 +0000 differ
2574=== added file 'framework/lib/activation-1.1.1.jar'
2575Binary files framework/lib/activation-1.1.1.jar 1970-01-01 00:00:00 +0000 and framework/lib/activation-1.1.1.jar 2010-09-16 22:21:08 +0000 differ
2576=== removed file 'framework/lib/activation.jar'
2577Binary files framework/lib/activation.jar 2008-11-20 16:26:31 +0000 and framework/lib/activation.jar 1970-01-01 00:00:00 +0000 differ
2578=== added file 'framework/lib/async-http-client-1.2.0-SNAPSHOT.jar'
2579Binary files framework/lib/async-http-client-1.2.0-SNAPSHOT.jar 1970-01-01 00:00:00 +0000 and framework/lib/async-http-client-1.2.0-SNAPSHOT.jar 2010-09-16 22:21:08 +0000 differ
2580=== removed file 'framework/lib/asyncweb-common-0.9.0-SNAPSHOT.jar'
2581Binary files framework/lib/asyncweb-common-0.9.0-SNAPSHOT.jar 2008-07-11 12:50:21 +0000 and framework/lib/asyncweb-common-0.9.0-SNAPSHOT.jar 1970-01-01 00:00:00 +0000 differ
2582=== removed file 'framework/lib/backport-util-concurrent-3.0.jar'
2583Binary files framework/lib/backport-util-concurrent-3.0.jar 2009-05-22 10:39:52 +0000 and framework/lib/backport-util-concurrent-3.0.jar 1970-01-01 00:00:00 +0000 differ
2584=== added file 'framework/lib/bcprov-jdk16-145.jar'
2585Binary files framework/lib/bcprov-jdk16-145.jar 1970-01-01 00:00:00 +0000 and framework/lib/bcprov-jdk16-145.jar 2010-09-16 22:21:08 +0000 differ
2586=== added file 'framework/lib/c3p0-0.9.1.2.jar'
2587Binary files framework/lib/c3p0-0.9.1.2.jar 1970-01-01 00:00:00 +0000 and framework/lib/c3p0-0.9.1.2.jar 2010-09-16 22:21:08 +0000 differ
2588=== removed file 'framework/lib/c3p0-0.9.1.jar'
2589Binary files framework/lib/c3p0-0.9.1.jar 2008-05-26 10:52:38 +0000 and framework/lib/c3p0-0.9.1.jar 1970-01-01 00:00:00 +0000 differ
2590=== added file 'framework/lib/cglib-2.2.jar'
2591Binary files framework/lib/cglib-2.2.jar 1970-01-01 00:00:00 +0000 and framework/lib/cglib-2.2.jar 2010-09-16 22:21:08 +0000 differ
2592=== removed file 'framework/lib/cglib-nodep-2.2.jar'
2593Binary files framework/lib/cglib-nodep-2.2.jar 2008-05-26 15:58:12 +0000 and framework/lib/cglib-nodep-2.2.jar 1970-01-01 00:00:00 +0000 differ
2594=== added file 'framework/lib/commons-beanutils-1.8.3.jar'
2595Binary files framework/lib/commons-beanutils-1.8.3.jar 1970-01-01 00:00:00 +0000 and framework/lib/commons-beanutils-1.8.3.jar 2010-09-16 22:21:08 +0000 differ
2596=== removed file 'framework/lib/commons-beanutils.jar'
2597Binary files framework/lib/commons-beanutils.jar 2008-06-23 22:35:20 +0000 and framework/lib/commons-beanutils.jar 1970-01-01 00:00:00 +0000 differ
2598=== added file 'framework/lib/commons-codec-1.4.jar'
2599Binary files framework/lib/commons-codec-1.4.jar 1970-01-01 00:00:00 +0000 and framework/lib/commons-codec-1.4.jar 2010-09-16 22:21:08 +0000 differ
2600=== removed file 'framework/lib/commons-codec.jar'
2601Binary files framework/lib/commons-codec.jar 2008-06-23 22:35:20 +0000 and framework/lib/commons-codec.jar 1970-01-01 00:00:00 +0000 differ
2602=== added file 'framework/lib/commons-email-1.2.jar'
2603Binary files framework/lib/commons-email-1.2.jar 1970-01-01 00:00:00 +0000 and framework/lib/commons-email-1.2.jar 2010-09-16 22:21:08 +0000 differ
2604=== removed file 'framework/lib/commons-httpclient.jar'
2605Binary files framework/lib/commons-httpclient.jar 2008-06-23 22:35:20 +0000 and framework/lib/commons-httpclient.jar 1970-01-01 00:00:00 +0000 differ
2606=== added file 'framework/lib/commons-io-1.4.jar'
2607Binary files framework/lib/commons-io-1.4.jar 1970-01-01 00:00:00 +0000 and framework/lib/commons-io-1.4.jar 2010-09-16 22:21:08 +0000 differ
2608=== removed file 'framework/lib/commons-io.jar'
2609Binary files framework/lib/commons-io.jar 2008-05-28 12:53:00 +0000 and framework/lib/commons-io.jar 1970-01-01 00:00:00 +0000 differ
2610=== added file 'framework/lib/commons-lang-2.5.jar'
2611Binary files framework/lib/commons-lang-2.5.jar 1970-01-01 00:00:00 +0000 and framework/lib/commons-lang-2.5.jar 2010-09-16 22:21:08 +0000 differ
2612=== removed file 'framework/lib/commons-lang.jar'
2613Binary files framework/lib/commons-lang.jar 2008-06-23 22:35:20 +0000 and framework/lib/commons-lang.jar 1970-01-01 00:00:00 +0000 differ
2614=== removed file 'framework/lib/compiler-jdt.jar'
2615Binary files framework/lib/compiler-jdt.jar 2010-01-31 12:12:26 +0000 and framework/lib/compiler-jdt.jar 1970-01-01 00:00:00 +0000 differ
2616=== removed file 'framework/lib/ehcache-1.5.0.jar'
2617Binary files framework/lib/ehcache-1.5.0.jar 2009-05-22 10:39:52 +0000 and framework/lib/ehcache-1.5.0.jar 1970-01-01 00:00:00 +0000 differ
2618=== added file 'framework/lib/ehcache-core-2.0.0.jar'
2619Binary files framework/lib/ehcache-core-2.0.0.jar 1970-01-01 00:00:00 +0000 and framework/lib/ehcache-core-2.0.0.jar 2010-09-16 22:21:08 +0000 differ
2620=== removed file 'framework/lib/ejb3-persistence.jar'
2621Binary files framework/lib/ejb3-persistence.jar 2009-05-21 15:44:21 +0000 and framework/lib/ejb3-persistence.jar 1970-01-01 00:00:00 +0000 differ
2622=== removed file 'framework/lib/gson-1.3.jar'
2623Binary files framework/lib/gson-1.3.jar 2009-08-13 22:09:29 +0000 and framework/lib/gson-1.3.jar 1970-01-01 00:00:00 +0000 differ
2624=== added file 'framework/lib/gson-1.4.jar'
2625Binary files framework/lib/gson-1.4.jar 1970-01-01 00:00:00 +0000 and framework/lib/gson-1.4.jar 2010-09-16 22:21:08 +0000 differ
2626=== added file 'framework/lib/hibernate-annotations-3.5.6-Final.jar'
2627Binary files framework/lib/hibernate-annotations-3.5.6-Final.jar 1970-01-01 00:00:00 +0000 and framework/lib/hibernate-annotations-3.5.6-Final.jar 2010-09-16 22:21:08 +0000 differ
2628=== removed file 'framework/lib/hibernate-annotations.jar'
2629Binary files framework/lib/hibernate-annotations.jar 2009-05-21 15:44:21 +0000 and framework/lib/hibernate-annotations.jar 1970-01-01 00:00:00 +0000 differ
2630=== added file 'framework/lib/hibernate-commons-annotations-3.2.0.Final.jar'
2631Binary files framework/lib/hibernate-commons-annotations-3.2.0.Final.jar 1970-01-01 00:00:00 +0000 and framework/lib/hibernate-commons-annotations-3.2.0.Final.jar 2010-09-16 22:21:08 +0000 differ
2632=== removed file 'framework/lib/hibernate-commons-annotations.jar'
2633Binary files framework/lib/hibernate-commons-annotations.jar 2009-05-21 15:44:21 +0000 and framework/lib/hibernate-commons-annotations.jar 1970-01-01 00:00:00 +0000 differ
2634=== added file 'framework/lib/hibernate-core-3.5.6-Final-patched.jar'
2635Binary files framework/lib/hibernate-core-3.5.6-Final-patched.jar 1970-01-01 00:00:00 +0000 and framework/lib/hibernate-core-3.5.6-Final-patched.jar 2010-09-16 22:21:08 +0000 differ
2636=== added file 'framework/lib/hibernate-entitymanager-3.5.6-Final.jar'
2637Binary files framework/lib/hibernate-entitymanager-3.5.6-Final.jar 1970-01-01 00:00:00 +0000 and framework/lib/hibernate-entitymanager-3.5.6-Final.jar 2010-09-16 22:21:08 +0000 differ
2638=== removed file 'framework/lib/hibernate-entitymanager.jar'
2639Binary files framework/lib/hibernate-entitymanager.jar 2009-05-21 15:44:21 +0000 and framework/lib/hibernate-entitymanager.jar 1970-01-01 00:00:00 +0000 differ
2640=== added file 'framework/lib/hibernate-jpa-2.0-api-1.0.0.Final.jar'
2641Binary files framework/lib/hibernate-jpa-2.0-api-1.0.0.Final.jar 1970-01-01 00:00:00 +0000 and framework/lib/hibernate-jpa-2.0-api-1.0.0.Final.jar 2010-09-16 22:21:08 +0000 differ
2642=== removed file 'framework/lib/hibernate-validator.jar'
2643Binary files framework/lib/hibernate-validator.jar 2010-04-01 10:08:27 +0000 and framework/lib/hibernate-validator.jar 1970-01-01 00:00:00 +0000 differ
2644=== removed file 'framework/lib/hibernate3-ast.jar'
2645Binary files framework/lib/hibernate3-ast.jar 2009-09-09 11:02:29 +0000 and framework/lib/hibernate3-ast.jar 1970-01-01 00:00:00 +0000 differ
2646=== removed file 'framework/lib/hibernate3.jar'
2647Binary files framework/lib/hibernate3.jar 2009-09-09 12:08:38 +0000 and framework/lib/hibernate3.jar 1970-01-01 00:00:00 +0000 differ
2648=== added file 'framework/lib/hsqldb-1.8.1.2.jar'
2649Binary files framework/lib/hsqldb-1.8.1.2.jar 1970-01-01 00:00:00 +0000 and framework/lib/hsqldb-1.8.1.2.jar 2010-09-16 22:21:08 +0000 differ
2650=== removed file 'framework/lib/hsqldb.jar'
2651Binary files framework/lib/hsqldb.jar 2008-05-26 10:52:38 +0000 and framework/lib/hsqldb.jar 1970-01-01 00:00:00 +0000 differ
2652=== added file 'framework/lib/javamail-1.4.3.jar'
2653Binary files framework/lib/javamail-1.4.3.jar 1970-01-01 00:00:00 +0000 and framework/lib/javamail-1.4.3.jar 2010-09-16 22:21:08 +0000 differ
2654=== added file 'framework/lib/javassist-3.9.0.GA.jar'
2655Binary files framework/lib/javassist-3.9.0.GA.jar 1970-01-01 00:00:00 +0000 and framework/lib/javassist-3.9.0.GA.jar 2010-09-16 22:21:08 +0000 differ
2656=== removed file 'framework/lib/javassist.jar'
2657Binary files framework/lib/javassist.jar 2008-11-21 19:28:31 +0000 and framework/lib/javassist.jar 1970-01-01 00:00:00 +0000 differ
2658=== added file 'framework/lib/jj-imaging.jar'
2659Binary files framework/lib/jj-imaging.jar 1970-01-01 00:00:00 +0000 and framework/lib/jj-imaging.jar 2010-09-16 22:21:08 +0000 differ
2660=== added file 'framework/lib/jj-simplecaptcha.jar'
2661Binary files framework/lib/jj-simplecaptcha.jar 1970-01-01 00:00:00 +0000 and framework/lib/jj-simplecaptcha.jar 2010-09-16 22:21:08 +0000 differ
2662=== added file 'framework/lib/jj-textile.jar'
2663Binary files framework/lib/jj-textile.jar 1970-01-01 00:00:00 +0000 and framework/lib/jj-textile.jar 2010-09-16 22:21:08 +0000 differ
2664=== added file 'framework/lib/jj-wikitext.jar'
2665Binary files framework/lib/jj-wikitext.jar 1970-01-01 00:00:00 +0000 and framework/lib/jj-wikitext.jar 2010-09-16 22:21:08 +0000 differ
2666=== removed file 'framework/lib/joda-time-1.6.jar'
2667Binary files framework/lib/joda-time-1.6.jar 2010-03-23 10:20:26 +0000 and framework/lib/joda-time-1.6.jar 1970-01-01 00:00:00 +0000 differ
2668=== added file 'framework/lib/jodatime-1.6.jar'
2669Binary files framework/lib/jodatime-1.6.jar 1970-01-01 00:00:00 +0000 and framework/lib/jodatime-1.6.jar 2010-09-16 22:21:08 +0000 differ
2670=== added file 'framework/lib/jregex-1.2_01.jar'
2671Binary files framework/lib/jregex-1.2_01.jar 1970-01-01 00:00:00 +0000 and framework/lib/jregex-1.2_01.jar 2010-09-16 22:21:08 +0000 differ
2672=== removed file 'framework/lib/jregex1.2_01.jar'
2673Binary files framework/lib/jregex1.2_01.jar 2008-05-23 16:01:41 +0000 and framework/lib/jregex1.2_01.jar 1970-01-01 00:00:00 +0000 differ
2674=== added file 'framework/lib/jta-1.1.jar'
2675Binary files framework/lib/jta-1.1.jar 1970-01-01 00:00:00 +0000 and framework/lib/jta-1.1.jar 2010-09-16 22:21:08 +0000 differ
2676=== removed file 'framework/lib/jta.jar'
2677Binary files framework/lib/jta.jar 2008-05-26 15:58:12 +0000 and framework/lib/jta.jar 1970-01-01 00:00:00 +0000 differ
2678=== removed file 'framework/lib/junit-4.4.jar'
2679Binary files framework/lib/junit-4.4.jar 2008-05-26 10:52:38 +0000 and framework/lib/junit-4.4.jar 1970-01-01 00:00:00 +0000 differ
2680=== added file 'framework/lib/junit-4.8.1.jar'
2681Binary files framework/lib/junit-4.8.1.jar 1970-01-01 00:00:00 +0000 and framework/lib/junit-4.8.1.jar 2010-09-16 22:21:08 +0000 differ
2682=== removed file 'framework/lib/lucene-analyzers-2.3.1.jar'
2683Binary files framework/lib/lucene-analyzers-2.3.1.jar 2008-08-12 10:32:25 +0000 and framework/lib/lucene-analyzers-2.3.1.jar 1970-01-01 00:00:00 +0000 differ
2684=== removed file 'framework/lib/lucene-core-2.3.1.jar'
2685Binary files framework/lib/lucene-core-2.3.1.jar 2008-06-29 20:09:10 +0000 and framework/lib/lucene-core-2.3.1.jar 1970-01-01 00:00:00 +0000 differ
2686=== removed file 'framework/lib/mail.jar'
2687Binary files framework/lib/mail.jar 2008-11-20 16:26:31 +0000 and framework/lib/mail.jar 1970-01-01 00:00:00 +0000 differ
2688=== removed file 'framework/lib/mina-core-2.0.0-M2-SNAPSHOT.jar'
2689Binary files framework/lib/mina-core-2.0.0-M2-SNAPSHOT.jar 2008-05-27 14:08:39 +0000 and framework/lib/mina-core-2.0.0-M2-SNAPSHOT.jar 1970-01-01 00:00:00 +0000 differ
2690=== removed file 'framework/lib/mysql-connector-java-5.1.8-bin.jar'
2691Binary files framework/lib/mysql-connector-java-5.1.8-bin.jar 2009-08-18 10:42:29 +0000 and framework/lib/mysql-connector-java-5.1.8-bin.jar 1970-01-01 00:00:00 +0000 differ
2692=== added file 'framework/lib/mysql-connector-java-5.1.8.jar'
2693Binary files framework/lib/mysql-connector-java-5.1.8.jar 1970-01-01 00:00:00 +0000 and framework/lib/mysql-connector-java-5.1.8.jar 2010-09-16 22:21:08 +0000 differ
2694=== added file 'framework/lib/netty-3.2.2.Final.jar'
2695Binary files framework/lib/netty-3.2.2.Final.jar 1970-01-01 00:00:00 +0000 and framework/lib/netty-3.2.2.Final.jar 2010-09-16 22:21:08 +0000 differ
2696=== added file 'framework/lib/org.eclipse.jdt.core_3.6.0.v_A56.jar'
2697Binary files framework/lib/org.eclipse.jdt.core_3.6.0.v_A56.jar 1970-01-01 00:00:00 +0000 and framework/lib/org.eclipse.jdt.core_3.6.0.v_A56.jar 2010-09-16 22:21:08 +0000 differ
2698=== removed file 'framework/lib/oval-1.31.jar'
2699Binary files framework/lib/oval-1.31.jar 2009-05-23 21:02:57 +0000 and framework/lib/oval-1.31.jar 1970-01-01 00:00:00 +0000 differ
2700=== added file 'framework/lib/oval-1.50.jar'
2701Binary files framework/lib/oval-1.50.jar 1970-01-01 00:00:00 +0000 and framework/lib/oval-1.50.jar 2010-09-16 22:21:08 +0000 differ
2702=== removed file 'framework/lib/play-SimpleCaptcha.jar'
2703Binary files framework/lib/play-SimpleCaptcha.jar 2010-01-12 14:42:25 +0000 and framework/lib/play-SimpleCaptcha.jar 1970-01-01 00:00:00 +0000 differ
2704=== removed file 'framework/lib/play-imaging.jar'
2705Binary files framework/lib/play-imaging.jar 2010-01-12 14:42:25 +0000 and framework/lib/play-imaging.jar 1970-01-01 00:00:00 +0000 differ
2706=== removed file 'framework/lib/provided-geronimo-servlet_2.5_spec-1.2.jar'
2707Binary files framework/lib/provided-geronimo-servlet_2.5_spec-1.2.jar 2009-05-20 10:41:13 +0000 and framework/lib/provided-geronimo-servlet_2.5_spec-1.2.jar 1970-01-01 00:00:00 +0000 differ
2708=== added file 'framework/lib/provided-servlet-2.5.jar'
2709Binary files framework/lib/provided-servlet-2.5.jar 1970-01-01 00:00:00 +0000 and framework/lib/provided-servlet-2.5.jar 2010-09-16 22:21:08 +0000 differ
2710=== removed file 'framework/lib/slf4j-api-1.5.0.jar'
2711Binary files framework/lib/slf4j-api-1.5.0.jar 2008-05-26 15:58:12 +0000 and framework/lib/slf4j-api-1.5.0.jar 1970-01-01 00:00:00 +0000 differ
2712=== added file 'framework/lib/slf4j-api-1.5.8.jar'
2713Binary files framework/lib/slf4j-api-1.5.8.jar 1970-01-01 00:00:00 +0000 and framework/lib/slf4j-api-1.5.8.jar 2010-09-16 22:21:08 +0000 differ
2714=== added file 'framework/lib/snakeyaml-1.6.jar'
2715Binary files framework/lib/snakeyaml-1.6.jar 1970-01-01 00:00:00 +0000 and framework/lib/snakeyaml-1.6.jar 2010-09-16 22:21:08 +0000 differ
2716=== removed file 'framework/lib/wikitext.core_1.1.1.jar'
2717Binary files framework/lib/wikitext.core_1.1.1.jar 2009-09-22 14:27:13 +0000 and framework/lib/wikitext.core_1.1.1.jar 1970-01-01 00:00:00 +0000 differ
2718=== added directory 'framework/patches'
2719=== added file 'framework/patches/hibernate-3.5.x.Final.patch'
2720--- framework/patches/hibernate-3.5.x.Final.patch 1970-01-01 00:00:00 +0000
2721+++ framework/patches/hibernate-3.5.x.Final.patch 2010-09-16 22:21:08 +0000
2722@@ -0,0 +1,114 @@
2723+--- project/core/src/main/java/org/hibernate/EmptyInterceptor.java 2008-07-30 11:46:34.000000000 +0200
2724++++ project/core/src/main/java/org/hibernate/EmptyInterceptor.java 2010-04-08 21:07:24.000000000 +0200
2725+@@ -112,10 +112,10 @@
2726+ return sql;
2727+ }
2728+
2729+- public void onCollectionRemove(Object collection, Serializable key) throws CallbackException {}
2730++ public boolean onCollectionRemove(Object collection, Serializable key) throws CallbackException { return true; }
2731+
2732+- public void onCollectionRecreate(Object collection, Serializable key) throws CallbackException {}
2733++ public boolean onCollectionRecreate(Object collection, Serializable key) throws CallbackException { return true; }
2734+
2735+- public void onCollectionUpdate(Object collection, Serializable key) throws CallbackException {}
2736++ public boolean onCollectionUpdate(Object collection, Serializable key) throws CallbackException { return true; }
2737+
2738+ }
2739+\ No newline at end of file
2740+
2741+--- project/core/src/main/java/org/hibernate/Interceptor.java 2008-07-30 11:46:34.000000000 +0200
2742++++ project/core/src/main/java/org/hibernate/Interceptor.java 2010-04-08 21:06:52.000000000 +0200
2743+@@ -84,15 +84,15 @@
2744+ /**
2745+ * Called before a collection is (re)created.
2746+ */
2747+- public void onCollectionRecreate(Object collection, Serializable key) throws CallbackException;
2748++ public boolean onCollectionRecreate(Object collection, Serializable key) throws CallbackException;
2749+ /**
2750+ * Called before a collection is deleted.
2751+ */
2752+- public void onCollectionRemove(Object collection, Serializable key) throws CallbackException;
2753++ public boolean onCollectionRemove(Object collection, Serializable key) throws CallbackException;
2754+ /**
2755+ * Called before a collection is updated.
2756+ */
2757+- public void onCollectionUpdate(Object collection, Serializable key) throws CallbackException;
2758++ public boolean onCollectionUpdate(Object collection, Serializable key) throws CallbackException;
2759+ /**
2760+ * Called before a flush
2761+ */
2762+
2763+--- project/core/src/main/java/org/hibernate/event/def/AbstractFlushingEventListener.java 2010-03-10 03:25:04.000000000 +0100
2764++++ project/core/src/main/java/org/hibernate/event/def/AbstractFlushingEventListener.java 2010-04-08 21:06:14.000000000 +0200
2765+@@ -255,39 +255,42 @@
2766+ CollectionEntry ce = (CollectionEntry) me.getValue();
2767+
2768+ if ( ce.isDorecreate() ) {
2769+- session.getInterceptor().onCollectionRecreate( coll, ce.getCurrentKey() );
2770+- actionQueue.addAction(
2771+- new CollectionRecreateAction(
2772+- coll,
2773+- ce.getCurrentPersister(),
2774+- ce.getCurrentKey(),
2775+- session
2776+- )
2777+- );
2778++ if ( session.getInterceptor().onCollectionRecreate( coll, ce.getCurrentKey() ) ) {
2779++ actionQueue.addAction(
2780++ new CollectionRecreateAction(
2781++ coll,
2782++ ce.getCurrentPersister(),
2783++ ce.getCurrentKey(),
2784++ session
2785++ )
2786++ );
2787++ }
2788+ }
2789+ if ( ce.isDoremove() ) {
2790+- session.getInterceptor().onCollectionRemove( coll, ce.getLoadedKey() );
2791+- actionQueue.addAction(
2792+- new CollectionRemoveAction(
2793+- coll,
2794+- ce.getLoadedPersister(),
2795+- ce.getLoadedKey(),
2796+- ce.isSnapshotEmpty(coll),
2797+- session
2798+- )
2799+- );
2800++ if ( session.getInterceptor().onCollectionRemove( coll, ce.getLoadedKey() ) ) {
2801++ actionQueue.addAction(
2802++ new CollectionRemoveAction(
2803++ coll,
2804++ ce.getLoadedPersister(),
2805++ ce.getLoadedKey(),
2806++ ce.isSnapshotEmpty(coll),
2807++ session
2808++ )
2809++ );
2810++ }
2811+ }
2812+ if ( ce.isDoupdate() ) {
2813+- session.getInterceptor().onCollectionUpdate( coll, ce.getLoadedKey() );
2814+- actionQueue.addAction(
2815+- new CollectionUpdateAction(
2816+- coll,
2817+- ce.getLoadedPersister(),
2818+- ce.getLoadedKey(),
2819+- ce.isSnapshotEmpty(coll),
2820+- session
2821+- )
2822+- );
2823++ if ( session.getInterceptor().onCollectionUpdate( coll, ce.getLoadedKey() ) ) {
2824++ actionQueue.addAction(
2825++ new CollectionUpdateAction(
2826++ coll,
2827++ ce.getLoadedPersister(),
2828++ ce.getLoadedKey(),
2829++ ce.isSnapshotEmpty(coll),
2830++ session
2831++ )
2832++ );
2833++ }
2834+ }
2835+
2836+ }
2837
2838=== added file 'framework/patches/hibernate-3.5.x.Final.patch.README'
2839--- framework/patches/hibernate-3.5.x.Final.patch.README 1970-01-01 00:00:00 +0000
2840+++ framework/patches/hibernate-3.5.x.Final.patch.README 2010-09-16 22:21:08 +0000
2841@@ -0,0 +1,1 @@
2842+Download Hibernate 3.5.0.Final source code, apply the patch, and build with maven (good luck)
2843\ No newline at end of file
2844
2845=== added directory 'framework/pym/play'
2846=== added file 'framework/pym/play/__init__.py'
2847=== added file 'framework/pym/play/application.py'
2848--- framework/pym/play/application.py 1970-01-01 00:00:00 +0000
2849+++ framework/pym/play/application.py 2010-09-16 22:21:08 +0000
2850@@ -0,0 +1,262 @@
2851+import sys
2852+import os, os.path
2853+import re
2854+import shutil
2855+import socket
2856+
2857+class ModuleNotFound(Exception):
2858+ def __init__(self, value):
2859+ self.value = value
2860+ def __str__(self):
2861+ return repr(self.value)
2862+
2863+class PlayApplication:
2864+ """A Play Application: conf file, java"""
2865+
2866+ # ~~~~~~~~~~~~~~~~~~~~~~ Constructor
2867+
2868+ def __init__(self, application_path, env):
2869+ self.path = application_path
2870+ if application_path is not None:
2871+ confpath = os.path.join(application_path, 'conf/application.conf')
2872+ try:
2873+ self.conf = PlayConfParser(confpath, env["id"])
2874+ except:
2875+ self.conf = None # No app / Invalid app
2876+ else:
2877+ self.conf = None
2878+ self.play_env = env
2879+ self.jpda_port = self.readConf('jpda_port')
2880+
2881+ # ~~~~~~~~~~~~~~~~~~~~~~ Configuration File
2882+
2883+ def check(self):
2884+ if not os.path.exists(os.path.join(self.path, 'conf', 'routes')):
2885+ print "~ Oops. %s does not seem to host a valid application" % os.path.normpath(self.path)
2886+ print "~"
2887+ sys.exit(-1)
2888+
2889+ def readConf(self, key):
2890+ if (self.conf is None):
2891+ return ''
2892+ return self.conf.get(key)
2893+
2894+ def readConfs(self, key):
2895+ if (self.conf is None):
2896+ return []
2897+ return self.conf.getAll(key)
2898+
2899+ # ~~~~~~~~~~~~~~~~~~~~~~ Modules
2900+
2901+ def modules(self):
2902+ modules = []
2903+ for m in self.readConfs('module.'):
2904+ om = m
2905+ if '${play.path}' in m:
2906+ m = m.replace('${play.path}', self.play_env["basedir"])
2907+ if m[0] is not '/':
2908+ m = os.path.normpath(os.path.join(self.path, m))
2909+ if not os.path.exists(m):
2910+ print "~ Oops,"
2911+ print "~ Module not found: %s" % (m)
2912+ print "~"
2913+ if m.startswith('${play.path}/modules'):
2914+ print "~ You can try to install the missing module using 'play install %s'" % (m[21:])
2915+ print "~"
2916+ sys.exit(-1)
2917+ modules.append(m)
2918+ if self.play_env["id"] == 'test':
2919+ modules.append(os.path.normpath(os.path.join(self.play_env["basedir"], 'modules/testrunner')))
2920+ return modules
2921+
2922+ def module_names(self):
2923+ return map(lambda x: x[7:],self.conf.getAllKeys("module."))
2924+
2925+ def load_modules(self):
2926+ if os.environ.has_key('MODULES'):
2927+ if os.name == 'nt':
2928+ modules = os.environ['MODULES'].split(';')
2929+ else:
2930+ modules = os.environ['MODULES'].split(':')
2931+ else:
2932+ modules = []
2933+
2934+ if play_app is not None:
2935+ try:
2936+ modules = play_app.modules()
2937+ except ModuleNotFound, e:
2938+ print 'Module not found %s' % e
2939+ sys.exit(-1)
2940+
2941+ if play_env["id"] == 'test':
2942+ modules.append(os.path.normpath(os.path.join(play_env["basedir"], 'modules/testrunner')))
2943+
2944+ def override(self, f, t):
2945+ fromFile = None
2946+ for module in self.modules():
2947+ pc = os.path.join(module, f)
2948+ if os.path.exists(pc): fromFile = pc
2949+ if not fromFile:
2950+ print "~ %s not found in any module" % f
2951+ print "~ "
2952+ sys.exit(-1)
2953+ toFile = os.path.join(self.path, t)
2954+ if os.path.exists(toFile):
2955+ response = raw_input("~ Warning! %s already exists and will be overriden (y/n)? " % toFile)
2956+ if not response == 'y':
2957+ return
2958+ if not os.path.exists(os.path.dirname(toFile)):
2959+ os.makedirs(os.path.dirname(toFile))
2960+ shutil.copyfile(fromFile, toFile)
2961+ print "~ Copied %s to %s " % (fromFile, toFile)
2962+
2963+ def name(self):
2964+ return self.readConf("application.name")
2965+
2966+ # ~~~~~~~~~~~~~~~~~~~~~~ JAVA
2967+
2968+ def getClasspath(self):
2969+ classpath = []
2970+
2971+ # The default
2972+ classpath.append(os.path.normpath(os.path.join(self.path, 'conf')))
2973+ classpath.append(os.path.normpath(os.path.join(self.play_env["basedir"], 'framework/play.jar')))
2974+
2975+ # The application
2976+ if os.path.exists(os.path.join(self.path, 'lib')):
2977+ for jar in os.listdir(os.path.join(self.path, 'lib')):
2978+ if jar.endswith('.jar'):
2979+ classpath.append(os.path.normpath(os.path.join(self.path, 'lib/%s' % jar)))
2980+
2981+ # The modules
2982+ for module in self.modules():
2983+ if os.path.exists(os.path.join(module, 'lib')):
2984+ libs = os.path.join(module, 'lib')
2985+ if os.path.exists(libs):
2986+ for jar in os.listdir(libs):
2987+ if jar.endswith('.jar'):
2988+ classpath.append(os.path.normpath(os.path.join(libs, '%s' % jar)))
2989+
2990+ # The framework
2991+ for jar in os.listdir(os.path.join(self.play_env["basedir"], 'framework/lib')):
2992+ if jar.endswith('.jar'):
2993+ classpath.append(os.path.normpath(os.path.join(self.play_env["basedir"], 'framework/lib/%s' % jar)))
2994+
2995+ return classpath
2996+
2997+ def agent_path(self):
2998+ return os.path.join(self.play_env["basedir"], 'framework/play.jar')
2999+
3000+ def cp_args(self):
3001+ classpath = self.getClasspath()
3002+ cp_args = ':'.join(classpath)
3003+ if os.name == 'nt':
3004+ cp_args = ';'.join(classpath)
3005+ return cp_args
3006+
3007+ def java_path(self):
3008+ if not os.environ.has_key('JAVA_HOME'):
3009+ return "java"
3010+ else:
3011+ return os.path.normpath("%s/bin/java" % os.environ['JAVA_HOME'])
3012+
3013+ def pid_path(self):
3014+ if os.environ.has_key('PLAY_PID_PATH'):
3015+ return os.environ['PLAY_PID_PATH'];
3016+ else:
3017+ return os.path.join(self.path, 'server.pid');
3018+
3019+ def log_path(self):
3020+ if not os.environ.has_key('PLAY_LOG_PATH'):
3021+ log_path = os.path.join(self.path, 'logs');
3022+ else:
3023+ log_path = os.environ['PLAY_LOG_PATH'];
3024+ if not os.path.exists(log_path):
3025+ os.mkdir(log_path);
3026+ return log_path
3027+
3028+ def check_jpda(self):
3029+ self.jpda_port = self.readConf('jpda.port')
3030+ try:
3031+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3032+ s.bind(('127.0.0.1', int(self.jpda_port)))
3033+ s.close()
3034+ except socket.error, e:
3035+ print 'JPDA port %s is already used. Will try to use any free port for debugging' % self.jpda_port
3036+ self.jpda_port = 0
3037+
3038+ def java_cmd(self, java_args, cp_args=None, className='play.server.Server', args=['']):
3039+ memory_in_args=False
3040+ for arg in java_args:
3041+ if arg.startswith('-Xm'):
3042+ memory_in_args=True
3043+ if not memory_in_args:
3044+ memory = self.readConf('jvm.memory')
3045+ if memory:
3046+ java_args = java_args + memory.split(' ')
3047+ if cp_args is None:
3048+ cp_args = self.cp_args()
3049+
3050+ self.jpda_port = self.readConf('jpda.port')
3051+
3052+ application_mode = self.readConf('application.mode')
3053+
3054+ if application_mode == 'prod':
3055+ java_args.append('-server')
3056+
3057+ java_policy = self.readConf('java.policy')
3058+ if java_policy != '':
3059+ policyFile = os.path.join(self.path, 'conf', java_policy)
3060+ if os.path.exists(policyFile):
3061+ print "~ using policy file \"%s\"" % policyFile
3062+ java_args.append('-Djava.security.manager')
3063+ java_args.append('-Djava.security.policy==%s' % policyFile)
3064+
3065+ java_cmd = [self.java_path(), '-javaagent:%s' % self.agent_path()] + java_args + ['-classpath', cp_args, '-Dapplication.path=%s' % self.path, '-Dplay.id=%s' % self.play_env["id"], className] + args
3066+ return java_cmd
3067+
3068+class PlayConfParser:
3069+
3070+ DEFAULTS = {
3071+ 'http.port': '9000',
3072+ 'jpda.port': '8000'
3073+ }
3074+
3075+ def __init__(self, filepath, frameworkId):
3076+ self.id = frameworkId
3077+ f = file(filepath)
3078+ self.entries = dict()
3079+ for line in f:
3080+ linedef = line.strip()
3081+ if len(linedef) == 0:
3082+ continue
3083+ if linedef[0] in ('!', '#'):
3084+ continue
3085+ if linedef.find('=') == -1:
3086+ continue
3087+ self.entries[linedef.split('=')[0].rstrip()] = linedef.split('=')[1].lstrip()
3088+ f.close()
3089+
3090+ def get(self, key):
3091+ idkey = '%' + self.id + "." + key
3092+ if idkey in self.entries:
3093+ return self.entries[idkey]
3094+ if key in self.entries:
3095+ return self.entries[key]
3096+ if key in self.DEFAULTS:
3097+ return self.DEFAULTS[key]
3098+ return ''
3099+
3100+ def getAllKeys(self, query):
3101+ result = []
3102+ for (key, value) in self.entries.items():
3103+ if key.startswith(query) or key.startswith('%' + self.id + '.' + query):
3104+ result.append(key)
3105+ return result
3106+
3107+ def getAll(self, query):
3108+ result = []
3109+ for (key, value) in self.entries.items():
3110+ if key.startswith(query) or key.startswith('%' + self.id + '.' + query):
3111+ result.append(value)
3112+ return result
3113
3114=== added file 'framework/pym/play/cmdloader.py'
3115--- framework/pym/play/cmdloader.py 1970-01-01 00:00:00 +0000
3116+++ framework/pym/play/cmdloader.py 2010-09-16 22:21:08 +0000
3117@@ -0,0 +1,40 @@
3118+import imp
3119+import os
3120+
3121+class CommandLoader:
3122+ def __init__(self, play_path):
3123+ self.path = os.path.join(play_path, 'framework', 'pym', 'play', 'commands')
3124+ self.commands = {}
3125+ self.modules = {}
3126+ self.load_core()
3127+
3128+ def load_core(self):
3129+ for filename in os.listdir(self.path):
3130+ if filename != "__init__.py" and filename.endswith(".py"):
3131+ name = filename.replace(".py", "")
3132+ mod = load_python_module(name, self.path)
3133+ self._load_cmd_from(mod)
3134+
3135+ def load_play_module(self, modname):
3136+ try:
3137+ leafname = os.path.basename(modname).split('.')[0]
3138+ mod = imp.load_source(leafname, os.path.join(modname, "commands.py"))
3139+ self._load_cmd_from(mod)
3140+ except:
3141+ pass # No command to load in this module
3142+
3143+ def _load_cmd_from(self, mod):
3144+ try:
3145+ for name in mod.COMMANDS:
3146+ if name in self.commands:
3147+ print "~ Warning: conflict on command " + name
3148+ self.commands[name] = mod
3149+ if 'MODULE' in dir(mod):
3150+ self.modules[mod.MODULE] = mod
3151+ except Exception:
3152+ print "~ Warning: error loading command " + name
3153+
3154+def load_python_module(name, location):
3155+ mod_desc = imp.find_module(name, [location])
3156+ return imp.load_module(name, mod_desc[0], mod_desc[1], mod_desc[2])
3157+
3158
3159=== added directory 'framework/pym/play/commands'
3160=== added file 'framework/pym/play/commands/__init__.py'
3161=== added file 'framework/pym/play/commands/base.py'
3162--- framework/pym/play/commands/base.py 1970-01-01 00:00:00 +0000
3163+++ framework/pym/play/commands/base.py 2010-09-16 22:21:08 +0000
3164@@ -0,0 +1,273 @@
3165+# Command related to creation and execution: run, new, clean, test, auto-test
3166+
3167+import sys
3168+import os
3169+import subprocess
3170+import shutil
3171+import getopt
3172+import urllib2
3173+import webbrowser
3174+import time
3175+
3176+from play.utils import *
3177+
3178+COMMANDS = ['run', 'new', 'clean', 'test', 'autotest', 'auto-test', 'id', 'new,run', 'clean,run']
3179+
3180+HELP = {
3181+ 'id': "Define the framework ID",
3182+ 'new': "Create a new application",
3183+ 'clean': "Delete temporary files (including the bytecode cache)",
3184+ 'run': "Run the application in the current shell",
3185+ 'test': "Run the application in test mode in the current shell",
3186+ 'auto-test': "Automatically run all application tests"
3187+}
3188+
3189+def execute(**kargs):
3190+ command = kargs.get("command")
3191+ app = kargs.get("app")
3192+ args = kargs.get("args")
3193+ env = kargs.get("env")
3194+
3195+ if command == 'id':
3196+ id(env)
3197+ if command == 'new' or command == 'new,run':
3198+ new(app, args, env)
3199+ if command == 'clean' or command == 'clean,run':
3200+ clean(app)
3201+ if command == 'new,run' or command == 'clean,run' or command == 'run':
3202+ run(app, args)
3203+ if command == 'test':
3204+ test(app, args)
3205+ if command == 'auto-test' or command == 'autotest':
3206+ autotest(app, args)
3207+
3208+def new(app, args, env):
3209+ withModules = []
3210+ application_name = None
3211+ try:
3212+ optlist, args = getopt.getopt(args, '', ['with=', 'name='])
3213+ for o, a in optlist:
3214+ if o in ('--with'):
3215+ withModules = a.split(',')
3216+ if o in ('--name'):
3217+ application_name = a
3218+ except getopt.GetoptError, err:
3219+ print "~ %s" % str(err)
3220+ print "~ Sorry, unrecognized option"
3221+ print "~ "
3222+ sys.exit(-1)
3223+ if os.path.exists(app.path):
3224+ print "~ Oops. %s already exists" % app.path
3225+ print "~"
3226+ sys.exit(-1)
3227+
3228+ md = []
3229+ for m in withModules:
3230+ dirname = None
3231+ if os.path.exists(os.path.join(env["basedir"], 'modules/%s' % m)) and os.path.isdir(os.path.join(env["basedir"], 'modules/%s' % m)):
3232+ dirname = m
3233+ else:
3234+ for f in os.listdir(os.path.join(env["basedir"], 'modules')):
3235+ if os.path.isdir(os.path.join(env["basedir"], 'modules/%s' % f)) and f.find('%s-' % m) == 0:
3236+ dirname = f
3237+ break
3238+
3239+ if not dirname:
3240+ print "~ Oops. No module %s found" % m
3241+ print "~ Try to install it using 'play install %s'" % m
3242+ print "~"
3243+ sys.exit(-1)
3244+
3245+ md.append(dirname)
3246+
3247+ print "~ The new application will be created in %s" % os.path.normpath(app.path)
3248+ if application_name is None:
3249+ application_name = raw_input("~ What is the application name? [%s] " % os.path.basename(app.path))
3250+ if application_name == "":
3251+ application_name = os.path.basename(app.path)
3252+ shutil.copytree(os.path.join(env["basedir"], 'resources/application-skel'), app.path)
3253+ app.check()
3254+ replaceAll(os.path.join(app.path, 'conf/application.conf'), r'%APPLICATION_NAME%', application_name)
3255+ replaceAll(os.path.join(app.path, 'conf/application.conf'), r'%SECRET_KEY%', secretKey())
3256+ print "~"
3257+
3258+ for m in md:
3259+ mn = m
3260+ if mn.find('-') > 0:
3261+ mn = mn[:mn.find('-')]
3262+ replaceAll(os.path.join(app.path, 'conf/application.conf'), r'# ---- MODULES ----', '# ---- MODULES ----\nmodule.%s=${play.path}/modules/%s' % (mn, m) )
3263+
3264+ print "~ OK, the application is created."
3265+ print "~ Start it with : play run %s" % sys.argv[2]
3266+ print "~ Have fun!"
3267+ print "~"
3268+
3269+def run(app, args):
3270+ app.check()
3271+ disable_check_jpda = False
3272+ if args.count('-f') == 1:
3273+ disable_check_jpda = True
3274+ args.remove('-f')
3275+
3276+ print "~ Ctrl+C to stop"
3277+ print "~ "
3278+ java_cmd = app.java_cmd(args)
3279+ if app.readConf('application.mode') == 'dev':
3280+ if not disable_check_jpda: app.check_jpda()
3281+ java_cmd.insert(2, '-Xdebug')
3282+ java_cmd.insert(2, '-Xrunjdwp:transport=dt_socket,address=%s,server=y,suspend=n' % app.jpda_port)
3283+ java_cmd.insert(2, '-Dplay.debug=yes')
3284+ try:
3285+ subprocess.call(java_cmd, env=os.environ)
3286+ except OSError:
3287+ print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). "
3288+ sys.exit(-1)
3289+ print
3290+
3291+def clean(app):
3292+ app.check()
3293+ print "~ Deleting %s" % os.path.normpath(os.path.join(app.path, 'tmp'))
3294+ if os.path.exists(os.path.join(app.path, 'tmp')):
3295+ shutil.rmtree(os.path.join(app.path, 'tmp'))
3296+ print "~"
3297+
3298+def show_modules(args):
3299+ check_application()
3300+ modules = app.modules()
3301+ if len(modules):
3302+ print "~ Application modules are:"
3303+ print "~ "
3304+ for module in modules:
3305+ print "~ %s" % module
3306+ else:
3307+ print "~ No modules installed in this application"
3308+ print "~ "
3309+ sys.exit(0)
3310+
3311+def test(app, args):
3312+ app.check()
3313+ disable_check_jpda = False
3314+ if args.count('-f') == 1:
3315+ disable_check_jpda = True
3316+ java_cmd = app.java_cmd(args)
3317+ print "~ Running in test mode"
3318+ print "~ Ctrl+C to stop"
3319+ print "~ "
3320+ app.check_jpda()
3321+ java_cmd.insert(2, '-Xdebug')
3322+ java_cmd.insert(2, '-Xrunjdwp:transport=dt_socket,address=%s,server=y,suspend=n' % app.jpda_port)
3323+ java_cmd.insert(2, '-Dplay.debug=yes')
3324+ try:
3325+ subprocess.call(java_cmd, env=os.environ)
3326+ except OSError:
3327+ print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). "
3328+ sys.exit(-1)
3329+ print
3330+
3331+def autotest(app, args):
3332+ app.check()
3333+ print "~ Running in test mode"
3334+ print "~ Ctrl+C to stop"
3335+ print "~ "
3336+
3337+ print "~ Deleting %s" % os.path.normpath(os.path.join(app.path, 'tmp'))
3338+ if os.path.exists(os.path.join(app.path, 'tmp')):
3339+ shutil.rmtree(os.path.join(app.path, 'tmp'))
3340+ print "~"
3341+
3342+ # Kill if exists
3343+ http_port = app.readConf('http.port')
3344+ try:
3345+ proxy_handler = urllib2.ProxyHandler({})
3346+ opener = urllib2.build_opener(proxy_handler)
3347+ opener.open('http://localhost:%s/@kill' % http_port);
3348+ except Exception, e:
3349+ pass
3350+
3351+ # Run app
3352+ test_result = os.path.join(app.path, 'test-result')
3353+ if os.path.exists(test_result):
3354+ shutil.rmtree(test_result)
3355+ sout = open(os.path.join(app.log_path(), 'system.out'), 'w')
3356+ java_cmd = app.java_cmd(args)
3357+ try:
3358+ play_process = subprocess.Popen(java_cmd, env=os.environ, stdout=sout)
3359+ except OSError:
3360+ print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). "
3361+ sys.exit(-1)
3362+ soutint = open(os.path.join(app.log_path(), 'system.out'), 'r')
3363+ while True:
3364+ if play_process.poll():
3365+ print "~"
3366+ print "~ Oops, application has not started?"
3367+ print "~"
3368+ sys.exit(-1)
3369+ line = soutint.readline().strip()
3370+ if line:
3371+ print line
3372+ if line.find('Listening for HTTP') > -1:
3373+ soutint.close()
3374+ break
3375+
3376+ # Run FirePhoque
3377+ print "~"
3378+
3379+ fpcp = [os.path.join(app.play_env["basedir"], 'modules/testrunner/lib/play-testrunner.jar')]
3380+ fpcp_libs = os.path.join(app.play_env["basedir"], 'modules/testrunner/firephoque')
3381+ for jar in os.listdir(fpcp_libs):
3382+ if jar.endswith('.jar'):
3383+ fpcp.append(os.path.normpath(os.path.join(fpcp_libs, jar)))
3384+ cp_args = ':'.join(fpcp)
3385+ if os.name == 'nt':
3386+ cp_args = ';'.join(fpcp)
3387+ java_cmd = [app.java_path(), '-classpath', cp_args, '-Dapplication.url=http://localhost:%s' % http_port, 'play.modules.testrunner.FirePhoque']
3388+ try:
3389+ subprocess.call(java_cmd, env=os.environ)
3390+ except OSError:
3391+ print "Could not execute the headless browser. "
3392+ sys.exit(-1)
3393+
3394+ print "~"
3395+ time.sleep(1)
3396+ if os.path.exists(os.path.join(app.path, 'test-result/result.passed')):
3397+ print "~ All tests passed"
3398+ print "~"
3399+ if os.path.exists(os.path.join(app.path, 'test-result/result.failed')):
3400+ print "~ Some tests have failed. See file://%s for results" % test_result
3401+ print "~"
3402+
3403+ kill(play_process.pid)
3404+
3405+def id(play_env):
3406+ if not play_env["id"]:
3407+ print "~ framework ID is not set"
3408+ new_id = raw_input("~ What is the new framework ID (or blank to unset)? ")
3409+ if new_id:
3410+ print "~"
3411+ print "~ OK, the framework ID is now %s" % new_id
3412+ print "~"
3413+ open(play_env["id_file"], 'w').write(new_id)
3414+ else:
3415+ print "~"
3416+ print "~ OK, the framework ID is unset"
3417+ print "~"
3418+ if os.path.exists(play_env["id_file"]):
3419+ os.remove(play_env["id_file"])
3420+
3421+# ~~~~~~~~~ UTILS
3422+
3423+def kill(pid):
3424+ if os.name == 'nt':
3425+ import ctypes
3426+ handle = ctypes.windll.kernel32.OpenProcess(1, False, int(pid))
3427+ if not ctypes.windll.kernel32.TerminateProcess(handle, 0):
3428+ print "~ Cannot kill the process with pid %s (ERROR %s)" % (pid, ctypes.windll.kernel32.GetLastError())
3429+ print "~ "
3430+ sys.exit(-1)
3431+ else:
3432+ try:
3433+ os.kill(int(pid), 15)
3434+ except OSError:
3435+ print "~ Play was not running (Process id %s not found)" % pid
3436+ print "~"
3437+ sys.exit(-1)
3438
3439=== added file 'framework/pym/play/commands/check.py'
3440--- framework/pym/play/commands/check.py 1970-01-01 00:00:00 +0000
3441+++ framework/pym/play/commands/check.py 2010-09-16 22:21:08 +0000
3442@@ -0,0 +1,55 @@
3443+import os, os.path
3444+import shutil
3445+
3446+from play.utils import *
3447+import play.launchpad as launchpad
3448+
3449+COMMANDS = ['check']
3450+
3451+HELP = {
3452+ 'check': 'Check for a release newer than the current one'
3453+}
3454+
3455+def execute(**kargs):
3456+ args = kargs.get("args")
3457+ play_env = kargs.get("env")
3458+
3459+ if len(sys.argv) == 3:
3460+ version = sys.argv[2]
3461+ else:
3462+ version = playVersion(play_env)
3463+
3464+ lpPlay = launchpad.Project("play")
3465+
3466+ if len(version) > 2:
3467+ series = lpPlay.getSeries(name=version[0:3])
3468+ if series is None:
3469+ print "~ Error: unable to determine series for version " + version + "."
3470+ print "~"
3471+ sys.exit(-1)
3472+
3473+ lpSeries = launchpad.Entry(series["self_link"])
3474+ releases = filter(lambda x: x["type"] == "release", lpSeries.get_timeline()["landmarks"])
3475+
3476+ if len(releases) == 0:
3477+ print "~ No release for the requested series. Are you on a development branch?"
3478+ elif isRelease(version, releases):
3479+ if releases[0]["name"] == version:
3480+ print "~ You are using the latest version of the serie (" + version + ")."
3481+ else:
3482+ print "~ ***** NEW RELEASE: " + releases[0]["name"] + " *****"
3483+ print "~ released on " + releases[0]["date"]
3484+ print "~"
3485+ print "~ Please upgrade: https://launchpad.net" + releases[0]["uri"]
3486+ else:
3487+ print "~ You don't seem to be using an official release."
3488+ print "~ Latest release is " + releases[0]["name"] + ", released on " + releases[0]["date"]
3489+
3490+ print "~"
3491+
3492+def isRelease(version, releases):
3493+ for release in releases:
3494+ if release["name"] == version:
3495+ return True
3496+ return False
3497+
3498
3499=== added file 'framework/pym/play/commands/classpath.py'
3500--- framework/pym/play/commands/classpath.py 1970-01-01 00:00:00 +0000
3501+++ framework/pym/play/commands/classpath.py 2010-09-16 22:21:08 +0000
3502@@ -0,0 +1,16 @@
3503+# Show the computed classpath for the application
3504+
3505+COMMANDS = ['cp', 'classpath']
3506+
3507+HELP = {
3508+ 'classpath': 'Display the computed classpath'
3509+}
3510+
3511+def execute(**kargs):
3512+ command = kargs.get("command")
3513+ app = kargs.get("app")
3514+ args = kargs.get("args")
3515+ print "~ Computed classpath is:"
3516+ print "~ "
3517+ print app.getClasspath()
3518+ print "~ "
3519
3520=== added file 'framework/pym/play/commands/daemon.py'
3521--- framework/pym/play/commands/daemon.py 1970-01-01 00:00:00 +0000
3522+++ framework/pym/play/commands/daemon.py 2010-09-16 22:21:08 +0000
3523@@ -0,0 +1,147 @@
3524+import os, os.path
3525+import subprocess
3526+from play.utils import *
3527+
3528+COMMANDS = ['start', 'stop', 'restart', 'pid', 'out']
3529+
3530+HELP = {
3531+ 'start': 'Start the application in the background',
3532+ 'stop': 'Stop the running application',
3533+ 'restart': 'Restart the running application',
3534+ 'pid': 'Show the PID of the running application',
3535+ 'out': 'Follow logs/system.out file'
3536+}
3537+
3538+def execute(**kargs):
3539+ command = kargs.get("command")
3540+ app = kargs.get("app")
3541+ args = kargs.get("args")
3542+ play_env = kargs.get("env")
3543+
3544+ if command == 'start':
3545+ start(app, args)
3546+ if command == 'stop':
3547+ stop(app)
3548+ if command == 'restart':
3549+ restart(app, args)
3550+ if command == 'pid':
3551+ pid(app)
3552+ if command == 'out':
3553+ out(app)
3554+
3555+def start(app, args):
3556+ app.check()
3557+ if os.path.exists(app.pid_path()):
3558+ print "~ Oops. %s is already started! (or delete %s)" % (os.path.normpath(app.path), os.path.normpath(app.pid_path()))
3559+ print "~"
3560+ sys.exit(1)
3561+
3562+ sysout = app.readConf('application.log.system.out')
3563+ sysout = sysout!='false' and sysout!='off'
3564+ if not sysout:
3565+ sout = None
3566+ else:
3567+ sout = open(os.path.join(app.log_path(), 'system.out'), 'w')
3568+ try:
3569+ pid = subprocess.Popen(app.java_cmd(args), stdout=sout, env=os.environ).pid
3570+ except OSError:
3571+ print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). "
3572+ sys.exit(-1)
3573+ print "~ OK, %s is started" % os.path.normpath(app.path)
3574+ if sysout:
3575+ print "~ output is redirected to %s" % os.path.normpath(os.path.join(app.log_path(), 'system.out'))
3576+ pid_file = open(app.pid_path(), 'w')
3577+ pid_file.write(str(pid))
3578+ print "~ pid is %s" % pid
3579+ print "~"
3580+
3581+def stop(app):
3582+ app.check()
3583+ if not os.path.exists(app.pid_path()):
3584+ print "~ Oops! %s is not started (server.pid not found)" % os.path.normpath(app.path)
3585+ print "~"
3586+ sys.exit(-1)
3587+ pid = open(app.pid_path()).readline().strip()
3588+ os.remove(app.pid_path())
3589+ kill(pid)
3590+ print "~ OK, %s is stopped" % app.path
3591+ print "~"
3592+
3593+
3594+def restart(app, args):
3595+ app.check()
3596+ if not os.path.exists(app.pid_path()):
3597+ print "~ Oops! %s is not started (server.pid not found)" % os.path.normpath(app.path)
3598+ print "~"
3599+ else:
3600+ pid = open(app.pid_path()).readline().strip()
3601+ os.remove(app.pid_path())
3602+ kill(pid)
3603+
3604+ sysout = app.readConf('application.log.system.out')
3605+ sysout = sysout!='false' and sysout!='off'
3606+ java_cmd = app.java_cmd(args)
3607+ if not sysout:
3608+ sout = None
3609+ else:
3610+ sout = open(os.path.join(app.log_path(), 'system.out'), 'w')
3611+ try:
3612+ pid = subprocess.Popen(java_cmd, stdout=sout, env=os.environ).pid
3613+ except OSError:
3614+ print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). "
3615+ sys.exit(-1)
3616+ print "~ OK, %s is restarted" % os.path.normpath(app.path)
3617+ if sysout:
3618+ print "~ output is redirected to %s" % os.path.normpath(os.path.join(app.log_path(), 'system.out'))
3619+ pid_file = open(app.pid_path(), 'w')
3620+ pid_file.write(str(pid))
3621+ print "~ New pid is %s" % pid
3622+ print "~"
3623+ sys.exit(0)
3624+
3625+
3626+def pid(app):
3627+ app.check()
3628+ if not os.path.exists(app.pid_path()):
3629+ print "~ Oops! %s is not started (server.pid not found)" % os.path.normpath(app.path)
3630+ print "~"
3631+ sys.exit(-1)
3632+ pid = open(app.pid_path()).readline().strip()
3633+ print "~ PID of the running applications is %s" % pid
3634+ print "~ "
3635+
3636+def out(app):
3637+ app.check()
3638+ if not os.path.exists(os.path.join(app.log_path(), 'system.out')):
3639+ print "~ Oops! %s not found" % os.path.normpath(os.path.join(app.log_path(), 'system.out'))
3640+ print "~"
3641+ sys.exit(-1)
3642+ sout = open(os.path.join(app.log_path(), 'system.out'), 'r')
3643+ try:
3644+ sout.seek(-5000, os.SEEK_END)
3645+ except IOError:
3646+ sout.seek(0)
3647+ while True:
3648+ where = sout.tell()
3649+ line = sout.readline().strip()
3650+ if not line:
3651+ time.sleep(1)
3652+ sout.seek(where)
3653+ else:
3654+ print line
3655+
3656+def kill(pid):
3657+ if os.name == 'nt':
3658+ import ctypes
3659+ handle = ctypes.windll.kernel32.OpenProcess(1, False, int(pid))
3660+ if not ctypes.windll.kernel32.TerminateProcess(handle, 0):
3661+ print "~ Cannot kill the process with pid %s (ERROR %s)" % (pid, ctypes.windll.kernel32.GetLastError())
3662+ print "~ "
3663+ sys.exit(-1)
3664+ else:
3665+ try:
3666+ os.kill(int(pid), 15)
3667+ except OSError:
3668+ print "~ Play was not running (Process id %s not found)" % pid
3669+ print "~"
3670+ sys.exit(-1)
3671
3672=== added file 'framework/pym/play/commands/eclipse.py'
3673--- framework/pym/play/commands/eclipse.py 1970-01-01 00:00:00 +0000
3674+++ framework/pym/play/commands/eclipse.py 2010-09-16 22:21:08 +0000
3675@@ -0,0 +1,115 @@
3676+import os, os.path
3677+import shutil
3678+import time
3679+
3680+from play.utils import *
3681+
3682+COMMANDS = ['eclipsify', 'ec']
3683+
3684+HELP = {
3685+ 'eclipsify': 'Create all Eclipse configuration files'
3686+}
3687+
3688+def execute(**kargs):
3689+ app = kargs.get("app")
3690+ args = kargs.get("args")
3691+ play_env = kargs.get("env")
3692+
3693+ app.check()
3694+ app.check_jpda()
3695+ modules = app.modules()
3696+ classpath = app.getClasspath()
3697+
3698+ application_name = app.readConf('application.name')
3699+ if application_name:
3700+ application_name = application_name.replace("/", " ")
3701+ else:
3702+ application_name = os.path.dirname(app.path)
3703+ dotProject = os.path.join(app.path, '.project')
3704+ dotClasspath = os.path.join(app.path, '.classpath')
3705+ dotSettings = os.path.join(app.path, '.settings')
3706+ eclipse = os.path.join(app.path, 'eclipse')
3707+ if os.path.exists(eclipse):
3708+ shutil.rmtree(eclipse)
3709+ if os.name == 'nt':
3710+ time.sleep(1)
3711+
3712+ if os.path.exists(dotSettings):
3713+ shutil.rmtree(dotSettings)
3714+ if os.name == 'nt':
3715+ time.sleep(1)
3716+
3717+ shutil.copyfile(os.path.join(play_env["basedir"], 'resources/eclipse/.project'), dotProject)
3718+ shutil.copyfile(os.path.join(play_env["basedir"], 'resources/eclipse/.classpath'), dotClasspath)
3719+ shutil.copytree(os.path.join(play_env["basedir"], 'resources/eclipse'), eclipse)
3720+ shutil.copytree(os.path.join(play_env["basedir"], 'resources/eclipse/.settings'), dotSettings)
3721+ replaceAll(dotProject, r'%PROJECT_NAME%', application_name)
3722+
3723+ playJarPath = os.path.join(play_env["basedir"], 'framework','play.jar')
3724+ playSourcePath = os.path.dirname(playJarPath)
3725+ if os.name == 'nt':
3726+ playSourcePath=playSourcePath.replace('\\','/').capitalize()
3727+
3728+ cpJarToSource = {}
3729+ for el in classpath:
3730+ if os.path.basename(el) != "conf" and el.endswith('-sources.jar'):
3731+ cpJarToSource[el.replace('-sources', '')] = el
3732+
3733+ cpXML = ""
3734+ for el in classpath:
3735+ if os.path.basename(el) != "conf":
3736+ if el == playJarPath:
3737+ cpXML += '<classpathentry kind="lib" path="%s" sourcepath="%s" />\n\t' % (os.path.normpath(el) , playSourcePath)
3738+ else:
3739+ if cpJarToSource.has_key(el):
3740+ cpXML += '<classpathentry kind="lib" path="%s" sourcepath="%s"/>\n\t' % (os.path.normpath(el), cpJarToSource[el])
3741+ else:
3742+ cpXML += '<classpathentry kind="lib" path="%s"/>\n\t' % os.path.normpath(el)
3743+
3744+ replaceAll(dotClasspath, r'%PROJECTCLASSPATH%', cpXML)
3745+
3746+ if len(modules):
3747+ lXML = ""
3748+ cXML = ""
3749+ for module in modules:
3750+ lXML += '<link><name>%s</name><type>2</type><location>%s</location></link>\n' % (os.path.basename(module), os.path.join(module, 'app').replace('\\', '/'))
3751+ if os.path.exists(os.path.join(module, "conf")):
3752+ lXML += '<link><name>conf/%s</name><type>2</type><location>%s/conf</location></link>\n' % (os.path.basename(module), module.replace('\\', '/'))
3753+ if os.path.exists(os.path.join(module, "public")):
3754+ lXML += '<link><name>public/%s</name><type>2</type><location>%s/public</location></link>\n' % (os.path.basename(module), module.replace('\\', '/'))
3755+ cXML += '<classpathentry kind="src" path="%s"/>' % (os.path.basename(module))
3756+ replaceAll(dotProject, r'%LINKS%', '<linkedResources>%s</linkedResources>' % lXML)
3757+ replaceAll(dotClasspath, r'%MODULES%', cXML)
3758+ else:
3759+ replaceAll(dotProject, r'%LINKS%', '')
3760+ replaceAll(dotClasspath, r'%MODULES%', '')
3761+
3762+ replaceAll(os.path.join(app.path, 'eclipse/debug.launch'), r'%PROJECT_NAME%', application_name)
3763+ replaceAll(os.path.join(app.path, 'eclipse/debug.launch'), r'%PLAY_BASE%', play_env["basedir"])
3764+ replaceAll(os.path.join(app.path, 'eclipse/debug.launch'), r'%PLAY_ID%', play_env["id"])
3765+ replaceAll(os.path.join(app.path, 'eclipse/debug.launch'), r'%JPDA_PORT%', str(app.jpda_port))
3766+
3767+ replaceAll(os.path.join(app.path, 'eclipse/test.launch'), r'%PROJECT_NAME%', application_name)
3768+ replaceAll(os.path.join(app.path, 'eclipse/test.launch'), r'%PLAY_BASE%', play_env["basedir"])
3769+ replaceAll(os.path.join(app.path, 'eclipse/test.launch'), r'%PLAY_ID%', play_env["id"])
3770+ replaceAll(os.path.join(app.path, 'eclipse/test.launch'), r'%JPDA_PORT%', str(app.jpda_port))
3771+
3772+ replaceAll(os.path.join(app.path, 'eclipse/connect.launch'), r'%PROJECT_NAME%', application_name)
3773+ replaceAll(os.path.join(app.path, 'eclipse/connect.launch'), r'%JPDA_PORT%', str(app.jpda_port))
3774+
3775+ os.rename(os.path.join(app.path, 'eclipse/connect.launch'), os.path.join(app.path, 'eclipse/Connect JPDA to %s.launch' % application_name))
3776+ os.rename(os.path.join(app.path, 'eclipse/test.launch'), os.path.join(app.path, 'eclipse/Test %s.launch' % application_name))
3777+ os.rename(os.path.join(app.path, 'eclipse/debug.launch'), os.path.join(app.path, 'eclipse/%s.launch' % application_name))
3778+
3779+ # Module-specific modifications
3780+ for module in modules:
3781+ commands = os.path.join(module, 'commands.py')
3782+ if os.path.exists(commands):
3783+ execfile(commands)
3784+
3785+ print "~ OK, the application is ready for eclipse"
3786+ print "~ Use File/Import/General/Existing project to import %s into eclipse" % os.path.normpath(app.path)
3787+ print "~"
3788+ print "~ Use eclipsify again when you want to update eclipse configuration files."
3789+ print "~ However, it's often better to delete and re-import the project into your workspace since eclipse keeps dirty caches..."
3790+ print "~"
3791
3792=== added file 'framework/pym/play/commands/help.py'
3793--- framework/pym/play/commands/help.py 1970-01-01 00:00:00 +0000
3794+++ framework/pym/play/commands/help.py 2010-09-16 22:21:08 +0000
3795@@ -0,0 +1,55 @@
3796+# Display help
3797+
3798+import sys, os
3799+
3800+COMMANDS = ['help']
3801+
3802+HELP = {
3803+ 'help': 'Display help on a specific command'
3804+}
3805+
3806+def execute(**kargs):
3807+ command = kargs.get("command")
3808+ app = kargs.get("app")
3809+ args = kargs.get("args")
3810+ play_env = kargs.get("env")
3811+ cmdloader = kargs.get("cmdloader")
3812+
3813+ if len(sys.argv) == 3:
3814+ cmd = sys.argv[2]
3815+ help_file = os.path.join(play_env["basedir"], 'documentation', 'commands', 'cmd-%s.txt' % cmd)
3816+ if os.path.exists(help_file):
3817+ print open(help_file, 'r').read()
3818+ else:
3819+ print '~ Oops, command \'%s\' not found. Try just \'play help\' to list all commands.' % cmd
3820+ print '~'
3821+ sys.exit(-1)
3822+ else:
3823+ main_help(cmdloader.commands, play_env)
3824+
3825+def main_help(commands, play_env):
3826+ modules_commands = []
3827+ print "~ For all commands, if the application is not specified, the current directory is used"
3828+ print "~ Use 'play help cmd' to get more help on a specific command"
3829+ print "~"
3830+ print "~ Core commands:"
3831+ print "~ ~~~~~~~~~~~~~~"
3832+ for cmd in sorted(commands):
3833+ if not isCore(commands[cmd], play_env):
3834+ modules_commands.append(cmd)
3835+ continue
3836+ if 'HELP' in dir(commands[cmd]) and cmd in commands[cmd].HELP:
3837+ print "~ " + cmd + (' ' * (16 - len(cmd))) + commands[cmd].HELP[cmd]
3838+ if len(modules_commands) > 0:
3839+ print "~"
3840+ print "~ Modules commands:"
3841+ print "~ ~~~~~~~~~~~~~~~~~"
3842+ for cmd in modules_commands:
3843+ if 'HELP' in dir(commands[cmd]) and cmd in commands[cmd].HELP:
3844+ print "~ " + cmd + (' ' * (20 - len(cmd))) + commands[cmd].HELP[cmd]
3845+ print "~"
3846+ print "~ Also refer to documentation at http://www.playframework.org/documentation"
3847+ print "~"
3848+
3849+def isCore(mod, play_env):
3850+ return mod.__file__.find(play_env["basedir"]) == 0
3851
3852=== added file 'framework/pym/play/commands/intellij.py'
3853--- framework/pym/play/commands/intellij.py 1970-01-01 00:00:00 +0000
3854+++ framework/pym/play/commands/intellij.py 2010-09-16 22:21:08 +0000
3855@@ -0,0 +1,40 @@
3856+import os, os.path
3857+import shutil
3858+
3859+from play.utils import *
3860+
3861+COMMANDS = ['idealize', 'idea']
3862+
3863+HELP = {
3864+ 'idealize': 'Create all IntelliJ Idea configuration files'
3865+}
3866+
3867+def execute(**kargs):
3868+ command = kargs.get("command")
3869+ app = kargs.get("app")
3870+ args = kargs.get("args")
3871+ play_env = kargs.get("env")
3872+
3873+ app.check()
3874+ modules = app.modules()
3875+ classpath = app.getClasspath()
3876+
3877+ application_name = app.readConf('application.name')
3878+ imlFile = os.path.join(app.path, application_name + '.iml')
3879+ shutil.copyfile(os.path.join(play_env["basedir"], 'resources/idea/imlTemplate.xml'), imlFile)
3880+ cpXML = ""
3881+
3882+ replaceAll(imlFile, r'%PLAYHOME%', play_env["basedir"].replace('\\', '/'))
3883+
3884+ if len(modules):
3885+ lXML = ""
3886+ cXML = ""
3887+ for module in modules:
3888+ lXML += ' <content url="file://%s">\n <sourceFolder url="file://%s" isTestSource="false" />\n </content>\n' % (module, os.path.join(module, 'app').replace('\\', '/'))
3889+ replaceAll(imlFile, r'%LINKS%', lXML)
3890+ else:
3891+ replaceAll(imlFile, r'%LINKS%', '')
3892+
3893+ print "~ OK, the application is ready for Intellij Idea"
3894+ print "~ Use File/New Module/Import Existing module"
3895+ print "~"
3896
3897=== added file 'framework/pym/play/commands/javadoc.py'
3898--- framework/pym/play/commands/javadoc.py 1970-01-01 00:00:00 +0000
3899+++ framework/pym/play/commands/javadoc.py 2010-09-16 22:21:08 +0000
3900@@ -0,0 +1,43 @@
3901+import os, os.path
3902+import shutil
3903+import subprocess
3904+
3905+from play.utils import *
3906+
3907+COMMANDS = ['javadoc', 'jd']
3908+
3909+HELP = {
3910+ 'javadoc': 'Generate your application Javadoc'
3911+}
3912+
3913+def execute(**kargs):
3914+ command = kargs.get("command")
3915+ app = kargs.get("app")
3916+ args = kargs.get("args")
3917+ play_env = kargs.get("env")
3918+
3919+ app.check()
3920+ modules = app.modules()
3921+ if not os.environ.has_key('JAVA_HOME'):
3922+ javadoc_path = "javadoc"
3923+ else:
3924+ javadoc_path = os.path.normpath("%s/bin/javadoc" % os.environ['JAVA_HOME'])
3925+
3926+ fileList = []
3927+ def add_java_files(app_path):
3928+ for root, subFolders, files in os.walk(os.path.join(app_path, 'app')):
3929+ for file in files:
3930+ if file.endswith(".java"):
3931+ fileList.append(os.path.join(root, file))
3932+ add_java_files(app.path)
3933+ for module in modules:
3934+ add_java_files(os.path.normpath(module))
3935+ outdir = os.path.join(app.path, 'javadoc')
3936+ sout = open(os.path.join(app.log_path(), 'javadoc.log'), 'w')
3937+ serr = open(os.path.join(app.log_path(), 'javadoc.err'), 'w')
3938+ if (os.path.isdir(outdir)):
3939+ shutil.rmtree(outdir)
3940+ javadoc_cmd = [javadoc_path, '-classpath', app.cp_args(), '-d', outdir] + fileList
3941+ print "Generating Javadoc in " + outdir + "..."
3942+ subprocess.call(javadoc_cmd, env=os.environ, stdout=sout, stderr=serr)
3943+ print "Done! You can open " + os.path.join(outdir, 'overview-tree.html') + " in your browser."
3944
3945=== added file 'framework/pym/play/commands/modulesrepo.py'
3946--- framework/pym/play/commands/modulesrepo.py 1970-01-01 00:00:00 +0000
3947+++ framework/pym/play/commands/modulesrepo.py 2010-09-16 22:21:08 +0000
3948@@ -0,0 +1,447 @@
3949+import os
3950+import sys
3951+import re
3952+import zipfile
3953+import urllib2
3954+import shutil
3955+import string
3956+import imp
3957+import time
3958+import urllib
3959+
3960+from play.utils import *
3961+
3962+NM = ['new-module']
3963+LM = ['list-modules', 'lm']
3964+BM = ['build-module', 'bm']
3965+AM = ['add']
3966+IM = ['install']
3967+
3968+COMMANDS = NM + LM + BM + IM + AM
3969+
3970+HELP = {
3971+ 'build-module': "Build and package a module",
3972+ 'list-modules': "List modules available from the central modules repository",
3973+ 'install': "Install a module"
3974+}
3975+
3976+DEFAULT_REPO = 'http://www.playframework.org'
3977+
3978+def load_module(name):
3979+ base = os.path.normpath(os.path.dirname(os.path.realpath(sys.argv[0])))
3980+ mod_desc = imp.find_module(name, [os.path.join(base, 'framework/pym')])
3981+ return imp.load_module(name, mod_desc[0], mod_desc[1], mod_desc[2])
3982+
3983+json = load_module('simplejson')
3984+
3985+repositories = []
3986+
3987+def execute(**kargs):
3988+ global repositories
3989+
3990+ command = kargs.get("command")
3991+ app = kargs.get("app")
3992+ args = kargs.get("args")
3993+ env = kargs.get("env")
3994+
3995+ repositories = get_repositories(env['basedir'])
3996+
3997+ if command in NM:
3998+ new(app, args, env)
3999+ elif command in LM:
4000+ list(app, args)
4001+ elif command in BM:
4002+ build(app, args, env)
4003+ elif command in IM:
4004+ install(app, args, env)
4005+ elif command in AM:
4006+ add(app, args, env)
4007+
4008+def get_repositories(play_base):
4009+ repopath = os.path.join(play_base, 'repositories')
4010+ if os.path.exists(repopath):
4011+ repos = []
4012+ f = file(repopath)
4013+ for line in f:
4014+ if not re.match("^\s*#", line) and not line.strip() == "":
4015+ repos.append(line.strip())
4016+ if len(repos) > 0:
4017+ return repos
4018+ return [DEFAULT_REPO]
4019+
4020+class Downloader(object):
4021+ before = .0
4022+ history = []
4023+ cycles = 0
4024+ average = lambda self: sum(self.history) / (len(self.history) or 1)
4025+
4026+ def __init__(self, width=55):
4027+ self.width = width
4028+ self.kibi = lambda bits: bits / 2 ** 10
4029+ self.proc = lambda a, b: a / (b * 0.01)
4030+
4031+ def retrieve(self, url, destination, callback=None):
4032+ self.size = 0
4033+ time.clock()
4034+ try: urllib.urlretrieve(url, destination, self.progress)
4035+ except KeyboardInterrupt:
4036+ print '\n~ Download cancelled'
4037+ print '~'
4038+ for i in range(5):
4039+ try:
4040+ os.remove(destination)
4041+ break
4042+ except:
4043+ time.sleep(.1)
4044+ else: raise
4045+ if callback: callback()
4046+ sys.exit()
4047+ print ''
4048+ return self.size
4049+
4050+ def progress(self, blocks, blocksize, filesize):
4051+ self.cycles += 1
4052+ bits = min(blocks*blocksize, filesize)
4053+ done = self.proc(bits, filesize) if bits != filesize else 100
4054+ bar = self.bar(done)
4055+ if not self.cycles % 3 and bits != filesize:
4056+ now = time.clock()
4057+ elapsed = now-self.before
4058+ if elapsed:
4059+ speed = self.kibi(blocksize * 3 / elapsed)
4060+ self.history.append(speed)
4061+ self.history = self.history[-4:]
4062+ self.before = now
4063+ average = round(sum(self.history[-4:]) / 4, 1)
4064+ self.size = self.kibi(bits)
4065+ print '\r~ [%s] %s KiB/s ' % (bar, str(average)),
4066+
4067+ def bar(self, done):
4068+ span = self.width * done * 0.01
4069+ offset = len(str(int(done))) - .99
4070+ result = ('%d%%' % (done,)).center(self.width)
4071+ return result.replace(' ', '-', int(span - offset))
4072+
4073+class Unzip:
4074+ def __init__(self, verbose = False, percent = 10):
4075+ self.verbose = verbose
4076+ self.percent = percent
4077+
4078+ def extract(self, file, dir):
4079+ if not dir.endswith(':') and not os.path.exists(dir):
4080+ os.mkdir(dir)
4081+ zf = zipfile.ZipFile(file)
4082+ # create directory structure to house files
4083+ self._createstructure(file, dir)
4084+ num_files = len(zf.namelist())
4085+ percent = self.percent
4086+ divisions = 100 / percent
4087+ perc = int(num_files / divisions)
4088+ # extract files to directory structure
4089+ for i, name in enumerate(zf.namelist()):
4090+ if self.verbose == True:
4091+ print "Extracting %s" % name
4092+ elif perc > 0 and (i % perc) == 0 and i > 0:
4093+ complete = int (i / perc) * percent
4094+ if not name.endswith('/'):
4095+ outfile = open(os.path.join(dir, name), 'wb')
4096+ outfile.write(zf.read(name))
4097+ outfile.flush()
4098+ outfile.close()
4099+
4100+ def _createstructure(self, file, dir):
4101+ self._makedirs(self._listdirs(file), dir)
4102+
4103+ def _makedirs(self, directories, basedir):
4104+ """ Create any directories that don't currently exist """
4105+ for dir in directories:
4106+ curdir = os.path.join(basedir, dir)
4107+ if not os.path.exists(curdir):
4108+ os.makedirs(curdir)
4109+
4110+ def _listdirs(self, file):
4111+ """ Grabs all the directories in the zip structure
4112+ This is necessary to create the structure before trying
4113+ to extract the file to it. """
4114+ zf = zipfile.ZipFile(file)
4115+ dirs = []
4116+ for name in zf.namelist():
4117+ dn = os.path.dirname(name)
4118+ dirs.append(dn)
4119+ dirs.sort()
4120+ return dirs
4121+
4122+def new(app, args, play_env):
4123+ if os.path.exists(app.path):
4124+ print "~ Oops. %s already exists" % app.path
4125+ print "~"
4126+ sys.exit(-1)
4127+
4128+ print "~ The new module will be created in %s" % os.path.normpath(app.path)
4129+ print "~"
4130+ application_name = os.path.basename(app.path)
4131+ shutil.copytree(os.path.join(play_env["basedir"], 'resources/module-skel'), app.path)
4132+ # check_application()
4133+ replaceAll(os.path.join(app.path, 'build.xml'), r'%MODULE%', application_name)
4134+ replaceAll(os.path.join(app.path, 'commands.py'), r'%MODULE%', application_name)
4135+ replaceAll(os.path.join(app.path, 'conf/messages'), r'%MODULE%', application_name)
4136+ replaceAll(os.path.join(app.path, 'conf/routes'), r'%MODULE%', application_name)
4137+ replaceAll(os.path.join(app.path, 'conf/routes'), r'%MODULE_LOWERCASE%', string.lower(application_name))
4138+ os.mkdir(os.path.join(app.path, 'app/controllers/%s' % application_name))
4139+ os.mkdir(os.path.join(app.path, 'app/models/%s' % application_name))
4140+ os.mkdir(os.path.join(app.path, 'app/views/%s' % application_name))
4141+ os.mkdir(os.path.join(app.path, 'app/views/tags/%s' % application_name))
4142+ os.mkdir(os.path.join(app.path, 'src/play/modules/%s' % application_name))
4143+
4144+ print "~ OK, the module is created."
4145+ print "~ Start using it by adding this line in the application.conf modules list: "
4146+ print "~ module.%s=%s" % (application_name, os.path.normpath(app.path))
4147+ print "~"
4148+ print "~ Have fun!"
4149+ print "~"
4150+
4151+def list(app, args):
4152+ print "~ You can also browse this list online at:"
4153+ for repo in repositories:
4154+ print "~ %s/modules" % repo
4155+ print "~"
4156+
4157+ modules_list = load_module_list()
4158+
4159+ for mod in modules_list:
4160+ print "~ [%s]" % mod['name']
4161+ print "~ %s" % mod['fullname']
4162+ print "~ %s/modules/%s" % (mod['server'], mod['name'])
4163+
4164+ vl = ''
4165+ i = 0
4166+ for v in mod['versions']:
4167+ vl += v["version"]
4168+ i = i+1
4169+ if i < len(mod['versions']):
4170+ vl += ', '
4171+
4172+ if vl:
4173+ print "~ Versions: %s" % vl
4174+ else:
4175+ print "~ (No versions released yet)"
4176+ print "~"
4177+
4178+ print "~ To install one of these modules use:"
4179+ print "~ play install module-version (eg: play install scala-1.0)"
4180+ print "~"
4181+ print "~ Or you can just install the default release of a module using:"
4182+ print "~ play install module (eg: play install scala)"
4183+ print "~"
4184+
4185+def build(app, args, env):
4186+ ftb = env["basedir"]
4187+
4188+ try:
4189+ optlist, args = getopt.getopt(args, '', ['framework='])
4190+ for o, a in optlist:
4191+ if o in ('--framework'):
4192+ ftb = a
4193+ except getopt.GetoptError, err:
4194+ print "~ %s" % str(err)
4195+ print "~ "
4196+ sys.exit(-1)
4197+
4198+ version = raw_input("~ What is the module version number? ")
4199+ fwkMatch = raw_input("~ What are the playframework versions required? ")
4200+
4201+ build_file = os.path.join(app.path, 'build.xml')
4202+ if os.path.exists(build_file):
4203+ print "~"
4204+ print "~ Building..."
4205+ print "~"
4206+ os.system('ant -f %s -Dplay.path=%s' % (build_file, ftb) )
4207+ print "~"
4208+
4209+ mv = '%s-%s' % (os.path.basename(app.path), version)
4210+ print("~ Packaging %s ... " % mv)
4211+
4212+ dist_dir = os.path.join(app.path, 'dist')
4213+ if os.path.exists(dist_dir):
4214+ shutil.rmtree(dist_dir)
4215+ os.mkdir(dist_dir)
4216+
4217+ manifest = os.path.join(app.path, 'manifest')
4218+ manifestF = open(manifest, 'w')
4219+ manifestF.write('version=%s\nframeworkVersions=%s\n' % (version, fwkMatch))
4220+ manifestF.close()
4221+
4222+ zip = zipfile.ZipFile(os.path.join(dist_dir, '%s.zip' % mv), 'w', zipfile.ZIP_STORED)
4223+ for (dirpath, dirnames, filenames) in os.walk(app.path):
4224+ if dirpath == dist_dir:
4225+ continue
4226+ if dirpath.find('/.') > -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'):
4227+ continue
4228+ for file in filenames:
4229+ if file.find('~') > -1 or file.endswith('.iml') or file.startswith('.'):
4230+ continue
4231+ zip.write(os.path.join(dirpath, file), os.path.join(dirpath[len(app.path):], file))
4232+ zip.close()
4233+
4234+ os.remove(manifest)
4235+
4236+ print "~"
4237+ print "~ Done!"
4238+ print "~ Package is available at %s" % os.path.join(dist_dir, '%s.zip' % mv)
4239+ print "~"
4240+
4241+def install(app, args, env):
4242+ if len(sys.argv) < 3:
4243+ help_file = os.path.join(env["basedir"], 'documentation/commands/cmd-install.txt')
4244+ print open(help_file, 'r').read()
4245+ sys.exit(0)
4246+
4247+ name = cmd = sys.argv[2]
4248+ groups = re.match(r'^([a-zA-Z0-9]+)([-](.*))?$', name)
4249+ module = groups.group(1)
4250+ version = groups.group(3)
4251+
4252+ modules_list = load_module_list()
4253+ fetch = None
4254+
4255+ for mod in modules_list:
4256+ if mod['name'] == module:
4257+ for v in mod['versions']:
4258+ if version == None and v['isDefault']:
4259+ print '~ Will install %s-%s' % (module, v['version'])
4260+ print '~ This module is compatible with: %s' % v['matches']
4261+ ok = raw_input('~ Do you want to install this version (y/n)? ')
4262+ if not ok == 'y':
4263+ print '~'
4264+ sys.exit(-1)
4265+ print '~ Installing module %s-%s...' % (module, v['version'])
4266+ fetch = '%s/modules/%s-%s.zip' % (mod['server'], module, v['version'])
4267+ break
4268+ if version == v['version']:
4269+ print '~ Will install %s-%s' % (module, v['version'])
4270+ print '~ This module is compatible with: %s' % v['matches']
4271+ ok = raw_input('~ Do you want to install this version (y/n)? ')
4272+ if not ok == 'y':
4273+ print '~'
4274+ sys.exit(-1)
4275+
4276+ print '~ Installing module %s-%s...' % (module, v['version'])
4277+ fetch = '%s/modules/%s-%s.zip' % (mod['server'], module, v['version'])
4278+ break
4279+
4280+ if fetch == None:
4281+ print '~ No module found \'%s\'' % name
4282+ print '~ Try play list-modules to get the modules list'
4283+ print '~'
4284+ sys.exit(-1)
4285+
4286+ archive = os.path.join(env["basedir"], 'modules/%s-%s.zip' % (module, v['version']))
4287+ if os.path.exists(archive):
4288+ os.remove(archive)
4289+
4290+ print '~'
4291+ print '~ Fetching %s' % fetch
4292+ Downloader().retrieve(fetch, archive)
4293+
4294+ if not os.path.exists(archive):
4295+ print '~ Oops, file does not exist'
4296+ print '~'
4297+ sys.exist(-1)
4298+
4299+ print '~ Unzipping...'
4300+
4301+ if os.path.exists(os.path.join(env["basedir"], 'modules/%s-%s' % (module, v['version']))):
4302+ shutil.rmtree(os.path.join(env["basedir"], 'modules/%s-%s' % (module, v['version'])))
4303+ os.mkdir(os.path.join(env["basedir"], 'modules/%s-%s' % (module, v['version'])))
4304+
4305+ Unzip().extract(archive, os.path.join(env["basedir"], 'modules/%s-%s' % (module, v['version'])))
4306+ os.remove(archive)
4307+ print '~'
4308+ print '~ Module %s-%s is installed!' % (module, v['version'])
4309+ print '~ You can now use it by add adding this line to application.conf file:'
4310+ print '~'
4311+ print '~ module.%s=${play.path}/modules/%s-%s' % (module, module, v['version'])
4312+ print '~'
4313+ sys.exit(0)
4314+
4315+def add(app, args, env):
4316+ app.check()
4317+
4318+ try:
4319+ optlist, args = getopt.getopt(args, '', ['module='])
4320+ for o, a in optlist:
4321+ if o in ('--module'):
4322+ m = a
4323+ except getopt.GetoptError, err:
4324+ print "~ %s" % str(err)
4325+ print "~ "
4326+ sys.exit(-1)
4327+
4328+ if m is None:
4329+ print "~ Usage: play add --module=<modulename>"
4330+ print "~ "
4331+ sys.exit(-1)
4332+
4333+ appConf = os.path.join(app.path, 'conf/application.conf')
4334+ if not fileHas(appConf, '# ---- MODULES ----'):
4335+ print "~ Line '---- MODULES ----' missing in your application.conf. Add it to use this command."
4336+ print "~ "
4337+ sys.exit(-1)
4338+
4339+ mn = m
4340+ if mn.find('-') > 0:
4341+ mn = mn[:mn.find('-')]
4342+
4343+ if mn in app.module_names():
4344+ print "~ Module %s already declared in application.conf, not doing anything." % mn
4345+ print "~ "
4346+ sys.exit(-1)
4347+
4348+ replaceAll(appConf, r'# ---- MODULES ----', '# ---- MODULES ----\nmodule.%s=${play.path}/modules/%s' % (mn, m) )
4349+ print "~ Module %s add to application %s." % (mn, app.name())
4350+ print "~ "
4351+
4352+def load_module_list():
4353+
4354+ def addServer(module, server):
4355+ module['server'] = server
4356+ return module
4357+
4358+ def any(arr, func):
4359+ for x in arr:
4360+ if func(x): return True
4361+ return False
4362+
4363+ modules = None
4364+ rev = repositories[:] # clone
4365+ rev.reverse()
4366+ for repo in rev:
4367+ result = load_modules_from(repo)
4368+ if modules == None:
4369+ modules = map(lambda m: addServer(m, repo), result['modules'])
4370+ else:
4371+ for module in result['modules']:
4372+ if not any(modules, lambda m: m['name'] == module['name']):
4373+ modules.append(addServer(module, repo))
4374+ return modules
4375+
4376+def load_modules_from(modules_server):
4377+ try:
4378+ url = '%s/modules' % modules_server
4379+ proxy_handler = urllib2.ProxyHandler({})
4380+ req = urllib2.Request(url)
4381+ req.add_header('Accept', 'application/json')
4382+ opener = urllib2.build_opener(proxy_handler)
4383+ result = opener.open(req)
4384+ return json.loads(result.read())
4385+ except urllib2.HTTPError, e:
4386+ print "~ Oops,"
4387+ print "~ Cannot fetch the modules list from %s (%s)..." % (url, e.code)
4388+ print "~"
4389+ sys.exit(-1)
4390+ except urllib2.URLError, e:
4391+ print "~ Oops,"
4392+ print "~ Cannot fetch the modules list from %s ..." % (url)
4393+ print "~"
4394+ sys.exit(-1)
4395+
4396
4397=== added file 'framework/pym/play/commands/netbeans.py'
4398--- framework/pym/play/commands/netbeans.py 1970-01-01 00:00:00 +0000
4399+++ framework/pym/play/commands/netbeans.py 2010-09-16 22:21:08 +0000
4400@@ -0,0 +1,50 @@
4401+import os, os.path
4402+import shutil
4403+
4404+from play.utils import *
4405+
4406+COMMANDS = ['netbeansify', 'nb']
4407+
4408+HELP = {
4409+ 'netbeansify': 'Create all NetBeans configuration files'
4410+}
4411+
4412+def execute(**kargs):
4413+ command = kargs.get("command")
4414+ app = kargs.get("app")
4415+ args = kargs.get("args")
4416+ play_env = kargs.get("env")
4417+
4418+ app.check()
4419+ classpath = app.getClasspath()
4420+ modules = app.modules()
4421+ application_name = app.readConf('application.name')
4422+ if not application_name:
4423+ application_name = os.path.dirname(app.path)
4424+ nbproject = os.path.join(app.path, 'nbproject')
4425+ if os.path.exists(nbproject):
4426+ shutil.rmtree(nbproject)
4427+ if os.name == 'nt':
4428+ time.sleep(1)
4429+ shutil.copytree(os.path.join(play_env["basedir"], 'resources/_nbproject'), nbproject)
4430+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%APPLICATION_NAME%', application_name)
4431+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%ANT_SCRIPT%', os.path.normpath(os.path.join(play_env["basedir"], 'framework/build.xml')))
4432+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%APPLICATION_PATH%', os.path.normpath(app.path))
4433+ if os.name == 'nt':
4434+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%PLAY_CLASSPATH%', ';'.join(classpath + ['nbproject\\classes']))
4435+ else:
4436+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%PLAY_CLASSPATH%', ':'.join(classpath + ['nbproject/classes']))
4437+ mr = ""
4438+ for module in modules:
4439+ mr += "<package-root>%s</package-root>" % os.path.normpath(os.path.join(module, 'app'))
4440+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%MODULES%', mr)
4441+ mr = ""
4442+ for dir in os.listdir(app.path):
4443+ if os.path.isdir(os.path.join(app.path, dir)) and dir not in ['app', 'conf', 'test', 'test-result', 'public', 'tmp', 'logs', 'nbproject', 'lib']:
4444+ mr = '<source-folder style="tree"><label>%s</label><location>%s</location></source-folder>' % (dir, dir)
4445+ replaceAll(os.path.join(nbproject, 'project.xml'), r'%MORE%', mr)
4446+ print "~ OK, the application is ready for netbeans"
4447+ print "~ Just open %s as a netbeans project" % os.path.normpath(app.path)
4448+ print "~"
4449+ print "~ Use netbeansify again when you want to update netbeans configuration files, then close and open you project again."
4450+ print "~"
4451
4452=== added file 'framework/pym/play/commands/precompile.py'
4453--- framework/pym/play/commands/precompile.py 1970-01-01 00:00:00 +0000
4454+++ framework/pym/play/commands/precompile.py 2010-09-16 22:21:08 +0000
4455@@ -0,0 +1,31 @@
4456+import os, os.path
4457+import shutil
4458+import subprocess
4459+
4460+from play.utils import *
4461+
4462+COMMANDS = ['precompile']
4463+
4464+HELP = {
4465+ 'precompile': 'Precompile all Java sources and templates to speed up application start-up'
4466+}
4467+
4468+def execute(**kargs):
4469+ command = kargs.get("command")
4470+ app = kargs.get("app")
4471+ args = kargs.get("args")
4472+ play_env = kargs.get("env")
4473+
4474+ app.check()
4475+ java_cmd = app.java_cmd(args)
4476+ if os.path.exists(os.path.join(app.path, 'tmp')):
4477+ shutil.rmtree(os.path.join(app.path, 'tmp'))
4478+ if os.path.exists(os.path.join(app.path, 'precompiled')):
4479+ shutil.rmtree(os.path.join(app.path, 'precompiled'))
4480+ java_cmd.insert(2, '-Dprecompile=yes')
4481+ try:
4482+ subprocess.call(java_cmd, env=os.environ)
4483+ except OSError:
4484+ print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). "
4485+ sys.exit(-1)
4486+ print
4487
4488=== added file 'framework/pym/play/commands/secret.py'
4489--- framework/pym/play/commands/secret.py 1970-01-01 00:00:00 +0000
4490+++ framework/pym/play/commands/secret.py 2010-09-16 22:21:08 +0000
4491@@ -0,0 +1,17 @@
4492+from play.utils import *
4493+
4494+COMMANDS = ['secret']
4495+
4496+HELP = {
4497+ 'secret': 'Generate a new secret key'
4498+}
4499+
4500+def execute(**kargs):
4501+ app = kargs.get("app")
4502+
4503+ app.check()
4504+ print "~ Generating the secret key..."
4505+ sk = secretKey()
4506+ replaceAll(os.path.join(app.path, 'conf', 'application.conf'), r'application.secret=.*', 'application.secret=%s' % sk, True)
4507+ print "~ Keep the secret : %s" % sk
4508+ print "~"
4509
4510=== added file 'framework/pym/play/commands/status.py'
4511--- framework/pym/play/commands/status.py 1970-01-01 00:00:00 +0000
4512+++ framework/pym/play/commands/status.py 2010-09-16 22:21:08 +0000
4513@@ -0,0 +1,68 @@
4514+import os, os.path
4515+import shutil
4516+import getopt
4517+import hmac
4518+import urllib2
4519+from hashlib import sha1 as sha
4520+
4521+from play.utils import *
4522+
4523+COMMANDS = ['status', 'st']
4524+
4525+HELP = {
4526+ 'status': 'Display the running application\'s status'
4527+}
4528+
4529+def execute(**kargs):
4530+ app = kargs.get("app")
4531+ args = kargs.get("args")
4532+ play_env = kargs.get("env")
4533+
4534+ url = ''
4535+ secret_key = ''
4536+
4537+ try:
4538+ optlist, args2 = getopt.getopt(args, '', ['url=', 'secret='])
4539+ for o, a in optlist:
4540+ if o in ('--url'):
4541+ if a.endswith('/'):
4542+ url = a + '@status'
4543+ else:
4544+ url = a + '/@status'
4545+ if o in ('--secret'):
4546+ secret_key = a
4547+ except getopt.GetoptError, err:
4548+ print "~ %s" % str(err)
4549+ print "~ "
4550+ sys.exit(-1)
4551+
4552+ if not url or not secret_key:
4553+ app.check()
4554+ if not url:
4555+ http_port = int(app.readConf('http.port'))
4556+ url = 'http://localhost:%s/@status' % http_port
4557+ if not secret_key:
4558+ secret_key = app.readConf('application.secret')
4559+
4560+ hm = hmac.new(secret_key, '@status', sha)
4561+ authorization = hm.hexdigest()
4562+
4563+ try:
4564+ proxy_handler = urllib2.ProxyHandler({})
4565+ req = urllib2.Request(url)
4566+ req.add_header('Authorization', authorization)
4567+ opener = urllib2.build_opener(proxy_handler)
4568+ status = opener.open(req);
4569+ print '~ Status from %s,' % url
4570+ print '~'
4571+ print status.read()
4572+ print '~'
4573+ except urllib2.HTTPError, e:
4574+ print "~ Cannot retrieve the application status... (%s)" % (e.code)
4575+ print "~"
4576+ sys.exit(-1)
4577+ except urllib2.URLError, e:
4578+ print "~ Cannot contact the application..."
4579+ print "~"
4580+ sys.exit(-1)
4581+ print
4582
4583=== added file 'framework/pym/play/commands/war.py'
4584--- framework/pym/play/commands/war.py 1970-01-01 00:00:00 +0000
4585+++ framework/pym/play/commands/war.py 2010-09-16 22:21:08 +0000
4586@@ -0,0 +1,46 @@
4587+import sys
4588+import os
4589+import getopt
4590+import shutil
4591+import zipfile
4592+
4593+from play.utils import *
4594+
4595+COMMANDS = ["war"]
4596+
4597+HELP = {
4598+ 'war': 'Export the application as a standalone WAR archive'
4599+}
4600+
4601+def execute(**kargs):
4602+ command = kargs.get("command")
4603+ app = kargs.get("app")
4604+ args = kargs.get("args")
4605+ env = kargs.get("env")
4606+
4607+ war_path = None
4608+ war_zip_path = None
4609+ try:
4610+ optlist, args = getopt.getopt(args, 'o:', ['output=', 'zip'])
4611+ for o, a in optlist:
4612+ if o in ('-o', '--output'):
4613+ war_path = os.path.normpath(os.path.abspath(a))
4614+ for o, a in optlist:
4615+ if o in ('--zip'):
4616+ war_zip_path = war_path + '.war'
4617+ except getopt.GetoptError, err:
4618+ print "~ %s" % str(err)
4619+ print "~ Please specify a path where to generate the WAR, using the -o or --output option"
4620+ print "~ "
4621+ sys.exit(-1)
4622+
4623+ package_as_war(app, env, war_path, war_zip_path)
4624+
4625+ print "~ Done !"
4626+ print "~"
4627+ print "~ You can now load %s as a standard WAR into your servlet container" % (os.path.normpath(war_path))
4628+ print "~ You can't use play standard commands to run/stop/debug the WAR application..."
4629+ print "~ ... just use your servlet container commands instead"
4630+ print "~"
4631+ print "~ Have fun!"
4632+ print "~"
4633
4634=== added file 'framework/pym/play/launchpad.py'
4635--- framework/pym/play/launchpad.py 1970-01-01 00:00:00 +0000
4636+++ framework/pym/play/launchpad.py 2010-09-16 22:21:08 +0000
4637@@ -0,0 +1,24 @@
4638+# The official launchpad lib is too heavy with dependancies, let's just implement only what we need
4639+
4640+from urllib2 import urlopen, Request
4641+import simplejson as json
4642+
4643+BASE_URL = 'https://api.edge.launchpad.net/1.0/'
4644+
4645+class Entry:
4646+ def __init__(self, baseUrl):
4647+ self.baseUrl = baseUrl
4648+
4649+ def __getattr__(self, method, *args, **kwargs):
4650+ return lambda *args, **kwargs: _doCall(self.baseUrl, method, kwargs)
4651+
4652+class Project(Entry):
4653+ def __init__(self, name):
4654+ Entry.__init__(self, BASE_URL + name)
4655+
4656+def _doCall(baseUrl, method, parameters):
4657+ url = baseUrl + "?ws.op=" + method
4658+ for key in parameters:
4659+ url += ("&" + key + "=" + parameters[key])
4660+ return json.loads(urlopen(Request(url)).read())
4661+
4662
4663=== added file 'framework/pym/play/utils.py'
4664--- framework/pym/play/utils.py 1970-01-01 00:00:00 +0000
4665+++ framework/pym/play/utils.py 2010-09-16 22:21:08 +0000
4666@@ -0,0 +1,186 @@
4667+import sys
4668+import os, os.path
4669+import re
4670+import random
4671+import fileinput
4672+import getopt
4673+import shutil
4674+
4675+def playVersion(play_env):
4676+ play_version_file = os.path.join(play_env["basedir"], 'framework', 'src', 'play', 'version')
4677+ return open(play_version_file).readline().strip()
4678+
4679+def replaceAll(file, searchExp, replaceExp, regexp=False):
4680+ if not regexp:
4681+ replaceExp = replaceExp.replace('\\', '\\\\')
4682+ searchExp = searchExp.replace('$', '\\$')
4683+ searchExp = searchExp.replace('{', '\\{')
4684+ searchExp = searchExp.replace('}', '\\}')
4685+ searchExp = searchExp.replace('.', '\\.')
4686+ for line in fileinput.input(file, inplace=1):
4687+ line = re.sub(searchExp, replaceExp, line)
4688+ sys.stdout.write(line)
4689+
4690+def fileHas(file, searchExp):
4691+ # The file doesn't get closed if we don't iterate to the end, so
4692+ # we must continue even after we found the match.
4693+ found = False
4694+ for line in fileinput.input(file):
4695+ if line.find(searchExp) > -1:
4696+ found = True
4697+ return found
4698+
4699+def secretKey():
4700+ return ''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') for i in range(64)])
4701+
4702+def isParentOf(path1, path2):
4703+ if len(path2) < len(path1) or len(path2) < 2:
4704+ return False
4705+ if (path1 == path2):
4706+ return True
4707+ return isParentOf(path1, os.path.dirname(path2))
4708+
4709+def isParentOf(path1, path2):
4710+ if len(path2) < len(path1) or len(path2) < 2:
4711+ return False
4712+ if (path1 == path2):
4713+ return True
4714+
4715+def getWithModules(args, env):
4716+ withModules = []
4717+ try:
4718+ optlist, newargs = getopt.getopt(args, '', ['with=', 'name='])
4719+ for o, a in optlist:
4720+ if o in ('--with='):
4721+ withModules = a.split(',')
4722+ except getopt.GetoptError:
4723+ pass # Other argument that --with= has been passed (which is OK)
4724+ md = []
4725+ for m in withModules:
4726+ dirname = None
4727+ candidate = os.path.join(env["basedir"], 'modules/%s' % m)
4728+ if os.path.exists(candidate) and os.path.isdir(candidate):
4729+ dirname = candidate
4730+ else:
4731+ for f in os.listdir(os.path.join(env["basedir"], 'modules')):
4732+ if os.path.isdir(os.path.join(env["basedir"], 'modules/%s' % f)) and f.find('%s-' % m) == 0:
4733+ dirname = os.path.join(env["basedir"], 'modules/%s' % f)
4734+ break
4735+ md.append(dirname)
4736+ return md
4737+
4738+def package_as_war(app, env, war_path, war_zip_path):
4739+ app.check()
4740+ modules = app.modules()
4741+ classpath = app.getClasspath()
4742+
4743+ if not war_path:
4744+ print "~ Oops. Please specify a path where to generate the WAR, using the -o or --output option"
4745+ print "~"
4746+ sys.exit(-1)
4747+
4748+ if os.path.exists(war_path) and not os.path.exists(os.path.join(war_path, 'WEB-INF')):
4749+ print "~ Oops. The destination path already exists but does not seem to host a valid WAR structure"
4750+ print "~"
4751+ sys.exit(-1)
4752+
4753+ if isParentOf(app.path, war_path):
4754+ print "~ Oops. Please specify a destination directory outside of the application"
4755+ print "~"
4756+ sys.exit(-1)
4757+
4758+ print "~ Packaging current version of the framework and the application to %s ..." % (os.path.normpath(war_path))
4759+ if os.path.exists(war_path): shutil.rmtree(war_path)
4760+ if os.path.exists(os.path.join(app.path, 'war')):
4761+ shutil.copytree(os.path.join(app.path, 'war'), war_path)
4762+ else:
4763+ os.mkdir(war_path)
4764+ if not os.path.exists(os.path.join(war_path, 'WEB-INF')): os.mkdir(os.path.join(war_path, 'WEB-INF'))
4765+ if not os.path.exists(os.path.join(war_path, 'WEB-INF/web.xml')):
4766+ shutil.copyfile(os.path.join(env["basedir"], 'resources/war/web.xml'), os.path.join(war_path, 'WEB-INF/web.xml'))
4767+ application_name = app.readConf('application.name')
4768+ replaceAll(os.path.join(war_path, 'WEB-INF/web.xml'), r'%APPLICATION_NAME%', application_name)
4769+ if env["id"] is not "":
4770+ replaceAll(os.path.join(war_path, 'WEB-INF/web.xml'), r'%PLAY_ID%', env["id"])
4771+ else:
4772+ replaceAll(os.path.join(war_path, 'WEB-INF/web.xml'), r'%PLAY_ID%', 'war')
4773+ if os.path.exists(os.path.join(war_path, 'WEB-INF/application')): shutil.rmtree(os.path.join(war_path, 'WEB-INF/application'))
4774+ shutil.copytree(app.path, os.path.join(war_path, 'WEB-INF/application'))
4775+ if os.path.exists(os.path.join(war_path, 'WEB-INF/application/war')):
4776+ shutil.rmtree(os.path.join(war_path, 'WEB-INF/application/war'))
4777+ if os.path.exists(os.path.join(war_path, 'WEB-INF/application/logs')):
4778+ shutil.rmtree(os.path.join(war_path, 'WEB-INF/application/logs'))
4779+ shutil.copytree(os.path.join(app.path, 'conf'), os.path.join(war_path, 'WEB-INF/classes'))
4780+ if os.path.exists(os.path.join(war_path, 'WEB-INF/lib')): shutil.rmtree(os.path.join(war_path, 'WEB-INF/lib'))
4781+ os.mkdir(os.path.join(war_path, 'WEB-INF/lib'))
4782+ for jar in classpath:
4783+ if jar.endswith('.jar') and jar.find('provided-') == -1:
4784+ shutil.copyfile(jar, os.path.join(war_path, 'WEB-INF/lib/%s' % os.path.split(jar)[1]))
4785+ if os.path.exists(os.path.join(war_path, 'WEB-INF/framework')): shutil.rmtree(os.path.join(war_path, 'WEB-INF/framework'))
4786+ os.mkdir(os.path.join(war_path, 'WEB-INF/framework'))
4787+ shutil.copytree(os.path.join(env["basedir"], 'framework/templates'), os.path.join(war_path, 'WEB-INF/framework/templates'))
4788+
4789+ # modules
4790+ for module in modules:
4791+ to = os.path.join(war_path, 'WEB-INF/modules/%s' % os.path.basename(module))
4792+ shutil.copytree(module, to)
4793+ if os.path.exists(os.path.join(to, 'src')):
4794+ shutil.rmtree(os.path.join(to, 'src'))
4795+ if os.path.exists(os.path.join(to, 'dist')):
4796+ shutil.rmtree(os.path.join(to, 'dist'))
4797+ if os.path.exists(os.path.join(to, 'samples-and-tests')):
4798+ shutil.rmtree(os.path.join(to, 'samples-and-tests'))
4799+ if os.path.exists(os.path.join(to, 'build.xml')):
4800+ os.remove(os.path.join(to, 'build.xml'))
4801+ if os.path.exists(os.path.join(to, 'commands.py')):
4802+ os.remove(os.path.join(to, 'commands.py'))
4803+ if os.path.exists(os.path.join(to, 'lib')):
4804+ shutil.rmtree(os.path.join(to, 'lib'))
4805+ if os.path.exists(os.path.join(to, 'nbproject')):
4806+ shutil.rmtree(os.path.join(to, 'nbproject'))
4807+ if os.path.exists(os.path.join(to, 'documentation')):
4808+ shutil.rmtree(os.path.join(to, 'documentation'))
4809+ pm = app.readConfs('module.')
4810+ for m in pm:
4811+ nm = os.path.basename(m)
4812+ replaceAll(os.path.join(war_path, 'WEB-INF/application/conf/application.conf'), m, '../modules/%s' % nm)
4813+
4814+ if not os.path.exists(os.path.join(war_path, 'WEB-INF/resources')): os.mkdir(os.path.join(war_path, 'WEB-INF/resources'))
4815+ shutil.copyfile(os.path.join(env["basedir"], 'resources/messages'), os.path.join(war_path, 'WEB-INF/resources/messages'))
4816+
4817+ deleteFrom(war_path, app.readConf('war.exclude').split("|"))
4818+
4819+ if war_zip_path:
4820+ print "~ Creating zipped archive to %s ..." % (os.path.normpath(war_zip_path))
4821+ if os.path.exists(war_zip_path):
4822+ os.remove(war_zip_path)
4823+ zip = zipfile.ZipFile(war_zip_path, 'w', zipfile.ZIP_STORED)
4824+ dist_dir = os.path.join(app.path, 'dist')
4825+ for (dirpath, dirnames, filenames) in os.walk(war_path):
4826+ if dirpath == dist_dir:
4827+ continue
4828+ if dirpath.find('/.') > -1:
4829+ continue
4830+ for file in filenames:
4831+ if file.find('~') > -1 or file.startswith('.'):
4832+ continue
4833+ zip.write(os.path.join(dirpath, file), os.path.join(dirpath[len(war_path):], file))
4834+
4835+ zip.close()
4836+
4837+# Recursively delete all files/folders in root whose name equals to filename
4838+# We could pass a "ignore" parameter to copytree, but that's not supported in Python 2.5
4839+
4840+def deleteFrom(root, filenames):
4841+ for f in os.listdir(root):
4842+ fullpath = os.path.join(root, f)
4843+ if f in filenames:
4844+ delete(fullpath)
4845+ elif os.path.isdir(fullpath):
4846+ deleteFrom(fullpath, filenames)
4847+
4848+def delete(filename):
4849+ if os.path.isdir(filename):
4850+ shutil.rmtree(filename)
4851+ else:
4852+ os.remove(filename)
4853\ No newline at end of file
4854
4855=== modified file 'framework/src/ehcache.xml'
4856--- framework/src/ehcache.xml 2009-05-23 21:02:57 +0000
4857+++ framework/src/ehcache.xml 2010-09-16 22:21:08 +0000
4858@@ -1,4 +1,4 @@
4859-<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
4860+<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd" updateCheck="false">
4861
4862 <defaultCache
4863 maxElementsInMemory="10000"
4864
4865=== modified file 'framework/src/play/CorePlugin.java'
4866--- framework/src/play/CorePlugin.java 2010-05-11 11:01:32 +0000
4867+++ framework/src/play/CorePlugin.java 2010-09-16 22:21:08 +0000
4868@@ -39,21 +39,20 @@
4869 }
4870 }
4871 return o.toString();
4872- } else {
4873- StringBuffer dump = new StringBuffer();
4874- for (PlayPlugin plugin : Play.plugins) {
4875- try {
4876- String status = plugin.getStatus();
4877- if (status != null) {
4878- dump.append(status);
4879- dump.append("\n");
4880- }
4881- } catch (Throwable e) {
4882- dump.append(plugin.getClass().getName() + ".getStatus() has failed (" + e.getMessage() + ")");
4883+ }
4884+ StringBuffer dump = new StringBuffer();
4885+ for (PlayPlugin plugin : Play.plugins) {
4886+ try {
4887+ String status = plugin.getStatus();
4888+ if (status != null) {
4889+ dump.append(status);
4890+ dump.append("\n");
4891 }
4892+ } catch (Throwable e) {
4893+ dump.append(plugin.getClass().getName() + ".getStatus() has failed (" + e.getMessage() + ")");
4894 }
4895- return dump.toString();
4896 }
4897+ return dump.toString();
4898 }
4899
4900 /**
4901@@ -78,15 +77,14 @@
4902 response.print(computeApplicationStatus(request.path.contains(".json")));
4903 response.status = 200;
4904 return true;
4905+ }
4906+ response.status = 401;
4907+ if(response.contentType.equals("application/json")) {
4908+ response.print("{\"error\": \"Not authorized\"}");
4909 } else {
4910- response.status = 401;
4911- if(response.contentType.equals("application/json")) {
4912- response.print("{\"error\": \"Not authorized\"}");
4913- } else {
4914- response.print("Not authorized");
4915- }
4916- return true;
4917+ response.print("Not authorized");
4918 }
4919+ return true;
4920 }
4921 return super.rawInvocation(request, response);
4922 }
4923
4924=== modified file 'framework/src/play/Invoker.java'
4925--- framework/src/play/Invoker.java 2010-06-02 10:08:08 +0000
4926+++ framework/src/play/Invoker.java 2010-09-16 22:21:08 +0000
4927@@ -2,9 +2,11 @@
4928
4929 import com.jamonapi.Monitor;
4930 import com.jamonapi.MonitorFactory;
4931-import java.util.HashMap;
4932+import java.util.Arrays;
4933 import java.util.HashSet;
4934+import java.util.List;
4935 import java.util.Map;
4936+import java.util.concurrent.ConcurrentHashMap;
4937 import java.util.concurrent.Future;
4938 import java.util.concurrent.ScheduledThreadPoolExecutor;
4939 import java.util.concurrent.ThreadPoolExecutor;
4940@@ -30,7 +32,7 @@
4941 * @param invocation The code to run
4942 * @return The future object, to know when the task is completed
4943 */
4944- public static Future invoke(final Invocation invocation) {
4945+ public static Future<?> invoke(final Invocation invocation) {
4946 Monitor monitor = MonitorFactory.getMonitor("Invoker queue size", "elmts.");
4947 monitor.add(executor.getQueue().size());
4948 invocation.waitInQueue = MonitorFactory.start("Waiting for execution");
4949@@ -40,13 +42,13 @@
4950 /**
4951 * Run the code in a new thread after a delay
4952 * @param invocation The code to run
4953- * @param seconds The time to wait before
4954+ * @param millis The time to wait before, in milliseconds
4955 * @return The future object, to know when the task is completed
4956 */
4957- public static Future invoke(final Invocation invocation, int seconds) {
4958+ public static Future<?> invoke(final Invocation invocation, long millis) {
4959 Monitor monitor = MonitorFactory.getMonitor("Invocation queue", "elmts.");
4960 monitor.add(executor.getQueue().size());
4961- return executor.schedule(invocation, seconds, TimeUnit.SECONDS);
4962+ return executor.schedule(invocation, millis, TimeUnit.MILLISECONDS);
4963 }
4964
4965 /**
4966@@ -61,10 +63,10 @@
4967 retry = false;
4968 } else {
4969 try {
4970- if (invocation.retry.task != null) {
4971- invocation.retry.task.get();
4972+ if (invocation.retry.tasks != null) {
4973+ for(Future<?> f : invocation.retry.tasks) f.get();
4974 } else {
4975- Thread.sleep(invocation.retry.timeout * 1000);
4976+ Thread.sleep(invocation.retry.timeout);
4977 }
4978 } catch (Exception e) {
4979 throw new UnexpectedException(e);
4980@@ -127,6 +129,12 @@
4981 }
4982
4983 /**
4984+ * Things to do when the whole invocation has succeeded (before + execute + after)
4985+ */
4986+ public void onSuccess() throws Exception {
4987+ }
4988+
4989+ /**
4990 * Things to do if the Invocation code thrown an exception
4991 */
4992 public void onException(Throwable e) {
4993@@ -147,8 +155,8 @@
4994 * @param suspendRequest
4995 */
4996 public void suspend(Suspend suspendRequest) {
4997- if (suspendRequest.task != null) {
4998- WaitForTasksCompletion.waitFor(suspendRequest.task, this);
4999+ if (suspendRequest.tasks != null) {
5000+ WaitForTasksCompletion.waitFor(suspendRequest.tasks, this);
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: