Merge lp:~nicolas-lunatech/play/play-ssl into lp:play
- play-ssl
- Merge into 1.0
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
play framework developers | Pending | ||
Review via email: mp+35750@code.launchpad.net |
Commit message
Description of the change
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' |
143 | Binary 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' |
145 | Binary 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' |
147 | Binary 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 | |
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 | + | |
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 | + | ${_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 **&** 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> & <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' |
2569 | Binary 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' |
2571 | Binary 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' |
2573 | Binary 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' |
2575 | Binary 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' |
2577 | Binary 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' |
2579 | Binary 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' |
2581 | Binary 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' |
2583 | Binary 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' |
2585 | Binary 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' |
2587 | Binary 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' |
2589 | Binary 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' |
2591 | Binary 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' |
2593 | Binary 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' |
2595 | Binary 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' |
2597 | Binary 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' |
2599 | Binary 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' |
2601 | Binary 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' |
2603 | Binary 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' |
2605 | Binary 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' |
2607 | Binary 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' |
2609 | Binary 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' |
2611 | Binary 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' |
2613 | Binary 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' |
2615 | Binary 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' |
2617 | Binary 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' |
2619 | Binary 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' |
2621 | Binary 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' |
2623 | Binary 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' |
2625 | Binary 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' |
2627 | Binary 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' |
2629 | Binary 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' |
2631 | Binary 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' |
2633 | Binary 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' |
2635 | Binary 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' |
2637 | Binary 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' |
2639 | Binary 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' |
2641 | Binary 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' |
2643 | Binary 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' |
2645 | Binary 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' |
2647 | Binary 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' |
2649 | Binary 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' |
2651 | Binary 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' |
2653 | Binary 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' |
2655 | Binary 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' |
2657 | Binary 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' |
2659 | Binary 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' |
2661 | Binary 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' |
2663 | Binary 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' |
2665 | Binary 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' |
2667 | Binary 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' |
2669 | Binary 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' |
2671 | Binary 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' |
2673 | Binary 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' |
2675 | Binary 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' |
2677 | Binary 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' |
2679 | Binary 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' |
2681 | Binary 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' |
2683 | Binary 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' |
2685 | Binary 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' |
2687 | Binary 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' |
2689 | Binary 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' |
2691 | Binary 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' |
2693 | Binary 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' |
2695 | Binary 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' |
2697 | Binary 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' |
2699 | Binary 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' |
2701 | Binary 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' |
2703 | Binary 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' |
2705 | Binary 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' |
2707 | Binary 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' |
2709 | Binary 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' |
2711 | Binary 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' |
2713 | Binary 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' |
2715 | Binary 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' |
2717 | Binary 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 | |
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 | |
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 | |
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 | |
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.