├── .classpath ├── .gitignore ├── .project ├── .settings ├── .jsdtscope ├── com.springsource.server.ide.jdt.core.xml ├── org.eclipse.jdt.core.prefs ├── org.eclipse.jst.common.project.facet.core.prefs ├── org.eclipse.wst.common.component ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.wst.jsdt.ui.superType.name ├── org.eclipse.wst.validation.prefs ├── org.maven.ide.eclipse.prefs ├── org.springframework.ide.eclipse.beans.core.prefs └── org.springframework.ide.eclipse.core.prefs ├── .springBeans ├── Gemfile ├── Gemfile.lock ├── README.markdown ├── Rakefile ├── app ├── controllers │ ├── application_controller.rb │ ├── owners_controller.rb │ ├── pets_controller.rb │ ├── vets_controller.rb │ ├── visits_controller.rb │ └── welcome_controller.rb ├── helpers │ └── application_helper.rb ├── models │ ├── owner.rb │ ├── pet.rb │ ├── pet_type.rb │ ├── vet.rb │ └── visit.rb └── views │ ├── layouts │ └── application.html.erb │ ├── owners │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ ├── search.html.erb │ └── show.html.erb │ ├── pets │ ├── _form.html.erb │ ├── edit.html.erb │ └── new.html.erb │ ├── shared │ └── _errors.html.erb │ ├── vets │ └── index.html.erb │ ├── visits │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.atom.builder │ └── new.html.erb │ └── welcome │ └── index.html.erb ├── commits.html ├── config.ru ├── config ├── application.rb ├── applicationContext-dataSource.xml ├── applicationContext-hibernate.xml ├── boot.rb ├── cucumber.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── classpath.rb │ ├── inflections.rb │ ├── load_classpath.rb │ ├── mime_types.rb │ ├── secret_token.rb │ ├── session_store.rb │ └── spring.rb ├── locales │ ├── de.yml │ └── en.yml ├── log4j.properties ├── routes.rb └── warble.rb ├── features ├── owners.feature ├── pets.feature ├── smoke_test.feature ├── step_definitions │ ├── custom_web_steps.rb │ ├── web_steps.rb │ └── xml_json_steps.rb ├── support │ ├── env.rb │ ├── paths.rb │ └── traverse.rb ├── vets.feature └── visits.feature ├── lib ├── java_ext.rb ├── spring_helpers.rb └── tasks │ ├── classpath.rake │ ├── cucumber.rake │ ├── database.rake │ └── docs.rake ├── petclinic-readme.txt ├── petclinic.iml ├── pom.xml ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── html │ └── tutorial.html ├── images │ ├── banner-graphic.png │ ├── bullet-arrow.png │ ├── pets.png │ ├── rails-banner-graphic.png │ ├── rails.png │ ├── springsource-logo.png │ └── submit-bg.png ├── javascripts │ ├── application.js │ ├── controls.js │ ├── dragdrop.js │ ├── effects.js │ ├── prototype.js │ └── rails.js ├── robots.txt └── stylesheets │ ├── .gitkeep │ └── petclinic.css ├── script ├── cucumber └── rails ├── src ├── main │ ├── java │ │ ├── com │ │ │ └── strobecorp │ │ │ │ └── kirk │ │ │ │ └── RewindableInputStream.java │ │ ├── org │ │ │ ├── jruby │ │ │ │ └── rack │ │ │ │ │ └── RubyFirstRackFilter.java │ │ │ └── springframework │ │ │ │ └── samples │ │ │ │ └── petclinic │ │ │ │ ├── BaseEntity.java │ │ │ │ ├── Clinic.java │ │ │ │ ├── NamedEntity.java │ │ │ │ ├── Owner.java │ │ │ │ ├── Person.java │ │ │ │ ├── Pet.java │ │ │ │ ├── PetType.java │ │ │ │ ├── Specialty.java │ │ │ │ ├── Vet.java │ │ │ │ ├── Vets.java │ │ │ │ ├── Visit.java │ │ │ │ ├── aspects │ │ │ │ ├── AbstractTraceAspect.java │ │ │ │ ├── CallMonitoringAspect.java │ │ │ │ └── UsageLogAspect.java │ │ │ │ ├── hibernate │ │ │ │ ├── HibernateClinic.java │ │ │ │ └── package-info.java │ │ │ │ ├── jdbc │ │ │ │ ├── JdbcPet.java │ │ │ │ ├── SimpleJdbcClinic.java │ │ │ │ ├── SimpleJdbcClinicMBean.java │ │ │ │ └── package-info.java │ │ │ │ ├── jpa │ │ │ │ ├── EntityManagerClinic.java │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── toplink │ │ │ │ ├── EssentialsHSQLPlatformWithNativeSequence.java │ │ │ │ └── package-info.java │ │ │ │ ├── util │ │ │ │ └── EntityUtils.java │ │ │ │ ├── validation │ │ │ │ ├── OwnerValidator.java │ │ │ │ ├── PetValidator.java │ │ │ │ ├── VisitValidator.java │ │ │ │ └── package-info.java │ │ │ │ └── web │ │ │ │ ├── AddOwnerForm.java │ │ │ │ ├── AddPetForm.java │ │ │ │ ├── AddVisitForm.java │ │ │ │ ├── ClinicBindingInitializer.java │ │ │ │ ├── ClinicController.java │ │ │ │ ├── EditOwnerForm.java │ │ │ │ ├── EditPetForm.java │ │ │ │ ├── FindOwnersForm.java │ │ │ │ ├── PetTypeEditor.java │ │ │ │ ├── VisitsAtomView.java │ │ │ │ └── package-info.java │ │ └── overview.html │ └── resources │ │ ├── META-INF │ │ ├── aop.xml │ │ ├── jpa-persistence.xml │ │ └── orm.xml │ │ ├── db │ │ ├── db_readme.txt │ │ ├── hsqldb │ │ │ ├── initDB.txt │ │ │ └── populateDB.txt │ │ ├── mysql │ │ │ ├── initDB.txt │ │ │ ├── petclinic_db_setup_mysql.txt │ │ │ └── populateDB.txt │ │ └── petclinic_tomcat_all.xml │ │ ├── jdbc.properties │ │ ├── log4j.properties │ │ ├── messages.properties │ │ ├── messages_de.properties │ │ ├── messages_en.properties │ │ └── petclinic.hbm.xml └── test │ ├── java │ └── org │ │ └── springframework │ │ └── samples │ │ └── petclinic │ │ ├── AbstractClinicTests.java │ │ ├── OwnerTests.java │ │ ├── hibernate │ │ └── HibernateClinicTests.java │ │ ├── jdbc │ │ └── SimpleJdbcClinicTests.java │ │ ├── jpa │ │ ├── AbstractJpaClinicTests.java │ │ ├── EntityManagerClinicTests.java │ │ ├── HibernateEntityManagerClinicTests.java │ │ └── OpenJpaEntityManagerClinicTests.java │ │ └── web │ │ └── VisitsAtomViewTest.java │ └── resources │ ├── log4j.xml │ └── org │ └── springframework │ └── samples │ └── petclinic │ ├── AbstractClinicTests-context.xml │ ├── hibernate │ └── HibernateClinicTests-context.xml │ ├── jdbc │ └── SimpleJdbcClinicTests-context.xml │ └── jpa │ ├── applicationContext-entityManager.xml │ ├── applicationContext-hibernateAdapter.xml │ ├── applicationContext-jpaCommon.xml │ ├── applicationContext-openJpaAdapter.xml │ └── applicationContext-toplinkAdapter.xml ├── test ├── performance │ └── browsing_test.rb └── test_helper.rb └── vendor └── plugins └── .gitkeep /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | target 3 | .bundle 4 | db/*.sqlite3 5 | log/*.log 6 | tmp/ 7 | /refactoring-to-rails.war 8 | /vendor/bundle 9 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.springframework.samples.petclinic 4 | 5 | 6 | Servers 7 | 8 | 9 | 10 | org.eclipse.wst.jsdt.core.javascriptValidator 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javabuilder 16 | 17 | 18 | 19 | 20 | org.eclipse.wst.common.project.facet.core.builder 21 | 22 | 23 | 24 | 25 | org.eclipse.wst.validation.validationbuilder 26 | 27 | 28 | 29 | 30 | org.springframework.ide.eclipse.core.springbuilder 31 | 32 | 33 | 34 | 35 | org.maven.ide.eclipse.maven2Builder 36 | 37 | 38 | 39 | 40 | 41 | org.eclipse.jem.workbench.JavaEMFNature 42 | org.maven.ide.eclipse.maven2Nature 43 | org.springframework.ide.eclipse.core.springnature 44 | org.eclipse.jdt.core.javanature 45 | org.eclipse.wst.common.project.facet.core.nature 46 | org.eclipse.wst.common.modulecore.ModuleCoreNature 47 | org.eclipse.wst.jsdt.core.jsNature 48 | 49 | 50 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.settings/com.springsource.server.ide.jdt.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Tue Mar 17 10:00:21 EDT 2009 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.5 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.5 13 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jst.common.project.facet.core.prefs: -------------------------------------------------------------------------------- 1 | #Tue Mar 17 09:59:19 EDT 2009 2 | classpath.helper/org.eclipse.jdt.launching.JRE_CONTAINER\:\:org.eclipse.jdt.internal.launching.macosx.MacOSXType\:\:JVM\ 1.5.0\ (MacOS\ X\ Default)/owners=jst.java\:5.0 3 | eclipse.preferences.version=1 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.validation.prefs: -------------------------------------------------------------------------------- 1 | #Fri Jun 06 17:00:12 BST 2008 2 | DELEGATES_PREFERENCE=delegateValidatorListorg.eclipse.wst.wsdl.validation.internal.eclipse.WSDLDelegatingValidator\=org.eclipse.wst.wsdl.validation.internal.eclipse.Validator;org.eclipse.wst.xsd.core.internal.validation.eclipse.XSDDelegatingValidator\=org.eclipse.wst.xsd.core.internal.validation.eclipse.Validator; 3 | USER_BUILD_PREFERENCE=enabledBuildValidatorListorg.eclipse.wst.wsdl.validation.internal.eclipse.WSDLDelegatingValidator;org.eclipse.wst.xsd.core.internal.validation.eclipse.XSDDelegatingValidator;org.eclipse.jst.jsf.validation.internal.JSPSemanticsValidator;org.eclipse.wst.dtd.core.internal.validation.eclipse.Validator;org.eclipse.wst.xml.core.internal.validation.eclipse.Validator;org.eclipse.wst.common.componentcore.internal.ModuleCoreValidator;org.eclipse.jst.jsf.validation.internal.appconfig.AppConfigValidator;org.eclipse.jst.jsp.core.internal.validation.JSPBatchValidator;org.eclipse.wst.html.internal.validation.HTMLValidator;org.eclipse.jst.jsp.core.internal.validation.JSPContentValidator;org.eclipse.jst.j2ee.internal.classpathdep.ClasspathDependencyValidator;org.eclipse.wst.wsi.ui.internal.WSIMessageValidator; 4 | USER_MANUAL_PREFERENCE=enabledManualValidatorListorg.eclipse.wst.wsdl.validation.internal.eclipse.WSDLDelegatingValidator;org.eclipse.wst.xsd.core.internal.validation.eclipse.XSDDelegatingValidator;org.eclipse.jst.jsf.validation.internal.JSPSemanticsValidator;org.eclipse.wst.dtd.core.internal.validation.eclipse.Validator;org.eclipse.wst.xml.core.internal.validation.eclipse.Validator;org.eclipse.wst.common.componentcore.internal.ModuleCoreValidator;org.eclipse.jst.jsf.validation.internal.appconfig.AppConfigValidator;org.eclipse.jst.jsp.core.internal.validation.JSPBatchValidator;org.eclipse.wst.html.internal.validation.HTMLValidator;org.eclipse.jst.jsp.core.internal.validation.JSPContentValidator;org.eclipse.jst.j2ee.internal.classpathdep.ClasspathDependencyValidator;org.eclipse.wst.wsi.ui.internal.WSIMessageValidator; 5 | USER_PREFERENCE=overrideGlobalPreferencesfalse 6 | eclipse.preferences.version=1 7 | -------------------------------------------------------------------------------- /.settings/org.maven.ide.eclipse.prefs: -------------------------------------------------------------------------------- 1 | #Tue Mar 17 14:28:16 EDT 2009 2 | activeProfiles= 3 | eclipse.preferences.version=1 4 | fullBuildGoals=process-test-resources 5 | includeModules=false 6 | resolveWorkspaceProjects=true 7 | resourceFilterGoals=process-resources resources\:testResources 8 | skipCompilerPlugin=true 9 | version=1 10 | -------------------------------------------------------------------------------- /.settings/org.springframework.ide.eclipse.beans.core.prefs: -------------------------------------------------------------------------------- 1 | #Wed Dec 17 01:09:03 EST 2008 2 | eclipse.preferences.version=1 3 | org.springframework.ide.eclipse.beans.core.ignoreMissingNamespaceHandler=false 4 | -------------------------------------------------------------------------------- /.springBeans: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.0.5' 4 | gem 'jruby-openssl', :require => false 5 | 6 | group :development do 7 | gem 'maruku' 8 | end 9 | 10 | group :test do 11 | gem 'rspec-rails' 12 | gem 'cucumber-rails' 13 | gem 'capybara' 14 | gem 'database_cleaner' 15 | # This until launchy 1.0.0 is released to rubyforge 16 | gem 'spoon', '~> 0.0.1' 17 | gem 'launchy', '1.0.0', :git => 'https://github.com/copiousfreetime/launchy.git', :ref => 'v1.0.0' 18 | end 19 | 20 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/copiousfreetime/launchy.git 3 | revision: f933aa72a3c925ecd15d83bdc510bd50b8f97d4f 4 | ref: v1.0.0 5 | specs: 6 | launchy (1.0.0) 7 | 8 | GEM 9 | remote: http://rubygems.org/ 10 | specs: 11 | abstract (1.0.0) 12 | actionmailer (3.0.5) 13 | actionpack (= 3.0.5) 14 | mail (~> 2.2.15) 15 | actionpack (3.0.5) 16 | activemodel (= 3.0.5) 17 | activesupport (= 3.0.5) 18 | builder (~> 2.1.2) 19 | erubis (~> 2.6.6) 20 | i18n (~> 0.4) 21 | rack (~> 1.2.1) 22 | rack-mount (~> 0.6.13) 23 | rack-test (~> 0.5.7) 24 | tzinfo (~> 0.3.23) 25 | activemodel (3.0.5) 26 | activesupport (= 3.0.5) 27 | builder (~> 2.1.2) 28 | i18n (~> 0.4) 29 | activerecord (3.0.5) 30 | activemodel (= 3.0.5) 31 | activesupport (= 3.0.5) 32 | arel (~> 2.0.2) 33 | tzinfo (~> 0.3.23) 34 | activeresource (3.0.5) 35 | activemodel (= 3.0.5) 36 | activesupport (= 3.0.5) 37 | activesupport (3.0.5) 38 | arel (2.0.9) 39 | bouncy-castle-java (1.5.0145.2) 40 | builder (2.1.2) 41 | capybara (0.4.1.1) 42 | celerity (>= 0.7.9) 43 | culerity (>= 0.2.4) 44 | mime-types (>= 1.16) 45 | nokogiri (>= 1.3.3) 46 | rack (>= 1.0.0) 47 | rack-test (>= 0.5.4) 48 | selenium-webdriver (>= 0.0.27) 49 | xpath (~> 0.1.3) 50 | celerity (0.8.9) 51 | childprocess (0.1.6) 52 | ffi (~> 0.6.3) 53 | cucumber (0.10.0) 54 | builder (>= 2.1.2) 55 | diff-lcs (~> 1.1.2) 56 | gherkin (~> 2.3.2) 57 | json (~> 1.4.6) 58 | term-ansicolor (~> 1.0.5) 59 | cucumber-rails (0.3.2) 60 | cucumber (>= 0.8.0) 61 | culerity (0.2.15) 62 | database_cleaner (0.6.3) 63 | diff-lcs (1.1.2) 64 | erubis (2.6.6) 65 | abstract (>= 1.0.0) 66 | ffi (0.6.3-java) 67 | gherkin (2.3.3-java) 68 | json (~> 1.4.6) 69 | i18n (0.5.0) 70 | jruby-openssl (0.7.3) 71 | bouncy-castle-java 72 | json (1.4.6-java) 73 | json_pure (1.5.1) 74 | mail (2.2.15) 75 | activesupport (>= 2.3.6) 76 | i18n (>= 0.4.0) 77 | mime-types (~> 1.16) 78 | treetop (~> 1.4.8) 79 | maruku (0.6.0) 80 | syntax (>= 1.0.0) 81 | mime-types (1.16) 82 | nokogiri (1.4.4.2-java) 83 | weakling (>= 0.0.3) 84 | polyglot (0.3.1) 85 | rack (1.2.2) 86 | rack-mount (0.6.13) 87 | rack (>= 1.0.0) 88 | rack-test (0.5.7) 89 | rack (>= 1.0) 90 | rails (3.0.5) 91 | actionmailer (= 3.0.5) 92 | actionpack (= 3.0.5) 93 | activerecord (= 3.0.5) 94 | activeresource (= 3.0.5) 95 | activesupport (= 3.0.5) 96 | bundler (~> 1.0) 97 | railties (= 3.0.5) 98 | railties (3.0.5) 99 | actionpack (= 3.0.5) 100 | activesupport (= 3.0.5) 101 | rake (>= 0.8.7) 102 | thor (~> 0.14.4) 103 | rake (0.8.7) 104 | rspec (2.5.0) 105 | rspec-core (~> 2.5.0) 106 | rspec-expectations (~> 2.5.0) 107 | rspec-mocks (~> 2.5.0) 108 | rspec-core (2.5.1) 109 | rspec-expectations (2.5.0) 110 | diff-lcs (~> 1.1.2) 111 | rspec-mocks (2.5.0) 112 | rspec-rails (2.5.0) 113 | actionpack (~> 3.0) 114 | activesupport (~> 3.0) 115 | railties (~> 3.0) 116 | rspec (~> 2.5.0) 117 | rubyzip (0.9.4) 118 | selenium-webdriver (0.1.2) 119 | childprocess (~> 0.1.5) 120 | ffi (~> 0.6.3) 121 | json_pure 122 | rubyzip 123 | spoon (0.0.1) 124 | syntax (1.0.0) 125 | term-ansicolor (1.0.5) 126 | thor (0.14.6) 127 | treetop (1.4.9) 128 | polyglot (>= 0.3.1) 129 | tzinfo (0.3.24) 130 | weakling (0.0.4-java) 131 | xpath (0.1.3) 132 | nokogiri (~> 1.3) 133 | 134 | PLATFORMS 135 | java 136 | 137 | DEPENDENCIES 138 | capybara 139 | cucumber-rails 140 | database_cleaner 141 | jruby-openssl 142 | launchy (= 1.0.0)! 143 | maruku 144 | rails (= 3.0.5) 145 | rspec-rails 146 | spoon (~> 0.0.1) 147 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Refactoring to Rails 2 | 3 | This is a project to demonstrate how you might refactor a legacy Java 4 | project to use Rails. 5 | 6 | The entire repository history is intended to be read start-to-finish. 7 | Each commit message is annotated with the goals of that particular 8 | step and what to expect. If you simply copy or imitate code from a 9 | particular snapshot without understanding how it got there, you're 10 | just practicing [cargo][c1] [cult][c2] [programming][c3] :). 11 | 12 | We're using the Spring-MVC-based "Petclinic" application as the 13 | project subject to the refactoring. We sourced the code of the 14 | application from [this subversion repository][svn]. 15 | 16 | There are three versions of the refactoring: `small`, `medium` and 17 | `large`. Each version is stored on a different branch in this 18 | repository. The three branches build upon each other in succession. 19 | The size name of each of the refactorings is an indicator to the 20 | amount of deviation from the original Java-only Spring MVC project. 21 | 22 | ## Small 23 | 24 | The small refactoring is the starting point. We inject JRuby into a 25 | specific endpoint in the webapp without disrupting any of the main 26 | functionality. A couple of XML endpoints are transferred into Ruby 27 | code using Sinatra as the Ruby web framework. 28 | 29 | The techniques used in this section could be used to add a RESTful web 30 | service wrapper to any existing Java web application. 31 | 32 | ## Medium 33 | 34 | The medium refactoring introduces Rails into the web application 35 | without restructuring the project. Rails application sources are 36 | stored under `src/main/webapp` in the place where they would appear in 37 | the application `.war` file. The result is that Rails can drive parts 38 | of the webapp but you don't gain the benefits of Rails development 39 | tooling. 40 | 41 | The main technique of note in this refactoring is the use of a 42 | "Ruby-first Rack filter": Requests to the application are first 43 | handled by Ruby/Rails; if Rails returns an error code, then the 44 | request is reset and passed through. This allows Rails to service some 45 | requests but leave others alone, so that you can layer successively 46 | more Rails requests on top of the existing application. 47 | 48 | ## Large 49 | 50 | The large refactoring begins by creating the Rails application 51 | structure at the root of the project, and continues by moving more and 52 | more functionality from Spring to Rails, until the entire UI layer is 53 | ported from Spring MVC to Rails. 54 | 55 | The main reason for undertaking a project reorganization is to take 56 | advantage of the Rails workflow and tools: the console, generators, 57 | profiling and benchmarking, and so on. 58 | 59 | The large refactoring is completed by adding Warbler configuration, 60 | coming full-circle back to generating a `.war` file of the 61 | application. This shows that you don't have to sacrifice tradidional 62 | Java deployment models while still taking advantage of the "Rails way" 63 | of developing applications. 64 | 65 | # Getting Started 66 | 67 | To play with this code, you should [install JRuby][jruby], and then 68 | install Bundler with `gem install bundler`. 69 | 70 | You will also need MySQL installed. You'll need to do the one-time 71 | step of creating the `petclinic` database. Assuming the default MySQL 72 | development setup of a root user with no password, run `rake 73 | db:create` to create the petclinic database. (Alternately, you can log 74 | into the mysql console manually and load the file 75 | `src/main/resources/db/mysql/initDB.txt`.) 76 | 77 | At any point in the history, you should be able to: 78 | 79 | - `bundle install`: This ensures that you have all the gems needed to 80 | run the project. 81 | - `rake`: This runs the test suite. See also the output of `rake -T` 82 | to see all available tasks. 83 | 84 | [svn]: https://src.springframework.org/svn/spring-samples/petclinic/trunk 85 | [c1]: http://c2.com/cgi/wiki?CargoCultProgramming 86 | [c2]: http://c2.com/cgi/wiki?CopyAndPasteProgramming 87 | [c3]: http://c2.com/cgi/wiki?VoodooChickenCoding 88 | [jruby]: http://jruby.org/getting-started 89 | 90 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | SpringPetclinic::Application.load_tasks 8 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | require 'java_ext' 5 | require 'spring_helpers' 6 | include Spring 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/owners_controller.rb: -------------------------------------------------------------------------------- 1 | class OwnersController < ApplicationController 2 | before_filter :load_owner, :only => [:edit, :show, :update] 3 | 4 | def index 5 | name = params[:lastName] || '' 6 | @owners = Owner.find_by_name(name) 7 | if @owners.size < 1 8 | flash[:notice] = "#{params[:lastName]} #{t(:notFound)}" 9 | render "search" 10 | elsif @owners.size == 1 11 | redirect_to owner_path(@owners.first) 12 | end 13 | end 14 | 15 | def search 16 | end 17 | 18 | def show 19 | end 20 | 21 | def edit 22 | end 23 | 24 | def new 25 | @owner = Owner.new 26 | end 27 | 28 | def create 29 | @owner = Owner.new 30 | create_or_update("new") 31 | end 32 | 33 | def update 34 | create_or_update("edit") 35 | end 36 | 37 | private 38 | def load_owner 39 | unless (@owner ||= Owner.load params[:id]) 40 | flash[:notice] = t(:notFound) 41 | redirect_to owners_path 42 | end 43 | end 44 | 45 | def create_or_update(template) 46 | @owner.update_attributes params[:owner] 47 | if @owner.valid? 48 | @owner.save 49 | redirect_to owner_path(@owner) 50 | else 51 | render template 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/controllers/pets_controller.rb: -------------------------------------------------------------------------------- 1 | class PetsController < ApplicationController 2 | before_filter :load_owner 3 | before_filter :new_pet, :only => [:new, :create] 4 | before_filter :load_pet, :only => [:show, :edit, :update, :destroy] 5 | 6 | def new 7 | @types = clinic.pet_types 8 | end 9 | 10 | def show 11 | end 12 | 13 | def edit 14 | @types = clinic.pet_types 15 | end 16 | 17 | def create 18 | create_or_update('new') 19 | end 20 | 21 | def update 22 | create_or_update('edit') 23 | end 24 | 25 | def destroy 26 | @pet.destroy 27 | redirect_to owner_path(params[:owner_id]) 28 | end 29 | 30 | private 31 | def load_owner 32 | unless (@owner ||= Owner.load params[:owner_id].to_i) 33 | flash[:notice] = t(:notFound) 34 | redirect_to owners_path 35 | end 36 | end 37 | 38 | def load_pet 39 | @pet = Pet.load(params[:id]) 40 | end 41 | 42 | def new_pet 43 | @pet = Pet.new.tap {|pet| pet.owner = @owner } 44 | end 45 | 46 | def create_or_update(template) 47 | @pet.update_attributes params[:pet] 48 | if @pet.valid? 49 | @pet.save 50 | redirect_to owner_path(@owner) 51 | else 52 | render template 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/controllers/vets_controller.rb: -------------------------------------------------------------------------------- 1 | class VetsController < ApplicationController 2 | def index 3 | @vets = Vet.all 4 | respond_to do |format| 5 | format.html 6 | format.xml do 7 | render :xml => @vets.to_a.to_xml 8 | end 9 | format.json do 10 | render :json => @vets.to_a.to_json 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/controllers/visits_controller.rb: -------------------------------------------------------------------------------- 1 | class VisitsController < ApplicationController 2 | before_filter :load_pet 3 | before_filter :new_visit, :only => [:new, :create] 4 | before_filter :load_visit, :only => [:show, :edit, :update] 5 | 6 | def index 7 | @visits = @pet.visits 8 | respond_to do |format| 9 | format.atom 10 | end 11 | end 12 | 13 | def new 14 | end 15 | 16 | def edit 17 | end 18 | 19 | def create 20 | create_or_update('new') 21 | end 22 | 23 | def update 24 | create_or_update('edit') 25 | end 26 | 27 | private 28 | def load_pet 29 | @pet = Pet.load(params[:pet_id]) 30 | end 31 | 32 | def new_visit 33 | @visit = Visit.new.tap {|v| v.pet = @pet } 34 | end 35 | 36 | def load_visit 37 | unless (@visit = @pet.visits.detect {|v| v.id == params[:id].to_i}) 38 | flash[:notice] = t(:notFound) 39 | redirect_to owner_path(@pet.owner) 40 | end 41 | end 42 | 43 | def create_or_update(template) 44 | @visit.update_attributes params[:visit] 45 | if @visit.valid? 46 | @visit.save 47 | redirect_to owner_path(@pet.owner) 48 | else 49 | render template 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /app/controllers/welcome_controller.rb: -------------------------------------------------------------------------------- 1 | class WelcomeController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/models/owner.rb: -------------------------------------------------------------------------------- 1 | java_import org.springframework.samples.petclinic.Owner 2 | 3 | class Owner 4 | extend ActiveModel::Naming 5 | include ActiveModel::Validations 6 | include ActiveModel::Conversion 7 | include Spring 8 | 9 | Pet # dev-mode: ensure child models are decorated 10 | 11 | validates_presence_of :first_name, :last_name, :address, :city, :telephone 12 | validates_format_of :telephone, :with => /[-0-9.+ ]+/, :message => "can only have numbers, punctuation, or spaces" 13 | 14 | def self.find_by_name(name) 15 | clinic.findOwners(name) 16 | end 17 | 18 | def self.load(id) 19 | clinic.loadOwner(id.to_i) 20 | end 21 | 22 | def save 23 | clinic.storeOwner(self) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/pet.rb: -------------------------------------------------------------------------------- 1 | java_import org.springframework.samples.petclinic.Pet 2 | 3 | class Pet 4 | extend ActiveModel::Naming 5 | include ActiveModel::Validations 6 | include ActiveModel::Conversion 7 | include Spring 8 | 9 | Owner; Visit ; PetType # dev-mode: ensure child models are decorated 10 | 11 | def birth_date 12 | getBirthDate && getBirthDate.to_date 13 | end 14 | 15 | def birth_date=(date) 16 | setBirthDate java.util.Date.from_date(date) 17 | end 18 | 19 | def type=(t) 20 | setType PetType === t ? t : PetType.load(t.to_i) 21 | end 22 | 23 | def self.load(id) 24 | clinic.loadPet(id.to_i) 25 | end 26 | 27 | def save 28 | clinic.storePet(self) 29 | end 30 | 31 | def destroy 32 | clinic.deletePet(id) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/models/pet_type.rb: -------------------------------------------------------------------------------- 1 | java_import org.springframework.samples.petclinic.PetType 2 | 3 | class PetType 4 | extend ActiveModel::Naming 5 | include ActiveModel::Validations 6 | include ActiveModel::Conversion 7 | include Spring 8 | 9 | def self.load(id) 10 | clinic.getPetTypes.detect {|pt| pt.id == id.to_i } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/vet.rb: -------------------------------------------------------------------------------- 1 | java_import org.springframework.samples.petclinic.Vet 2 | 3 | class Vet 4 | include ActiveModel::Serializers::Xml 5 | include ActiveModel::Serializers::JSON 6 | include Spring 7 | 8 | def serializable_hash(*) 9 | { 10 | "id" => id, 11 | "first_name" => first_name, "last_name" => last_name, 12 | "specialties" => specialties.map{|s| s.name}.join(', '), 13 | "nr_of_specialties" => nr_of_specialties 14 | } 15 | end 16 | alias attributes serializable_hash 17 | 18 | def self.all 19 | clinic.getVets 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/visit.rb: -------------------------------------------------------------------------------- 1 | java_import org.springframework.samples.petclinic.Visit 2 | 3 | class Visit 4 | extend ActiveModel::Naming 5 | include ActiveModel::Validations 6 | include ActiveModel::Conversion 7 | include Spring 8 | 9 | def date 10 | getDate.to_date 11 | end 12 | 13 | def date=(date) 14 | setDate java.util.Date.from_date(date) 15 | end 16 | 17 | def save 18 | clinic.storeVisit(self) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= stylesheet_link_tag :all %> 6 | PetClinic :: a Rails + Spring demonstration 7 | 8 | 9 | 10 | 11 |
12 | 13 | <%= yield %> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/views/owners/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @owner do |f| %> 2 | <%= render :partial => 'shared/errors', :locals => { :object => @owner } %> 3 | 4 | 5 | 10 | 11 | 12 | 17 | 18 | 19 | 24 | 25 | 26 | 31 | 32 | 33 | 38 | 39 | 40 | 43 | 44 |
6 | <%= f.label :first_name %> 7 |
8 | <%= f.text_field :first_name, :size => 30, :maxlength => 80 %> 9 |
13 | <%= f.label :last_name %> 14 |
15 | <%= f.text_field :last_name, :size => 30, :maxlength => 80 %> 16 |
20 | <%= f.label :address %> 21 |
22 | <%= f.text_field :address, :size => 30, :maxlength => 80 %> 23 |
27 | <%= f.label :city %> 28 |
29 | <%= f.text_field :city, :size => 30, :maxlength => 80 %> 30 |
34 | <%= f.label :telephone %> 35 |
36 | <%= f.text_field :telephone, :size => 20, :maxlength => 20 %> 37 |
41 |

<%= f.submit "#{@owner.new? ? 'Add' : 'Update'} Owner" %>

42 |
45 | <% end -%> 46 | -------------------------------------------------------------------------------- /app/views/owners/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Owner:

2 | 3 | <%= render :partial => 'form' %> -------------------------------------------------------------------------------- /app/views/owners/index.html.erb: -------------------------------------------------------------------------------- 1 |

Owners:

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% @owners.each do |owner| -%> 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | <% end -%> 22 |
NameAddressCityTelephonePets
14 | <%= link_to "#{owner.firstName} #{owner.lastName}", owner_path(owner.id) %> 15 | <%= owner.address %><%= owner.city %><%= owner.telephone %><% owner.pets.each do |pet| -%><%= pet.name %> <% end -%>
23 |
24 | <%= link_to 'Add Owner', new_owner_path %> -------------------------------------------------------------------------------- /app/views/owners/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Owner:

2 | 3 | <%= render :partial => 'form' %> -------------------------------------------------------------------------------- /app/views/owners/search.html.erb: -------------------------------------------------------------------------------- 1 |

Find Owners:

2 | 3 | <% form_tag '/owners', :method => :get do -%> 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 |
7 | 8 |
9 | <%= search_field_tag "lastName", params[:lastName], :size => 30, :maxlength => 80 %> 10 |

16 | <% end -%> 17 | 18 |
19 | <%= link_to 'Add Owner', '/owners/new' %> 20 | -------------------------------------------------------------------------------- /app/views/owners/show.html.erb: -------------------------------------------------------------------------------- 1 |

Owner Information

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Name<%= @owner.first_name %> <%= @owner.last_name %>
Address<%= @owner.address %>
City<%= @owner.city %>
Telephone <%= @owner.telephone %>
21 | 22 | 23 | 26 | 29 | 32 | 33 |
24 | <%= link_to 'Edit Owner', edit_owner_path %> 25 | 27 | <%= link_to 'Destroy Owner', owner_path(@owner), :confirm => 'Are you sure?', :method => :delete %> 28 | 30 | <%= link_to 'Add New Pet', new_owner_pet_path(@owner) %> 31 |
34 | 35 |

Pets and Visits

36 | 37 | <% @owner.pets.each do |pet| -%> 38 | 39 | 40 | 56 | 70 | 71 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
Name<%= pet.name %>
Birth Date<%= pet.birth_date.to_s %>
Type<%= pet.type.name %>
55 |
57 | 58 | 59 | 60 | 61 | 62 | <% pet.visits.each do |visit| -%> 63 | 64 | 65 | 66 | 67 | <% end -%> 68 |
Visit DateDescription
<%= visit.date.to_s %><%= visit.description %>
69 |
72 | 73 | 74 | 77 | 78 | 81 | 82 | 85 | 86 |
75 | <%= link_to 'Edit Pet', edit_owner_pet_path(@owner, pet) %> 76 | 79 | <%= link_to 'Add Visit', new_owner_pet_visit_path(@owner, pet) %> 80 | 83 | <%= link_to 'Atom Feed', owner_pet_visits_path(@owner, pet, 'atom') %> 84 |
87 | <% end -%> 88 | -------------------------------------------------------------------------------- /app/views/pets/_form.html.erb: -------------------------------------------------------------------------------- 1 | Owner: <%= @pet.owner.first_name %> <%= @pet.owner.last_name %> 2 |
3 | <%= form_for [@owner, @pet] do |f| %> 4 | <%= render :partial => 'shared/errors', :locals => { :object => @pet } %> 5 | 6 | 7 | 12 | 13 | 14 | 19 | 20 | 21 | 26 | 27 | 28 | 31 | 32 |
8 | <%= f.label :name %> 9 |
10 | <%= f.text_field :name, :size => 30, :maxlength => 30 %> 11 |
15 | <%= f.label :birth_date %> 16 |
17 | <%= f.date_select :birth_date %> 18 |
22 | <%= f.label :type %> 23 |
24 | <%= f.select :type, options_from_collection_for_select(@types, "id", "name", @pet.type && @pet.type.id) %> 25 |
29 |

<%= f.submit "#{@pet.new? ? 'Add' : 'Update'} Pet" %>

30 |
33 | <% end -%> 34 | -------------------------------------------------------------------------------- /app/views/pets/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Pet

2 | 3 | <%= render :partial => "form" %> 4 | 5 | <%= form_for [@owner, @pet], :html => {:method => 'delete'} do |f| %> 6 |

<%= f.submit "Delete Pet", :confirm => "Are you sure?" %>

7 | <% end -%> 8 | 9 | Visits: 10 | 11 | 12 | 13 | 14 | 15 | <% @pet.visits.each do |visit| -%> 16 | <% unless visit.new? -%> 17 | 18 | 19 | 20 | 21 | <% end -%> 22 | <% end -%> 23 |
DateDescription
<%= link_to visit.date.to_s, edit_owner_pet_visit_path(@owner, @pet, visit) %><%= visit.description %>
24 | -------------------------------------------------------------------------------- /app/views/pets/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Pet

2 | 3 | <%= render :partial => 'form' %> -------------------------------------------------------------------------------- /app/views/shared/_errors.html.erb: -------------------------------------------------------------------------------- 1 | <% unless object.errors.empty? -%> 2 |
3 |

There were problems with your entry, please correct them and re-submit.

4 | 9 |
10 | <% end -%> 11 | -------------------------------------------------------------------------------- /app/views/vets/index.html.erb: -------------------------------------------------------------------------------- 1 |

Veterinarians:

2 | 3 | 4 | 5 | 6 | 7 | 8 | <% @vets.each do |vet| -%> 9 | 10 | 11 | 17 | 18 | <% end -%> 19 |
NameSpecialties
<%= vet.firstName %> <%= vet.lastName %> 12 | <% vet.specialties.each do |specialty| -%> 13 | <%= specialty.name %> 14 | <% end -%> 15 | <% if vet.nr_of_specialties == 0 %>none<% end -%> 16 |
20 | 21 | 22 | 25 | 28 | 29 |
23 | View as XML 24 | 26 | View as JSON 27 |
30 | -------------------------------------------------------------------------------- /app/views/visits/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [@pet.owner, @pet, @visit] do |f| %> 2 | Pet: 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
NameBirth DateTypeOwner
<%= @pet.name %><%= @pet.birth_date.to_s %><%= @pet.type.name %><%= @pet.owner.firstName %> <%= @pet.owner.lastName %>
17 | 18 | 19 | 20 | 24 | 27 | 28 | 29 | 33 | 36 | 37 | 38 | 41 | 42 |
21 | <%= f.label :date %> 22 |
23 |
25 | <%= f.date_select :date %> 26 |
30 | <%= f.label :description %> 31 |
32 |
34 | <%= f.text_area :description, :rows => 10, :cols => 25 %> 35 |
39 |

<%= f.submit "#{@visit.new? ? 'Add' : 'Update'} Visit" %>

40 |
43 | <% end -%> 44 | 45 |
46 | Previous Visits: 47 | 48 | 49 | 50 | 51 | 52 | <% @pet.visits.each do |visit| -%> 53 | <% unless visit.new? -%> 54 | 55 | 56 | 57 | 58 | <% end -%> 59 | <% end -%> 60 |
DateDescription
<%= visit.date.to_s %><%= visit.description %>
61 | -------------------------------------------------------------------------------- /app/views/visits/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Visit:

2 | 3 | <%= render :partial => "form" %> -------------------------------------------------------------------------------- /app/views/visits/index.atom.builder: -------------------------------------------------------------------------------- 1 | atom_feed :id => "tag:springsource.com" do |feed| 2 | feed.title("Pet Clinic Visits") 3 | feed.updated(@visits.max(&:date).date) 4 | 5 | for visit in @visits 6 | feed.entry(visit, 7 | :id => "tag:springsource.com,#{visit.date}:#{visit.id}", 8 | :url => owner_pet_url(@pet.owner, @pet)) do |entry| 9 | entry.title("#{@pet.name} visit on #{visit.date}") 10 | entry.content(visit.description, :type => 'text') 11 | entry.updated(visit.date) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/visits/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Visit:

2 | 3 | <%= render :partial => "form" %> 4 | -------------------------------------------------------------------------------- /app/views/welcome/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= image_tag 'pets.png', :align => "right", :style => "position:relative;right:30px;" %> 2 |

<%= t :welcome %>

3 | 4 | 9 | 10 |

 

11 |

 

12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run SpringPetclinic::Application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | # require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "active_resource/railtie" 8 | require "rails/test_unit/railtie" 9 | 10 | # If you have a Gemfile, require the gems listed there, including any gems 11 | # you've limited to :test, :development, or :production. 12 | Bundler.require(:default, Rails.env) if defined?(Bundler) 13 | 14 | module SpringPetclinic 15 | class Application < Rails::Application 16 | # Settings in config/environments/* take precedence over those specified here. 17 | # Application configuration should go into files in config/initializers 18 | # -- all .rb files in that directory are automatically loaded. 19 | 20 | # Custom directories with classes and modules you want to be autoloadable. 21 | # config.autoload_paths += %W(#{config.root}/extras) 22 | 23 | # Only load the plugins named here, in the order given (default is alphabetical). 24 | # :all can be used as a placeholder for all plugins not explicitly named. 25 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 26 | 27 | # Activate observers that should always be running. 28 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 29 | 30 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 31 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 32 | # config.time_zone = 'Central Time (US & Canada)' 33 | 34 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 35 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 36 | # config.i18n.default_locale = :de 37 | 38 | # JavaScript files you want as :defaults (application.js is always included). 39 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 40 | 41 | # Configure the default encoding used in templates for Ruby 1.9. 42 | config.encoding = "utf-8" 43 | 44 | # Configure sensitive parameters which will be filtered from the log file. 45 | config.filter_parameters += [:password] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /config/applicationContext-dataSource.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /config/applicationContext-hibernate.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | ${hibernate.dialect} 28 | false 29 | ${hibernate.generate_statistics} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /config/cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" 3 | rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" 4 | tags = "~@wip" 5 | std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --color --tags #{tags}" 6 | %> 7 | default: <%= std_opts %> --tags ~@extended features 8 | wip: --tags @wip:3 --wip --color features 9 | rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --color --tags ~@wip 10 | extended: <%= std_opts %> features 11 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | SpringPetclinic::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | SpringPetclinic::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | SpringPetclinic::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | SpringPetclinic::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/load_classpath.rb: -------------------------------------------------------------------------------- 1 | # This requires that the generated classpath.rb in this directory was 2 | # already loaded. 3 | require 'java' 4 | $CLASSPATH << File.join(Rails.root, 'config') unless defined?($servlet_context) 5 | Maven.set_classpath unless defined?($servlet_context) 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | SpringPetclinic::Application.config.secret_token = '67e6b3dd0d831badf25a4a24ef1911f37cd474f2d172960daa630f2725a5ed1b89f4b0352d5d0a4e10e59599a3d8dce63f65e78c90672fbd9353a7eace783d56' 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | SpringPetclinic::Application.config.session_store :cookie_store, :key => '_spring-petclinic_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # SpringPetclinic::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/initializers/spring.rb: -------------------------------------------------------------------------------- 1 | require 'spring_helpers' 2 | 3 | module Spring 4 | SPRING_XML_CONFIG_FILES = %w( 5 | applicationContext-dataSource 6 | applicationContext-hibernate 7 | ).map { |c| 8 | "classpath:#{c}.xml" 9 | }.to_java :string 10 | 11 | CONTEXT = org.springframework.context.support.ClassPathXmlApplicationContext.new SPRING_XML_CONFIG_FILES 12 | def self.context 13 | CONTEXT 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | welcome: Willkommen 3 | required: muss angegeben werden 4 | notFound: wurde nicht gefunden 5 | duplicate: ist bereits vergeben 6 | nonNumeric: darf nur numerisch sein 7 | duplicateFormSubmission: Wiederholtes Absenden des Formulars ist nicht erlaubt 8 | "typeMismatch.date": ungültiges Datum 9 | "typeMismatch.birthDate": ungültiges Datum 10 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | welcome: Welcome 6 | required: is required 7 | notFound: has not been found 8 | duplicate: is already in use 9 | nonNumeric: must be all numeric 10 | duplicateFormSubmission: Duplicate form submission is not allowed 11 | "typeMismatch.date": invalid date 12 | "typeMismatch.birthDate": invalid date 13 | -------------------------------------------------------------------------------- /config/log4j.properties: -------------------------------------------------------------------------------- 1 | # For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml! 2 | # For all other servers: Comment out the Log4J listener in web.xml to activate Log4J. 3 | log4j.rootLogger=INFO, logfile 4 | 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n 8 | 9 | log4j.appender.logfile=org.apache.log4j.RollingFileAppender 10 | log4j.appender.logfile.File=log/petclinic.log 11 | log4j.appender.logfile.MaxFileSize=512KB 12 | # Keep three backup files. 13 | log4j.appender.logfile.MaxBackupIndex=3 14 | # Pattern to output: date priority [category] - message 15 | log4j.appender.logfile.layout=org.apache.log4j.PatternLayout 16 | log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n 17 | 18 | log4j.logger.org.springframework.samples.petclinic.aspects=DEBUG 19 | 20 | # Send hibernate output to logs as well 21 | log4j.logger.org.hibernate.SQL=DEBUG, logfile 22 | log4j.logger.org.hibernate=INFO, logfile -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | SpringPetclinic::Application.routes.draw do 2 | resources :vets 3 | 4 | resources :owners do 5 | collection do 6 | get :search 7 | end 8 | 9 | resources :pets do 10 | resources :visits 11 | end 12 | end 13 | 14 | # The priority is based upon order of creation: 15 | # first created -> highest priority. 16 | 17 | # Sample of regular route: 18 | # match 'products/:id' => 'catalog#view' 19 | # Keep in mind you can assign values other than :controller and :action 20 | 21 | # Sample of named route: 22 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 23 | # This route can be invoked with purchase_url(:id => product.id) 24 | 25 | # Sample resource route (maps HTTP verbs to controller actions automatically): 26 | # resources :products 27 | 28 | # Sample resource route with options: 29 | # resources :products do 30 | # member do 31 | # get 'short' 32 | # post 'toggle' 33 | # end 34 | # 35 | # collection do 36 | # get 'sold' 37 | # end 38 | # end 39 | 40 | # Sample resource route with sub-resources: 41 | # resources :products do 42 | # resources :comments, :sales 43 | # resource :seller 44 | # end 45 | 46 | # Sample resource route with more complex sub-resources 47 | # resources :products do 48 | # resources :comments 49 | # resources :sales do 50 | # get 'recent', :on => :collection 51 | # end 52 | # end 53 | 54 | # Sample resource route within a namespace: 55 | # namespace :admin do 56 | # # Directs /admin/products/* to Admin::ProductsController 57 | # # (app/controllers/admin/products_controller.rb) 58 | # resources :products 59 | # end 60 | 61 | # You can have the root of your site routed with "root" 62 | # just remember to delete public/index.html. 63 | root :to => "welcome#index" 64 | 65 | # See how all your routes lay out with "rake routes" 66 | 67 | # This is a legacy wild controller route that's not recommended for RESTful applications. 68 | # Note: This route will make all actions in every controller accessible via GET requests. 69 | # match ':controller(/:action(/:id(.:format)))' 70 | end 71 | -------------------------------------------------------------------------------- /config/warble.rb: -------------------------------------------------------------------------------- 1 | # Disable Rake-environment-task framework detection by uncommenting/setting to false 2 | # Warbler.framework_detection = false 3 | 4 | # Warbler web application assembly configuration file 5 | Warbler::Config.new do |config| 6 | # Features: additional options controlling how the jar is built. 7 | # Currently the following features are supported: 8 | # - gemjar: package the gem repository in a jar file in WEB-INF/lib 9 | # - executable: embed a web server and make the war executable 10 | # - compiled: compile .rb files to .class files 11 | # config.features = %w(gemjar) 12 | 13 | # Application directories to be included in the webapp. 14 | config.dirs = %w(app config lib log vendor tmp) 15 | 16 | # Additional files/directories to include, above those in config.dirs 17 | # config.includes = FileList["db"] 18 | 19 | # Additional files/directories to exclude 20 | # config.excludes = FileList["lib/tasks/*"] 21 | 22 | # Additional Java .jar files to include. Note that if .jar files are placed 23 | # in lib (and not otherwise excluded) then they need not be mentioned here. 24 | # JRuby and JRuby-Rack are pre-loaded in this list. Be sure to include your 25 | # own versions if you directly set the value 26 | # config.java_libs += FileList["lib/java/*.jar"] 27 | require 'config/initializers/classpath' 28 | config.java_libs += Maven.jar_files 29 | 30 | # Loose Java classes and miscellaneous files to be included. 31 | config.java_classes = FileList["target/classes/**/*", "config/applicationContext-{dataSource,hibernate}.xml"] 32 | 33 | # One or more pathmaps defining how the java classes should be copied into 34 | # the archive. The example pathmap below accompanies the java_classes 35 | # configuration above. See http://rake.rubyforge.org/classes/String.html#M000017 36 | # for details of how to specify a pathmap. 37 | config.pathmaps.java_classes += ["%{target/classes/,}p", "%{config/,}p"] 38 | 39 | # Bundler support is built-in. If Warbler finds a Gemfile in the 40 | # project directory, it will be used to collect the gems to bundle 41 | # in your application. If you wish to explicitly disable this 42 | # functionality, uncomment here. 43 | # config.bundler = false 44 | 45 | # An array of Bundler groups to avoid including in the war file. 46 | # Defaults to ["development", "test"]. 47 | # config.bundle_without = [] 48 | 49 | # Other gems to be included. If you don't use Bundler or a gemspec 50 | # file, you need to tell Warbler which gems your application needs 51 | # so that they can be packaged in the archive. 52 | # For Rails applications, the Rails gems are included by default 53 | # unless the vendor/rails directory is present. 54 | # config.gems += ["activerecord-jdbcmysql-adapter", "jruby-openssl"] 55 | # config.gems << "tzinfo" 56 | 57 | # Uncomment this if you don't want to package rails gem. 58 | # config.gems -= ["rails"] 59 | 60 | # The most recent versions of gems are used. 61 | # You can specify versions of gems by using a hash assignment: 62 | # config.gems["rails"] = "2.3.10" 63 | 64 | # You can also use regexps or Gem::Dependency objects for flexibility or 65 | # finer-grained control. 66 | # config.gems << /^merb-/ 67 | # config.gems << Gem::Dependency.new("merb-core", "= 0.9.3") 68 | 69 | # Include gem dependencies not mentioned specifically. Default is 70 | # true, uncomment to turn off. 71 | # config.gem_dependencies = false 72 | 73 | # Array of regular expressions matching relative paths in gems to be 74 | # excluded from the war. Defaults to empty, but you can set it like 75 | # below, which excludes test files. 76 | # config.gem_excludes = [/^(test|spec)\//] 77 | 78 | # Pathmaps for controlling how application files are copied into the archive 79 | # config.pathmaps.application = ["WEB-INF/%p"] 80 | 81 | # Name of the archive (without the extension). Defaults to the basename 82 | # of the project directory. 83 | # config.jar_name = "mywar" 84 | 85 | # Name of the MANIFEST.MF template for the war file. Defaults to a simple 86 | # MANIFEST.MF that contains the version of Warbler used to create the war file. 87 | # config.manifest_file = "config/MANIFEST.MF" 88 | 89 | # When using the 'compiled' feature and specified, only these Ruby 90 | # files will be compiled. Default is to compile all \.rb files in 91 | # the application. 92 | # config.compiled_ruby_files = FileList['app/**/*.rb'] 93 | 94 | # === War files only below here === 95 | 96 | # Path to the pre-bundled gem directory inside the war file. Default 97 | # is 'WEB-INF/gems'. Specify path if gems are already bundled 98 | # before running Warbler. This also sets 'gem.path' inside web.xml. 99 | # config.gem_path = "WEB-INF/vendor/bundler_gems" 100 | 101 | # Files for WEB-INF directory (next to web.xml). This contains 102 | # web.xml by default. If there is an .erb-File it will be processed 103 | # with webxml-config. You may want to exclude this file via 104 | # config.excludes. 105 | # config.webinf_files += FileList["jboss-web.xml"] 106 | 107 | # Files to be included in the root of the webapp. Note that files in public 108 | # will have the leading 'public/' part of the path stripped during staging. 109 | # config.public_html = FileList["public/**/*", "doc/**/*"] 110 | 111 | # Pathmaps for controlling how public HTML files are copied into the .war 112 | # config.pathmaps.public_html = ["%{public/,}p"] 113 | 114 | # Value of RAILS_ENV for the webapp -- default as shown below 115 | # config.webxml.rails.env = ENV['RAILS_ENV'] || 'production' 116 | 117 | # Application booter to use, one of :rack, :rails, or :merb (autodetected by default) 118 | # config.webxml.booter = :rails 119 | 120 | # Set JRuby to run in 1.9 mode. 121 | # config.webxml.jruby.compat.version = "1.9" 122 | 123 | # When using the :rack booter, "Rackup" script to use. 124 | # - For 'rackup.path', the value points to the location of the rackup 125 | # script in the web archive file. You need to make sure this file 126 | # gets included in the war, possibly by adding it to config.includes 127 | # or config.webinf_files above. 128 | # - For 'rackup', the rackup script you provide as an inline string 129 | # is simply embedded in web.xml. 130 | # The script is evaluated in a Rack::Builder to load the application. 131 | # Examples: 132 | # config.webxml.rackup.path = 'WEB-INF/hello.ru' 133 | # config.webxml.rackup = %{require './lib/demo'; run Rack::Adapter::Camping.new(Demo)} 134 | # config.webxml.rackup = require 'cgi' && CGI::escapeHTML(File.read("config.ru")) 135 | 136 | # Control the pool of Rails runtimes. Leaving unspecified means 137 | # the pool will grow as needed to service requests. It is recommended 138 | # that you fix these values when running a production server! 139 | # config.webxml.jruby.min.runtimes = 2 140 | # config.webxml.jruby.max.runtimes = 4 141 | 142 | # JNDI data source name 143 | # config.webxml.jndi = 'jdbc/rails' 144 | end 145 | -------------------------------------------------------------------------------- /features/owners.feature: -------------------------------------------------------------------------------- 1 | Feature: Owners 2 | 3 | Scenario: Owner page 4 | Given I am on the owners search page 5 | And I press "Find Owners" 6 | When I follow "George Franklin" 7 | Then I should see "Pets and Visits" 8 | And I should see "Leo" 9 | 10 | Scenario: Navigate to Add Owner 11 | Given I am on the owners search page 12 | When I follow "Add Owner" 13 | Then I should see "New Owner" within "h2" 14 | 15 | Scenario: Add New Owner 16 | Given I am on the new owner page 17 | When I fill in the following: 18 | | First name | Dan | 19 | | Last name | Wood | 20 | | Address | 123 Main St | 21 | | City | Anywhere | 22 | | Telephone | 5555555 | 23 | And I press "Add Owner" 24 | Then I should see "Owner Information" within "h2" 25 | And I should see "Dan Wood" 26 | 27 | Scenario: Edit Owner 28 | Given I am on the owners search page 29 | And I press "Find Owners" 30 | When I follow "George Franklin" 31 | And follow "Edit Owner" 32 | And fill in "City" with "Minneapolis" 33 | And press "Update Owner" 34 | Then I should see "Owner Information" within "h2" 35 | And I should see "Minneapolis" 36 | 37 | Scenario: Search Owners 38 | Given I am on the owners search page 39 | When I fill in "name" with "Davis" 40 | And press "Find Owners" 41 | Then I should see "Betty Davis" 42 | And I should see "Harold Davis" 43 | -------------------------------------------------------------------------------- /features/pets.feature: -------------------------------------------------------------------------------- 1 | Feature: Pets 2 | 3 | Scenario: Add New Pet 4 | Given I am on the owners search page 5 | And I press "Find Owners" 6 | And I follow "George Franklin" 7 | And I follow "Add New Pet" 8 | When I fill in "Name" with "Lizzie" 9 | And select "lizard" from "Type" 10 | And fill in "birthDate" with "2011-04-01" if present 11 | And press "Add Pet" 12 | Then I should see "Owner Information" 13 | And I should see "George Franklin" 14 | And I should see "Lizzie" 15 | And I should see "lizard" 16 | 17 | Scenario: Edit Pet 18 | Given I am on the owners search page 19 | And I press "Find Owners" 20 | And I follow "George Franklin" 21 | And I follow "Edit Pet" 22 | When I fill in "Name" with "Leoni" 23 | And press "Update Pet" 24 | Then I should see "Leoni" 25 | 26 | -------------------------------------------------------------------------------- /features/smoke_test.feature: -------------------------------------------------------------------------------- 1 | Feature: Browsing around 2 | 3 | Scenario: The home page 4 | Given I am on the home page 5 | Then I should see "Welcome" 6 | 7 | Scenario: Vets 8 | Given I am on the home page 9 | And I follow "Display all veterinarians" 10 | Then I should be on the vets page 11 | And I should see "Veterinarians" within "h2" 12 | 13 | Scenario: Owners 14 | Given I am on the home page 15 | And I follow "Find owner" 16 | Then I should see "Last name" 17 | When I fill in "lastName" with "Franklin" 18 | And I press "Find Owners" 19 | Then I should see "George" 20 | And I should see "Franklin" 21 | 22 | -------------------------------------------------------------------------------- /features/step_definitions/custom_web_steps.rb: -------------------------------------------------------------------------------- 1 | When /^(?:|I )fill in "([^\"]*)" with "([^\"]*)"(?: within "([^\"]*)")? if present$/ do |field, value, selector| 2 | with_scope(selector) do 3 | begin 4 | fill_in(field, :with => value) 5 | rescue Capybara::ElementNotFound 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /features/step_definitions/xml_json_steps.rb: -------------------------------------------------------------------------------- 1 | When /^(?:|I )download "([^\"]*)"(?: within "([^\"]*)")?$/ do |link, selector| 2 | with_scope(selector) do 3 | begin 4 | click_link(link) 5 | rescue Celerity::Exception::UnexpectedPageException 6 | # This is ok 7 | end 8 | end 9 | end 10 | 11 | Then /^I should see an XML document$/ do 12 | page.response_headers["Content-Type"].should =~ /xml/ 13 | @document = Nokogiri::XML(source) 14 | end 15 | 16 | Then /^I should see an element containing "([^\"]*)"$/ do |text| 17 | @document.xpath("//.").detect {|n| n.text =~ /#{text}/ }.should_not be_nil 18 | end 19 | 20 | Then /^I should see a JSON document$/ do 21 | page.response_headers["Content-Type"].should =~ /json/ 22 | @document = JSON.parse(source) 23 | end 24 | 25 | Then /^I should see a JSON structure containing "([^\"]*)"$/ do |arg| 26 | traverse(@document) do |elem| 27 | elem.to_s =~ /#{arg}/ 28 | end.should be_true 29 | end 30 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | ENV["RAILS_ENV"] ||= "test" 8 | require File.expand_path(File.dirname(__FILE__) + '/../../config/environment') 9 | 10 | require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support 11 | require 'cucumber/rails/world' 12 | require 'cucumber/web/tableish' 13 | 14 | require 'capybara/rails' 15 | require 'capybara/cucumber' 16 | require 'capybara/session' 17 | #require 'cucumber/rails/capybara_javascript_emulation' # Lets you click links with onclick javascript handlers without using @culerity or @javascript 18 | # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In 19 | # order to ease the transition to Capybara we set the default here. If you'd 20 | # prefer to use XPath just remove this line and adjust any selectors in your 21 | # steps to use the XPath syntax. 22 | Capybara.default_selector = :css 23 | #Capybara.default_driver = :selenium 24 | 25 | 26 | # If you set this to false, any error raised from within your app will bubble 27 | # up to your step definition and out to cucumber unless you catch it somewhere 28 | # on the way. You can make Rails rescue errors and render error pages on a 29 | # per-scenario basis by tagging a scenario or feature with the @allow-rescue tag. 30 | # 31 | # If you set this to true, Rails will rescue all errors and render error 32 | # pages, more or less in the same way your application would behave in the 33 | # default production environment. It's not recommended to do this for all 34 | # of your scenarios, as this makes it hard to discover errors in your application. 35 | ActionController::Base.allow_rescue = false 36 | 37 | # How to clean your database when transactions are turned off. See 38 | # http://github.com/bmabey/database_cleaner for more info. 39 | if defined?(ActiveRecord::Base) 40 | begin 41 | require 'database_cleaner' 42 | DatabaseCleaner.strategy = :truncation 43 | rescue LoadError => ignore_if_database_cleaner_not_present 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /features/support/paths.rb: -------------------------------------------------------------------------------- 1 | module NavigationHelpers 2 | # Maps a name to a path. Used by the 3 | # 4 | # When /^I go to (.+)$/ do |page_name| 5 | # 6 | # step definition in web_steps.rb 7 | # 8 | def path_to(page_name) 9 | case page_name 10 | 11 | when /the home\s?page/ 12 | '/' 13 | 14 | # Add more mappings here. 15 | # Here is an example that pulls values out of the Regexp: 16 | # 17 | # when /^(.*)'s profile page$/i 18 | # user_profile_path(User.find_by_login($1)) 19 | 20 | when /the vets page/ 21 | '/vets' 22 | 23 | when /the owners search page/ 24 | '/owners/search' 25 | 26 | when /the new owner page/ 27 | '/owners/new' 28 | 29 | when %r{^/[^ ]+} 30 | page_name 31 | 32 | else 33 | begin 34 | page_name =~ /the (.*) page/ 35 | path_components = $1.split(/\s+/) 36 | self.send(path_components.push('path').join('_').to_sym) 37 | rescue Object => e 38 | raise "Can't find mapping from \"#{page_name}\" to a path.\n" + 39 | "Now, go and add a mapping in #{__FILE__}" 40 | end 41 | end 42 | end 43 | end 44 | 45 | World(NavigationHelpers) 46 | -------------------------------------------------------------------------------- /features/support/traverse.rb: -------------------------------------------------------------------------------- 1 | module Traverse 2 | def traverse(obj,&block) 3 | case obj 4 | when Hash 5 | obj.any? {|k,v| traverse(k,&block) || traverse(v,&block) } 6 | when Array 7 | obj.any? {|el| traverse(el,&block) } 8 | else 9 | block.call(obj) 10 | end 11 | end 12 | end 13 | 14 | World(Traverse) 15 | -------------------------------------------------------------------------------- /features/vets.feature: -------------------------------------------------------------------------------- 1 | Feature: Vets 2 | 3 | Scenario: View vets as XML 4 | Given I am on the vets page 5 | When I download "View as XML" 6 | Then I should see an XML document 7 | And I should see an element containing "Carter" 8 | 9 | @extended 10 | Scenario: View vets as JSON 11 | Given I am on the vets page 12 | When I download "View as JSON" 13 | Then I should see a JSON document 14 | And I should see a JSON structure containing "Carter" 15 | -------------------------------------------------------------------------------- /features/visits.feature: -------------------------------------------------------------------------------- 1 | Feature: Visits 2 | 3 | Scenario: Add a visit 4 | Given I am on the owners search page 5 | And I press "Find Owners" 6 | And I follow "George Franklin" 7 | And I follow "Add Visit" 8 | When I fill in "Description" with "annual checkup" 9 | And press "Add Visit" 10 | Then I should see "annual checkup" 11 | 12 | Scenario: Visits Atom Feed 13 | Given I am on the owners search page 14 | And I press "Find Owners" 15 | And I follow "George Franklin" 16 | When I download "Atom Feed" 17 | Then I should see an XML document 18 | And I should see an element containing "annual checkup" 19 | -------------------------------------------------------------------------------- /lib/java_ext.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | class java::util::Date 3 | def to_date 4 | t = to_time 5 | Date.civil(t.year, t.month, t.day) 6 | end 7 | 8 | def to_time 9 | Time.at(time / 1000) 10 | end 11 | 12 | def self.from_date(datetime) 13 | java.util.Date.new(datetime.to_time.to_i * 1000) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/spring_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | 3 | module org::springframework::beans::factory::BeanFactory 4 | def [](key) 5 | getBean(key) 6 | end 7 | 8 | def include?(key) 9 | containsBean(key) 10 | end 11 | end 12 | 13 | module Spring 14 | def self.included(base) 15 | base.extend self 16 | end 17 | 18 | def context 19 | CONTEXT 20 | end 21 | 22 | def clinic 23 | context['clinic'] 24 | end 25 | end 26 | 27 | class org::springframework::samples::petclinic::BaseEntity 28 | def self.name 29 | super.split('::')[-1] 30 | end 31 | 32 | def persisted? 33 | !new? 34 | end 35 | 36 | # Since the Hibernate model classes are technically not fully 37 | # unloaded every request (even though the constants are), we need to 38 | # reset validations here. 39 | def self.before_remove_const 40 | if respond_to?(:_validators) 41 | _validators.clear 42 | reset_callbacks(:validate) 43 | end 44 | end 45 | 46 | def update_attributes(attrs) 47 | date_values = {} 48 | attrs.each do |k,v| 49 | if k =~ /\(/ 50 | md = /(.*)\((.*)\)/.match(k) 51 | date_values[md[1]] ||= [] 52 | date_values[md[1]][md[2].to_i - 1] = v.to_i 53 | else 54 | send("#{k}=", v) if respond_to?("#{k}=") 55 | end 56 | end 57 | date_values.each do |k,v| 58 | if v.length > 3 59 | send("#{k}=", Time.zone.local(*v)) 60 | else 61 | send("#{k}=", Date.new(*v)) 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/tasks/classpath.rake: -------------------------------------------------------------------------------- 1 | desc "Setup up the CLASSPATH for the Java libraries" 2 | task :maven_classpath do 3 | sh "mvn org.jruby.plugins:jruby-rake-plugin:classpath -Djruby.classpath.rb=config/initializers/classpath.rb" 4 | end 5 | -------------------------------------------------------------------------------- /lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | 8 | unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks 9 | 10 | task 'db:test:prepare' do 11 | #noop 12 | end 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 18 | t.fork = false 19 | t.profile = 'default' 20 | end 21 | 22 | Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| 23 | t.fork = false 24 | t.profile = 'wip' 25 | end 26 | 27 | Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| 28 | t.fork = false 29 | t.profile = 'rerun' 30 | end 31 | 32 | Cucumber::Rake::Task.new({:extended => 'db:test:prepare'}, 'Run extended features') do |t| 33 | t.fork = false 34 | t.profile = 'extended' 35 | end 36 | 37 | desc 'Run all features' 38 | task :all => [:ok, :wip] 39 | end 40 | desc 'Alias for cucumber:ok' 41 | task :cucumber => 'cucumber:ok' 42 | 43 | task :default => :cucumber 44 | 45 | task :features => :cucumber do 46 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 47 | end 48 | rescue LoadError 49 | desc 'cucumber rake task not available (cucumber not installed)' 50 | task :cucumber do 51 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 52 | end 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /lib/tasks/database.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | task "test:prepare" do 3 | mysql_connect = "mysql -N -u pc --password=pc petclinic" 4 | output = `echo 'show tables' | #{mysql_connect}` 5 | fail "mysql failed to connect to petclinic database:\n#{output}" unless $?.success? 6 | truncate_script = 'SET foreign_key_checks=0;' 7 | output.chomp.split.each do |table| 8 | truncate_script << " truncate table #{table};" 9 | end 10 | sh "echo '#{truncate_script}' | #{mysql_connect}" 11 | populate_script = File.expand_path('../../../src/main/resources/db/mysql/populateDB.txt', __FILE__) 12 | sh "echo '\\. #{populate_script}' | #{mysql_connect}" 13 | end 14 | 15 | desc "Reset the petclinic database to its original state" 16 | task :reset => "db:test:prepare" 17 | 18 | desc "Create the petclinic database" 19 | task :create do 20 | init_script = File.expand_path('../../../src/main/resources/db/mysql/initDB.txt', __FILE__) 21 | sh "echo '\\. #{init_script}' | mysql -u root" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tasks/docs.rake: -------------------------------------------------------------------------------- 1 | desc "Generate an HTML document describing all commits" 2 | task :commitdoc do 3 | header = <<-HDR 4 | # Refactoring to Rails Commits 5 | 6 | The commits in this project are meant to be read from beginning to 7 | end. This document is an easy way to do that. It can be generated 8 | directly from the commit logs. You can regenerate it by running 9 | `rake commitdoc`. 10 | 11 | HDR 12 | format = '### %s%n%n- [Commit: %h](https://github.com/nicksieger/refactoring-to-rails/commit/%H)%n%n%b%n' 13 | sh "(echo '#{header}' && git log --reverse --format=format:'#{format}' refactor-base..HEAD) | maruku -o- > commits.html" 14 | end 15 | -------------------------------------------------------------------------------- /petclinic-readme.txt: -------------------------------------------------------------------------------- 1 | ========================================================================== 2 | === Spring PetClinic Sample Application 3 | ========================================================================== 4 | 5 | @author Ken Krebs 6 | @author Juergen Hoeller 7 | @author Rob Harrop 8 | @author Costin Leau 9 | @author Sam Brannen 10 | @author Scott Andrews 11 | 12 | ========================================================================== 13 | === Data Access Strategies 14 | ========================================================================== 15 | 16 | PetClinic features alternative DAO implementations and application 17 | configurations for JDBC, Hibernate, and JPA, with HSQLDB and MySQL as 18 | target databases. The default PetClinic configuration is JDBC on HSQLDB. 19 | See "src/main/resources/jdbc.properties" as well as web.xml and 20 | applicationContext-*.xml in the "src/main/webapp/WEB-INF" folder for 21 | details. A simple comment change in web.xml switches between the data 22 | access strategies. 23 | 24 | The JDBC and Hibernate versions of PetClinic also demonstrate JMX support 25 | via the use of for exporting MBeans. 26 | SimpleJdbcClinic exposes the SimpleJdbcClinicMBean management interface 27 | via JMX through the use of the @ManagedResource and @ManagedOperation 28 | annotations; whereas, the HibernateStatistics service is exposed via JMX 29 | through auto-detection of the service MBean. You can start up the JDK's 30 | JConsole to manage the exported bean. 31 | 32 | All data access strategies can work with JTA for transaction management by 33 | activating the JtaTransactionManager and a JndiObjectFactoryBean that 34 | refers to a transactional container DataSource. The default for JDBC is 35 | DataSourceTransactionManager; for Hibernate, HibernateTransactionManager; 36 | for JPA, JpaTransactionManager. Those local strategies allow for working 37 | with any locally defined DataSource. 38 | 39 | Note that the sample configurations for JDBC, Hibernate, and JPA configure 40 | a BasicDataSource from the Apache Commons DBCP project for connection 41 | pooling. 42 | 43 | ========================================================================== 44 | === Build and Deployment 45 | ========================================================================== 46 | 47 | The Spring PetClinic sample application is built using Maven. 48 | When the project is first built, Maven will automatically download all required 49 | dependencies (if these haven't been downloaded before). Thus the initial build 50 | may take a few minutes depending on the speed of your Internet connection, 51 | but subsequent builds will be much faster. 52 | 53 | Available build commands: 54 | 55 | - mvn clean --> cleans the project 56 | - mvn clean test --> cleans the project and runs all tests 57 | - mvn clean package --> cleans the project and builds the WAR 58 | 59 | After building the project with "mvn clean package", you will find the 60 | resulting WAR file in the "target/" directory. By default, an 61 | embedded HSQLDB instance in configured. No other steps are necessary to 62 | get the data source up and running: you can simply deploy the built WAR 63 | file directly to your Servlet container. 64 | 65 | For MySQL, you'll need to use the corresponding schema and SQL scripts in 66 | the "db/mysql" subdirectory. Follow the steps outlined in 67 | "db/mysql/petclinic_db_setup_mysql.txt" for explicit details. 68 | 69 | In you intend to use a local DataSource, the JDBC settings can be adapted 70 | in "src/main/resources/jdbc.properties". To use a JTA DataSource, you need 71 | to set up corresponding DataSources in your Java EE container. 72 | 73 | Notes on enabling Log4J: 74 | - Log4J is disabled by default due to issues with JBoss. 75 | - Uncomment the Log4J listener in "WEB-INF/web.xml" to enable logging. 76 | 77 | Notes on service static resources: 78 | - Most web containers provide a 'default' servlet for serving static 79 | resources; Petclinic relies on it for its images. 80 | - On containers without such a mapping (ex: GlassFish), uncomment the 81 | 'default' declaration in "WEB-INF/web.xml". 82 | 83 | ========================================================================== 84 | === JPA on Tomcat 85 | ========================================================================== 86 | 87 | This section provides tips on using the Java Persistence API (JPA) on 88 | Apache Tomcat 4.x or higher with a persistence provider that requires 89 | class instrumentation (such as TopLink Essentials). 90 | 91 | To use JPA class instrumentation, Tomcat has to be instructed to use a 92 | custom class loader which supports instrumentation. See the JPA section of 93 | the Spring reference manual for complete details. 94 | 95 | The basic steps are: 96 | - Copy "org.springframework.instrument.tomcat-3.0.0.RELEASE.jar" from the 97 | Spring distribution to "TOMCAT_HOME/server/lib". 98 | - If you're running on Tomcat 5.x, modify "TOMCAT_HOME/conf/server.xml" 99 | and add a new "" element for 'petclinic' (see below). You can 100 | alternatively deploy the WAR including "META-INF/context.xml" from this 101 | sample application's "src/main/webapp" directory, in which case you 102 | will need to uncomment the Loader element in that file to enable the 103 | use of the TomcatInstrumentableClassLoader. 104 | 105 | 106 | 107 | 108 | ... 109 | 110 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/favicon.ico -------------------------------------------------------------------------------- /public/images/banner-graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/banner-graphic.png -------------------------------------------------------------------------------- /public/images/bullet-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/bullet-arrow.png -------------------------------------------------------------------------------- /public/images/pets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/pets.png -------------------------------------------------------------------------------- /public/images/rails-banner-graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/rails-banner-graphic.png -------------------------------------------------------------------------------- /public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/rails.png -------------------------------------------------------------------------------- /public/images/springsource-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/springsource-logo.png -------------------------------------------------------------------------------- /public/images/submit-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/images/submit-bg.png -------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /public/javascripts/rails.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Technique from Juriy Zaytsev 3 | // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ 4 | function isEventSupported(eventName) { 5 | var el = document.createElement('div'); 6 | eventName = 'on' + eventName; 7 | var isSupported = (eventName in el); 8 | if (!isSupported) { 9 | el.setAttribute(eventName, 'return;'); 10 | isSupported = typeof el[eventName] == 'function'; 11 | } 12 | el = null; 13 | return isSupported; 14 | } 15 | 16 | function isForm(element) { 17 | return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' 18 | } 19 | 20 | function isInput(element) { 21 | if (Object.isElement(element)) { 22 | var name = element.nodeName.toUpperCase() 23 | return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' 24 | } 25 | else return false 26 | } 27 | 28 | var submitBubbles = isEventSupported('submit'), 29 | changeBubbles = isEventSupported('change') 30 | 31 | if (!submitBubbles || !changeBubbles) { 32 | // augment the Event.Handler class to observe custom events when needed 33 | Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( 34 | function(init, element, eventName, selector, callback) { 35 | init(element, eventName, selector, callback) 36 | // is the handler being attached to an element that doesn't support this event? 37 | if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || 38 | (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { 39 | // "submit" => "emulated:submit" 40 | this.eventName = 'emulated:' + this.eventName 41 | } 42 | } 43 | ) 44 | } 45 | 46 | if (!submitBubbles) { 47 | // discover forms on the page by observing focus events which always bubble 48 | document.on('focusin', 'form', function(focusEvent, form) { 49 | // special handler for the real "submit" event (one-time operation) 50 | if (!form.retrieve('emulated:submit')) { 51 | form.on('submit', function(submitEvent) { 52 | var emulated = form.fire('emulated:submit', submitEvent, true) 53 | // if custom event received preventDefault, cancel the real one too 54 | if (emulated.returnValue === false) submitEvent.preventDefault() 55 | }) 56 | form.store('emulated:submit', true) 57 | } 58 | }) 59 | } 60 | 61 | if (!changeBubbles) { 62 | // discover form inputs on the page 63 | document.on('focusin', 'input, select, texarea', function(focusEvent, input) { 64 | // special handler for real "change" events 65 | if (!input.retrieve('emulated:change')) { 66 | input.on('change', function(changeEvent) { 67 | input.fire('emulated:change', changeEvent, true) 68 | }) 69 | input.store('emulated:change', true) 70 | } 71 | }) 72 | } 73 | 74 | function handleRemote(element) { 75 | var method, url, params; 76 | 77 | var event = element.fire("ajax:before"); 78 | if (event.stopped) return false; 79 | 80 | if (element.tagName.toLowerCase() === 'form') { 81 | method = element.readAttribute('method') || 'post'; 82 | url = element.readAttribute('action'); 83 | params = element.serialize(); 84 | } else { 85 | method = element.readAttribute('data-method') || 'get'; 86 | url = element.readAttribute('href'); 87 | params = {}; 88 | } 89 | 90 | new Ajax.Request(url, { 91 | method: method, 92 | parameters: params, 93 | evalScripts: true, 94 | 95 | onComplete: function(request) { element.fire("ajax:complete", request); }, 96 | onSuccess: function(request) { element.fire("ajax:success", request); }, 97 | onFailure: function(request) { element.fire("ajax:failure", request); } 98 | }); 99 | 100 | element.fire("ajax:after"); 101 | } 102 | 103 | function handleMethod(element) { 104 | var method = element.readAttribute('data-method'), 105 | url = element.readAttribute('href'), 106 | csrf_param = $$('meta[name=csrf-param]')[0], 107 | csrf_token = $$('meta[name=csrf-token]')[0]; 108 | 109 | var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); 110 | element.parentNode.insert(form); 111 | 112 | if (method !== 'post') { 113 | var field = new Element('input', { type: 'hidden', name: '_method', value: method }); 114 | form.insert(field); 115 | } 116 | 117 | if (csrf_param) { 118 | var param = csrf_param.readAttribute('content'), 119 | token = csrf_token.readAttribute('content'), 120 | field = new Element('input', { type: 'hidden', name: param, value: token }); 121 | form.insert(field); 122 | } 123 | 124 | form.submit(); 125 | } 126 | 127 | 128 | document.on("click", "*[data-confirm]", function(event, element) { 129 | var message = element.readAttribute('data-confirm'); 130 | if (!confirm(message)) event.stop(); 131 | }); 132 | 133 | document.on("click", "a[data-remote]", function(event, element) { 134 | if (event.stopped) return; 135 | handleRemote(element); 136 | event.stop(); 137 | }); 138 | 139 | document.on("click", "a[data-method]", function(event, element) { 140 | if (event.stopped) return; 141 | handleMethod(element); 142 | event.stop(); 143 | }); 144 | 145 | document.on("submit", function(event) { 146 | var element = event.findElement(), 147 | message = element.readAttribute('data-confirm'); 148 | if (message && !confirm(message)) { 149 | event.stop(); 150 | return false; 151 | } 152 | 153 | var inputs = element.select("input[type=submit][data-disable-with]"); 154 | inputs.each(function(input) { 155 | input.disabled = true; 156 | input.writeAttribute('data-original-value', input.value); 157 | input.value = input.readAttribute('data-disable-with'); 158 | }); 159 | 160 | var element = event.findElement("form[data-remote]"); 161 | if (element) { 162 | handleRemote(element); 163 | event.stop(); 164 | } 165 | }); 166 | 167 | document.on("ajax:after", "form", function(event, element) { 168 | var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); 169 | inputs.each(function(input) { 170 | input.value = input.readAttribute('data-original-value'); 171 | input.removeAttribute('data-original-value'); 172 | input.disabled = false; 173 | }); 174 | }); 175 | 176 | Ajax.Responders.register({ 177 | onCreate: function(request) { 178 | var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; 179 | 180 | if (csrf_meta_tag) { 181 | var header = 'X-CSRF-Token', 182 | token = csrf_meta_tag.readAttribute('content'); 183 | 184 | if (!request.options.requestHeaders) { 185 | request.options.requestHeaders = {}; 186 | } 187 | request.options.requestHeaders[header] = token; 188 | } 189 | } 190 | }); 191 | })(); 192 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/public/stylesheets/.gitkeep -------------------------------------------------------------------------------- /public/stylesheets/petclinic.css: -------------------------------------------------------------------------------- 1 | /* main elements */ 2 | 3 | body,div,td { 4 | font-family: Arial, Helvetica, sans-serif; 5 | font-size: 12px; 6 | color: #666; 7 | } 8 | 9 | body { 10 | background-color: #fff; 11 | background-image: url(../images/rails-banner-graphic.png); 12 | background-position: top center; 13 | background-repeat: no-repeat; 14 | text-align: center; 15 | min-width: 600px; 16 | margin-top: 60px; 17 | margin-left: auto; 18 | margin-right: auto; 19 | } 20 | 21 | div { 22 | margin: 5px 25px 5px 25px; 23 | text-align: left; 24 | } 25 | 26 | /* header and footer elements */ 27 | 28 | #main { 29 | margin:0 auto; 30 | position:relative; 31 | top: 35px; 32 | left:0px; 33 | width:560px; 34 | text-align:left; 35 | } 36 | 37 | .footer { 38 | background:#fff; 39 | border:none; 40 | margin-top:20px; 41 | border-top:1px solid #999999; 42 | width:100%; 43 | } 44 | 45 | .footer td {color:#999999;} 46 | 47 | .footer a:link {color: #7db223;} 48 | 49 | 50 | /* text styles */ 51 | 52 | h1,h2,h3 { 53 | font-family: Helvetica, sans-serif; 54 | color: #7db223; 55 | } 56 | 57 | h1 { 58 | font-size: 20px; 59 | line-height: 26px; 60 | } 61 | 62 | h2 { 63 | font-size: 18px; 64 | line-height: 24px; 65 | } 66 | 67 | h3 { 68 | font-size: 15px; 69 | line-height: 21px; 70 | color:#555; 71 | } 72 | 73 | h4 { 74 | font-size: 14px; 75 | line-height: 20px; 76 | } 77 | 78 | .errors { 79 | color: red; 80 | font-weight: bold; 81 | } 82 | 83 | a { 84 | text-decoration: underline; 85 | font-size: 13px; 86 | } 87 | 88 | a:link { 89 | color: #7db223; 90 | } 91 | 92 | a:hover { 93 | color: #456314; 94 | } 95 | 96 | a:active { 97 | color: #7db223; 98 | } 99 | 100 | a:visited { 101 | color: #7db223; 102 | } 103 | 104 | ul { 105 | list-style: disc url(../images/bullet-arrow.png); 106 | } 107 | 108 | li { 109 | padding-top: 5px; 110 | text-align: left; 111 | } 112 | 113 | li ul { 114 | list-style: square url(../images/bullet-arrow.png); 115 | } 116 | 117 | li ul li ul { 118 | list-style: circle none; 119 | } 120 | 121 | /* table elements */ 122 | 123 | table { 124 | background: #d6e2c3; 125 | margin: 3px 0 0 0; 126 | border: 4px solid #d6e2c3; 127 | border-collapse: collapse; 128 | } 129 | 130 | table table { 131 | margin: -5px 0; 132 | border: 0px solid #e0e7d3; 133 | width: 100%; 134 | } 135 | 136 | table td,table th { 137 | padding: 8px; 138 | } 139 | 140 | table th { 141 | font-size: 12px; 142 | text-align: left; 143 | font-weight: bold; 144 | } 145 | 146 | table thead { 147 | font-weight: bold; 148 | font-style: italic; 149 | background-color: #c2ceaf; 150 | } 151 | 152 | table a:link {color: #303030;} 153 | 154 | caption { 155 | caption-side: top; 156 | width: auto; 157 | text-align: left; 158 | font-size: 12px; 159 | color: #848f73; 160 | padding-bottom: 4px; 161 | } 162 | 163 | fieldset { 164 | background: #e0e7d3; 165 | padding: 8px; 166 | padding-bottom: 22px; 167 | border: none; 168 | width: 560px; 169 | } 170 | 171 | fieldset label { 172 | width: 70px; 173 | float: left; 174 | margin-top: 1.7em; 175 | margin-left: 20px; 176 | } 177 | 178 | fieldset textfield { 179 | margin: 3px; 180 | height: 20px; 181 | background: #e0e7d3; 182 | } 183 | 184 | fieldset textarea { 185 | margin: 3px; 186 | height: 165px; 187 | background: #e0e7d3; 188 | } 189 | 190 | fieldset input { 191 | margin: 3px; 192 | height: 20px; 193 | background: #e0e7d3; 194 | } 195 | 196 | fieldset table { 197 | width: 100%; 198 | } 199 | 200 | fieldset th { 201 | padding-left: 25px; 202 | } 203 | 204 | .table-buttons { 205 | background-color:#fff; 206 | border:none; 207 | } 208 | 209 | .table-buttons td { 210 | border:none; 211 | } 212 | 213 | .submit input { 214 | background:url(../images/submit-bg.png) repeat-x; 215 | border: 2px outset #d7b9c9; 216 | color:#383838; 217 | padding:2px 10px; 218 | font-size:11px; 219 | text-transform:uppercase; 220 | font-weight:bold; 221 | } 222 | 223 | .updated { 224 | background:#ecf1e5; 225 | font-size:11px; 226 | margin-left:2px; 227 | border:4px solid #ecf1e5; 228 | } 229 | 230 | .updated td { 231 | padding:2px 8px; 232 | font-size:11px; 233 | color:#888888; 234 | } 235 | -------------------------------------------------------------------------------- /script/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 4 | if vendored_cucumber_bin 5 | load File.expand_path(vendored_cucumber_bin) 6 | else 7 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 8 | require 'cucumber' 9 | load Cucumber::BINARY 10 | end 11 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | /** 4 | * Simple JavaBean domain object with an id property. 5 | * Used as a base class for objects needing this property. 6 | * 7 | * @author Ken Krebs 8 | * @author Juergen Hoeller 9 | */ 10 | public class BaseEntity { 11 | 12 | private Integer id; 13 | 14 | 15 | public void setId(Integer id) { 16 | this.id = id; 17 | } 18 | 19 | public Integer getId() { 20 | return id; 21 | } 22 | 23 | public boolean isNew() { 24 | return (this.id == null); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Clinic.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.dao.DataAccessException; 6 | 7 | /** 8 | * The high-level PetClinic business interface. 9 | * 10 | *

This is basically a data access object. 11 | * PetClinic doesn't have a dedicated business facade. 12 | * 13 | * @author Ken Krebs 14 | * @author Juergen Hoeller 15 | * @author Sam Brannen 16 | */ 17 | public interface Clinic { 18 | 19 | /** 20 | * Retrieve all Vets from the data store. 21 | * @return a Collection of Vets 22 | */ 23 | Collection getVets() throws DataAccessException; 24 | 25 | /** 26 | * Retrieve all PetTypes from the data store. 27 | * @return a Collection of PetTypes 28 | */ 29 | Collection getPetTypes() throws DataAccessException; 30 | 31 | /** 32 | * Retrieve Owners from the data store by last name, 33 | * returning all owners whose last name starts with the given name. 34 | * @param lastName Value to search for 35 | * @return a Collection of matching Owners 36 | * (or an empty Collection if none found) 37 | */ 38 | Collection findOwners(String lastName) throws DataAccessException; 39 | 40 | /** 41 | * Retrieve an Owner from the data store by id. 42 | * @param id the id to search for 43 | * @return the Owner if found 44 | * @throws org.springframework.dao.DataRetrievalFailureException if not found 45 | */ 46 | Owner loadOwner(int id) throws DataAccessException; 47 | 48 | /** 49 | * Retrieve a Pet from the data store by id. 50 | * @param id the id to search for 51 | * @return the Pet if found 52 | * @throws org.springframework.dao.DataRetrievalFailureException if not found 53 | */ 54 | Pet loadPet(int id) throws DataAccessException; 55 | 56 | /** 57 | * Save an Owner to the data store, either inserting or updating it. 58 | * @param owner the Owner to save 59 | * @see BaseEntity#isNew 60 | */ 61 | void storeOwner(Owner owner) throws DataAccessException; 62 | 63 | /** 64 | * Save a Pet to the data store, either inserting or updating it. 65 | * @param pet the Pet to save 66 | * @see BaseEntity#isNew 67 | */ 68 | void storePet(Pet pet) throws DataAccessException; 69 | 70 | /** 71 | * Save a Visit to the data store, either inserting or updating it. 72 | * @param visit the Visit to save 73 | * @see BaseEntity#isNew 74 | */ 75 | void storeVisit(Visit visit) throws DataAccessException; 76 | 77 | /** 78 | * Deletes a Pet from the data store. 79 | */ 80 | void deletePet(int id) throws DataAccessException; 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/NamedEntity.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | /** 4 | * Simple JavaBean domain object adds a name property to BaseEntity. 5 | * Used as a base class for objects needing these properties. 6 | * 7 | * @author Ken Krebs 8 | * @author Juergen Hoeller 9 | */ 10 | public class NamedEntity extends BaseEntity { 11 | 12 | private String name; 13 | 14 | 15 | public void setName(String name) { 16 | this.name = name; 17 | } 18 | 19 | public String getName() { 20 | return this.name; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return this.getName(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Owner.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | import org.springframework.beans.support.MutableSortDefinition; 10 | import org.springframework.beans.support.PropertyComparator; 11 | import org.springframework.core.style.ToStringCreator; 12 | 13 | /** 14 | * Simple JavaBean domain object representing an owner. 15 | * 16 | * @author Ken Krebs 17 | * @author Juergen Hoeller 18 | * @author Sam Brannen 19 | */ 20 | public class Owner extends Person { 21 | 22 | private String address; 23 | 24 | private String city; 25 | 26 | private String telephone; 27 | 28 | private Set pets; 29 | 30 | 31 | public String getAddress() { 32 | return this.address; 33 | } 34 | 35 | public void setAddress(String address) { 36 | this.address = address; 37 | } 38 | 39 | public String getCity() { 40 | return this.city; 41 | } 42 | 43 | public void setCity(String city) { 44 | this.city = city; 45 | } 46 | 47 | public String getTelephone() { 48 | return this.telephone; 49 | } 50 | 51 | public void setTelephone(String telephone) { 52 | this.telephone = telephone; 53 | } 54 | 55 | protected void setPetsInternal(Set pets) { 56 | this.pets = pets; 57 | } 58 | 59 | protected Set getPetsInternal() { 60 | if (this.pets == null) { 61 | this.pets = new HashSet(); 62 | } 63 | return this.pets; 64 | } 65 | 66 | public List getPets() { 67 | List sortedPets = new ArrayList(getPetsInternal()); 68 | PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true)); 69 | return Collections.unmodifiableList(sortedPets); 70 | } 71 | 72 | public void addPet(Pet pet) { 73 | getPetsInternal().add(pet); 74 | pet.setOwner(this); 75 | } 76 | 77 | /** 78 | * Return the Pet with the given name, or null if none found for this Owner. 79 | * 80 | * @param name to test 81 | * @return true if pet name is already in use 82 | */ 83 | public Pet getPet(String name) { 84 | return getPet(name, false); 85 | } 86 | 87 | /** 88 | * Return the Pet with the given name, or null if none found for this Owner. 89 | * 90 | * @param name to test 91 | * @return true if pet name is already in use 92 | */ 93 | public Pet getPet(String name, boolean ignoreNew) { 94 | name = name.toLowerCase(); 95 | for (Pet pet : getPetsInternal()) { 96 | if (!ignoreNew || !pet.isNew()) { 97 | String compName = pet.getName(); 98 | compName = compName.toLowerCase(); 99 | if (compName.equals(name)) { 100 | return pet; 101 | } 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return new ToStringCreator(this) 110 | 111 | .append("id", this.getId()) 112 | 113 | .append("new", this.isNew()) 114 | 115 | .append("lastName", this.getLastName()) 116 | 117 | .append("firstName", this.getFirstName()) 118 | 119 | .append("address", this.address) 120 | 121 | .append("city", this.city) 122 | 123 | .append("telephone", this.telephone) 124 | 125 | .toString(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Person.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | /** 4 | * Simple JavaBean domain object representing an person. 5 | * 6 | * @author Ken Krebs 7 | */ 8 | public class Person extends BaseEntity { 9 | 10 | private String firstName; 11 | 12 | private String lastName; 13 | 14 | public String getFirstName() { 15 | return this.firstName; 16 | } 17 | 18 | public void setFirstName(String firstName) { 19 | this.firstName = firstName; 20 | } 21 | 22 | public String getLastName() { 23 | return this.lastName; 24 | } 25 | 26 | public void setLastName(String lastName) { 27 | this.lastName = lastName; 28 | } 29 | 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Pet.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Date; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | import org.springframework.beans.support.MutableSortDefinition; 11 | import org.springframework.beans.support.PropertyComparator; 12 | 13 | /** 14 | * Simple JavaBean business object representing a pet. 15 | * 16 | * @author Ken Krebs 17 | * @author Juergen Hoeller 18 | * @author Sam Brannen 19 | */ 20 | public class Pet extends NamedEntity { 21 | 22 | private Date birthDate; 23 | 24 | private PetType type; 25 | 26 | private Owner owner; 27 | 28 | private Set visits; 29 | 30 | 31 | public void setBirthDate(Date birthDate) { 32 | this.birthDate = birthDate; 33 | } 34 | 35 | public Date getBirthDate() { 36 | return this.birthDate; 37 | } 38 | 39 | public void setType(PetType type) { 40 | this.type = type; 41 | } 42 | 43 | public PetType getType() { 44 | return this.type; 45 | } 46 | 47 | protected void setOwner(Owner owner) { 48 | this.owner = owner; 49 | } 50 | 51 | public Owner getOwner() { 52 | return this.owner; 53 | } 54 | 55 | protected void setVisitsInternal(Set visits) { 56 | this.visits = visits; 57 | } 58 | 59 | protected Set getVisitsInternal() { 60 | if (this.visits == null) { 61 | this.visits = new HashSet(); 62 | } 63 | return this.visits; 64 | } 65 | 66 | public List getVisits() { 67 | List sortedVisits = new ArrayList(getVisitsInternal()); 68 | PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false)); 69 | return Collections.unmodifiableList(sortedVisits); 70 | } 71 | 72 | public void addVisit(Visit visit) { 73 | getVisitsInternal().add(visit); 74 | visit.setPet(this); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/PetType.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | /** 4 | * @author Juergen Hoeller 5 | */ 6 | public class PetType extends NamedEntity { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Specialty.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | /** 4 | * Models a {@link Vet Vet's} specialty (for example, dentistry). 5 | * 6 | * @author Juergen Hoeller 7 | */ 8 | public class Specialty extends NamedEntity { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Vet.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | import javax.xml.bind.annotation.XmlElement; 9 | 10 | import org.springframework.beans.support.MutableSortDefinition; 11 | import org.springframework.beans.support.PropertyComparator; 12 | 13 | /** 14 | * Simple JavaBean domain object representing a veterinarian. 15 | * 16 | * @author Ken Krebs 17 | * @author Juergen Hoeller 18 | * @author Sam Brannen 19 | * @author Arjen Poutsma 20 | */ 21 | public class Vet extends Person { 22 | 23 | private Set specialties; 24 | 25 | 26 | protected void setSpecialtiesInternal(Set specialties) { 27 | this.specialties = specialties; 28 | } 29 | 30 | protected Set getSpecialtiesInternal() { 31 | if (this.specialties == null) { 32 | this.specialties = new HashSet(); 33 | } 34 | return this.specialties; 35 | } 36 | 37 | @XmlElement 38 | public List getSpecialties() { 39 | List sortedSpecs = new ArrayList(getSpecialtiesInternal()); 40 | PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true)); 41 | return Collections.unmodifiableList(sortedSpecs); 42 | } 43 | 44 | public int getNrOfSpecialties() { 45 | return getSpecialtiesInternal().size(); 46 | } 47 | 48 | public void addSpecialty(Specialty specialty) { 49 | getSpecialtiesInternal().add(specialty); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Vets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2009 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import javax.xml.bind.annotation.XmlElement; 22 | import javax.xml.bind.annotation.XmlRootElement; 23 | 24 | /** 25 | * Simple JavaBean domain object representing a list of veterinarians. Mostly here to be used for the 'vets' 26 | * {@link org.springframework.web.servlet.view.xml.MarshallingView}. 27 | * 28 | * @author Arjen Poutsma 29 | */ 30 | @XmlRootElement 31 | public class Vets { 32 | 33 | private List vets; 34 | 35 | @XmlElement 36 | public List getVetList() { 37 | if (vets == null) { 38 | vets = new ArrayList(); 39 | } 40 | return vets; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/Visit.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Simple JavaBean domain object representing a visit. 7 | * 8 | * @author Ken Krebs 9 | */ 10 | public class Visit extends BaseEntity { 11 | 12 | /** Holds value of property date. */ 13 | private Date date; 14 | 15 | /** Holds value of property description. */ 16 | private String description; 17 | 18 | /** Holds value of property pet. */ 19 | private Pet pet; 20 | 21 | 22 | /** Creates a new instance of Visit for the current date */ 23 | public Visit() { 24 | this.date = new Date(); 25 | } 26 | 27 | 28 | /** Getter for property date. 29 | * @return Value of property date. 30 | */ 31 | public Date getDate() { 32 | return this.date; 33 | } 34 | 35 | /** Setter for property date. 36 | * @param date New value of property date. 37 | */ 38 | public void setDate(Date date) { 39 | this.date = date; 40 | } 41 | 42 | /** Getter for property description. 43 | * @return Value of property description. 44 | */ 45 | public String getDescription() { 46 | return this.description; 47 | } 48 | 49 | /** Setter for property description. 50 | * @param description New value of property description. 51 | */ 52 | public void setDescription(String description) { 53 | this.description = description; 54 | } 55 | 56 | /** Getter for property pet. 57 | * @return Value of property pet. 58 | */ 59 | public Pet getPet() { 60 | return this.pet; 61 | } 62 | 63 | /** Setter for property pet. 64 | * @param pet New value of property pet. 65 | */ 66 | public void setPet(Pet pet) { 67 | this.pet = pet; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/aspects/AbstractTraceAspect.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.aspects; 2 | 3 | import org.aspectj.lang.JoinPoint; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.aspectj.lang.annotation.Before; 6 | import org.aspectj.lang.annotation.Pointcut; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * Aspect to illustrate Spring-driven load-time weaving. 12 | * 13 | * @author Ramnivas Laddad 14 | * @since 2.5 15 | */ 16 | @Aspect 17 | public abstract class AbstractTraceAspect { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(AbstractTraceAspect.class); 20 | 21 | @Pointcut 22 | public abstract void traced(); 23 | 24 | @Before("traced()") 25 | public void trace(JoinPoint.StaticPart jpsp) { 26 | if (logger.isTraceEnabled()) { 27 | logger.trace("Entering " + jpsp.getSignature().toLongString()); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/aspects/CallMonitoringAspect.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.aspects; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.Around; 5 | import org.aspectj.lang.annotation.Aspect; 6 | 7 | import org.springframework.jmx.export.annotation.ManagedAttribute; 8 | import org.springframework.jmx.export.annotation.ManagedOperation; 9 | import org.springframework.jmx.export.annotation.ManagedResource; 10 | import org.springframework.util.StopWatch; 11 | 12 | /** 13 | * Simple AspectJ aspect that monitors call count and call invocation time. 14 | * Implements the CallMonitor management interface. 15 | * 16 | * @author Rob Harrop 17 | * @author Juergen Hoeller 18 | * @since 2.5 19 | */ 20 | @ManagedResource("petclinic:type=CallMonitor") 21 | @Aspect 22 | public class CallMonitoringAspect { 23 | 24 | private boolean isEnabled = true; 25 | 26 | private int callCount = 0; 27 | 28 | private long accumulatedCallTime = 0; 29 | 30 | 31 | @ManagedAttribute 32 | public void setEnabled(boolean enabled) { 33 | isEnabled = enabled; 34 | } 35 | 36 | @ManagedAttribute 37 | public boolean isEnabled() { 38 | return isEnabled; 39 | } 40 | 41 | @ManagedOperation 42 | public void reset() { 43 | this.callCount = 0; 44 | this.accumulatedCallTime = 0; 45 | } 46 | 47 | @ManagedAttribute 48 | public int getCallCount() { 49 | return callCount; 50 | } 51 | 52 | @ManagedAttribute 53 | public long getCallTime() { 54 | return (this.callCount > 0 ? this.accumulatedCallTime / this.callCount : 0); 55 | } 56 | 57 | 58 | @Around("within(@org.springframework.stereotype.Service *)") 59 | public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable { 60 | if (this.isEnabled) { 61 | StopWatch sw = new StopWatch(joinPoint.toShortString()); 62 | 63 | sw.start("invoke"); 64 | try { 65 | return joinPoint.proceed(); 66 | } 67 | finally { 68 | sw.stop(); 69 | synchronized (this) { 70 | this.callCount++; 71 | this.accumulatedCallTime += sw.getTotalTimeMillis(); 72 | } 73 | } 74 | } 75 | 76 | else { 77 | return joinPoint.proceed(); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/aspects/UsageLogAspect.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.aspects; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.annotation.Before; 9 | 10 | /** 11 | * Sample AspectJ annotation-style aspect that saves 12 | * every owner name requested to the clinic. 13 | * 14 | * @author Rod Johnson 15 | * @author Juergen Hoeller 16 | * @since 2.0 17 | */ 18 | @Aspect 19 | public class UsageLogAspect { 20 | 21 | private int historySize = 100; 22 | 23 | // Of course saving all names is not suitable for 24 | // production use, but this is a simple example. 25 | private List namesRequested = new ArrayList(this.historySize); 26 | 27 | 28 | public synchronized void setHistorySize(int historySize) { 29 | this.historySize = historySize; 30 | this.namesRequested = new ArrayList(historySize); 31 | } 32 | 33 | @Before("execution(* *.findOwners(String)) && args(name)") 34 | public synchronized void logNameRequest(String name) { 35 | // Not the most efficient implementation, 36 | // but we're aiming to illustrate the power of 37 | // @AspectJ AOP, not write perfect code here :-) 38 | if (this.namesRequested.size() > this.historySize) { 39 | this.namesRequested.remove(0); 40 | } 41 | this.namesRequested.add(name); 42 | } 43 | 44 | public synchronized List getNamesRequested() { 45 | return Collections.unmodifiableList(this.namesRequested); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/hibernate/HibernateClinic.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.hibernate; 2 | 3 | import java.util.Collection; 4 | 5 | import org.hibernate.SessionFactory; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.dao.DataAccessException; 9 | import org.springframework.samples.petclinic.Clinic; 10 | import org.springframework.samples.petclinic.Owner; 11 | import org.springframework.samples.petclinic.Pet; 12 | import org.springframework.samples.petclinic.PetType; 13 | import org.springframework.samples.petclinic.Vet; 14 | import org.springframework.samples.petclinic.Visit; 15 | import org.springframework.stereotype.Repository; 16 | import org.springframework.transaction.annotation.Transactional; 17 | 18 | /** 19 | * Hibernate implementation of the Clinic interface. 20 | * 21 | *

The mappings are defined in "petclinic.hbm.xml", located in the root of the 22 | * class path. 23 | * 24 | *

Note that transactions are declared with annotations and that some methods 25 | * contain "readOnly = true" which is an optimization that is particularly 26 | * valuable when using Hibernate (to suppress unnecessary flush attempts for 27 | * read-only operations). 28 | * 29 | * @author Juergen Hoeller 30 | * @author Sam Brannen 31 | * @author Mark Fisher 32 | * @since 19.10.2003 33 | */ 34 | @Repository 35 | @Transactional 36 | public class HibernateClinic implements Clinic { 37 | 38 | private SessionFactory sessionFactory; 39 | 40 | @Autowired 41 | public HibernateClinic(SessionFactory sessionFactory) { 42 | this.sessionFactory = sessionFactory; 43 | } 44 | 45 | @Transactional(readOnly = true) 46 | @SuppressWarnings("unchecked") 47 | public Collection getVets() { 48 | return sessionFactory.getCurrentSession().createQuery("from Vet vet order by vet.lastName, vet.firstName").list(); 49 | } 50 | 51 | @Transactional(readOnly = true) 52 | @SuppressWarnings("unchecked") 53 | public Collection getPetTypes() { 54 | return sessionFactory.getCurrentSession().createQuery("from PetType type order by type.name").list(); 55 | } 56 | 57 | @Transactional(readOnly = true) 58 | @SuppressWarnings("unchecked") 59 | public Collection findOwners(String lastName) { 60 | return sessionFactory.getCurrentSession().createQuery("from Owner owner where owner.lastName like :lastName") 61 | .setString("lastName", lastName + "%").list(); 62 | } 63 | 64 | @Transactional(readOnly = true) 65 | public Owner loadOwner(int id) { 66 | return (Owner) sessionFactory.getCurrentSession().load(Owner.class, id); 67 | } 68 | 69 | @Transactional(readOnly = true) 70 | public Pet loadPet(int id) { 71 | return (Pet) sessionFactory.getCurrentSession().load(Pet.class, id); 72 | } 73 | 74 | public void storeOwner(Owner owner) { 75 | // Note: Hibernate3's merge operation does not reassociate the object 76 | // with the current Hibernate Session. Instead, it will always copy the 77 | // state over to a registered representation of the entity. In case of a 78 | // new entity, it will register a copy as well, but will not update the 79 | // id of the passed-in object. To still update the ids of the original 80 | // objects too, we need to register Spring's 81 | // IdTransferringMergeEventListener on our SessionFactory. 82 | sessionFactory.getCurrentSession().merge(owner); 83 | } 84 | 85 | public void storePet(Pet pet) { 86 | sessionFactory.getCurrentSession().merge(pet); 87 | } 88 | 89 | public void storeVisit(Visit visit) { 90 | sessionFactory.getCurrentSession().merge(visit); 91 | } 92 | 93 | public void deletePet(int id) throws DataAccessException { 94 | Pet pet = loadPet(id); 95 | sessionFactory.getCurrentSession().delete(pet); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/hibernate/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package represent the Hibernate implementation 5 | * of PetClinic's persistence layer. 6 | * 7 | */ 8 | package org.springframework.samples.petclinic.hibernate; 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/jdbc/JdbcPet.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.jdbc; 2 | 3 | import org.springframework.samples.petclinic.Pet; 4 | 5 | /** 6 | * Subclass of Pet that carries temporary id properties which 7 | * are only relevant for a JDBC implmentation of the Clinic. 8 | * 9 | * @author Juergen Hoeller 10 | * @see SimpleJdbcClinic 11 | */ 12 | class JdbcPet extends Pet { 13 | 14 | private int typeId; 15 | 16 | private int ownerId; 17 | 18 | 19 | public void setTypeId(int typeId) { 20 | this.typeId = typeId; 21 | } 22 | 23 | public int getTypeId() { 24 | return this.typeId; 25 | } 26 | 27 | public void setOwnerId(int ownerId) { 28 | this.ownerId = ownerId; 29 | } 30 | 31 | public int getOwnerId() { 32 | return this.ownerId; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicMBean.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.jdbc; 2 | 3 | /** 4 | * Interface that defines a cache refresh operation. 5 | * To be exposed for management via JMX. 6 | * 7 | * @author Rob Harrop 8 | * @author Juergen Hoeller 9 | * @see SimpleJdbcClinic 10 | */ 11 | public interface SimpleJdbcClinicMBean { 12 | 13 | /** 14 | * Refresh the cache of Vets that the Clinic is holding. 15 | * @see org.springframework.samples.petclinic.Clinic#getVets() 16 | * @see SimpleJdbcClinic#refreshVetsCache() 17 | */ 18 | void refreshVetsCache(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/jdbc/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package represent the JDBC implementation 5 | * of PetClinic's persistence layer. 6 | * 7 | */ 8 | package org.springframework.samples.petclinic.jdbc; 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/jpa/EntityManagerClinic.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.jpa; 2 | 3 | import java.util.Collection; 4 | 5 | import javax.persistence.EntityManager; 6 | import javax.persistence.PersistenceContext; 7 | import javax.persistence.Query; 8 | 9 | import org.springframework.samples.petclinic.Clinic; 10 | import org.springframework.samples.petclinic.Owner; 11 | import org.springframework.samples.petclinic.Pet; 12 | import org.springframework.samples.petclinic.PetType; 13 | import org.springframework.samples.petclinic.Vet; 14 | import org.springframework.samples.petclinic.Visit; 15 | import org.springframework.stereotype.Repository; 16 | import org.springframework.transaction.annotation.Transactional; 17 | import org.springframework.dao.DataAccessException; 18 | 19 | /** 20 | * JPA implementation of the Clinic interface using EntityManager. 21 | * 22 | *

The mappings are defined in "orm.xml" located in the META-INF directory. 23 | * 24 | * @author Mike Keith 25 | * @author Rod Johnson 26 | * @author Sam Brannen 27 | * @since 22.4.2006 28 | */ 29 | @Repository 30 | @Transactional 31 | public class EntityManagerClinic implements Clinic { 32 | 33 | @PersistenceContext 34 | private EntityManager em; 35 | 36 | 37 | @Transactional(readOnly = true) 38 | @SuppressWarnings("unchecked") 39 | public Collection getVets() { 40 | return this.em.createQuery("SELECT vet FROM Vet vet ORDER BY vet.lastName, vet.firstName").getResultList(); 41 | } 42 | 43 | @Transactional(readOnly = true) 44 | @SuppressWarnings("unchecked") 45 | public Collection getPetTypes() { 46 | return this.em.createQuery("SELECT ptype FROM PetType ptype ORDER BY ptype.name").getResultList(); 47 | } 48 | 49 | @Transactional(readOnly = true) 50 | @SuppressWarnings("unchecked") 51 | public Collection findOwners(String lastName) { 52 | Query query = this.em.createQuery("SELECT owner FROM Owner owner WHERE owner.lastName LIKE :lastName"); 53 | query.setParameter("lastName", lastName + "%"); 54 | return query.getResultList(); 55 | } 56 | 57 | @Transactional(readOnly = true) 58 | public Owner loadOwner(int id) { 59 | return this.em.find(Owner.class, id); 60 | } 61 | 62 | @Transactional(readOnly = true) 63 | public Pet loadPet(int id) { 64 | return this.em.find(Pet.class, id); 65 | } 66 | 67 | public void storeOwner(Owner owner) { 68 | // Consider returning the persistent object here, for exposing 69 | // a newly assigned id using any persistence provider... 70 | Owner merged = this.em.merge(owner); 71 | this.em.flush(); 72 | owner.setId(merged.getId()); 73 | } 74 | 75 | public void storePet(Pet pet) { 76 | // Consider returning the persistent object here, for exposing 77 | // a newly assigned id using any persistence provider... 78 | Pet merged = this.em.merge(pet); 79 | this.em.flush(); 80 | pet.setId(merged.getId()); 81 | } 82 | 83 | public void storeVisit(Visit visit) { 84 | // Consider returning the persistent object here, for exposing 85 | // a newly assigned id using any persistence provider... 86 | Visit merged = this.em.merge(visit); 87 | this.em.flush(); 88 | visit.setId(merged.getId()); 89 | } 90 | 91 | public void deletePet(int id) throws DataAccessException { 92 | Pet pet = loadPet(id); 93 | this.em.remove(pet); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/jpa/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package represent the JPA implementation 5 | * of PetClinic's persistence layer. 6 | * 7 | */ 8 | package org.springframework.samples.petclinic.jpa; 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package represent PetClinic's business layer. 5 | * 6 | */ 7 | package org.springframework.samples.petclinic; 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/toplink/EssentialsHSQLPlatformWithNativeSequence.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.toplink; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | import oracle.toplink.essentials.exceptions.ValidationException; 7 | import oracle.toplink.essentials.platform.database.HSQLPlatform; 8 | import oracle.toplink.essentials.queryframework.ValueReadQuery; 9 | 10 | /** 11 | * Subclass of the TopLink Essentials default HSQLPlatform class, using native 12 | * HSQLDB identity columns for id generation. 13 | * 14 | *

Necessary for PetClinic's default data model, which relies on identity 15 | * columns: this is uniformly used across all persistence layer implementations 16 | * (JDBC, Hibernate, and JPA). 17 | * 18 | * @author Juergen Hoeller 19 | * @author James Clark 20 | * @since 1.2 21 | */ 22 | public class EssentialsHSQLPlatformWithNativeSequence extends HSQLPlatform { 23 | 24 | private static final long serialVersionUID = -55658009691346735L; 25 | 26 | 27 | public EssentialsHSQLPlatformWithNativeSequence() { 28 | // setUsesNativeSequencing(true); 29 | } 30 | 31 | @Override 32 | public boolean supportsNativeSequenceNumbers() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public boolean shouldNativeSequenceAcquireValueAfterInsert() { 38 | return true; 39 | } 40 | 41 | @Override 42 | public ValueReadQuery buildSelectQueryForNativeSequence() { 43 | return new ValueReadQuery("CALL IDENTITY()"); 44 | } 45 | 46 | @Override 47 | public void printFieldIdentityClause(Writer writer) throws ValidationException { 48 | try { 49 | writer.write(" IDENTITY"); 50 | } 51 | catch (IOException ex) { 52 | throw ValidationException.fileError(ex); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/toplink/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package provide support for using the TopLink 5 | * implementation with PetClinic's EntityManagerClinic. 6 | * 7 | * 8 | */ 9 | package org.springframework.samples.petclinic.toplink; 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.util; 3 | 4 | import java.util.Collection; 5 | 6 | import org.springframework.orm.ObjectRetrievalFailureException; 7 | import org.springframework.samples.petclinic.BaseEntity; 8 | 9 | /** 10 | * Utility methods for handling entities. Separate from the BaseEntity class 11 | * mainly because of dependency on the ORM-associated 12 | * ObjectRetrievalFailureException. 13 | * 14 | * @author Juergen Hoeller 15 | * @author Sam Brannen 16 | * @since 29.10.2003 17 | * @see org.springframework.samples.petclinic.BaseEntity 18 | */ 19 | public abstract class EntityUtils { 20 | 21 | /** 22 | * Look up the entity of the given class with the given id in the given 23 | * collection. 24 | * 25 | * @param entities the collection to search 26 | * @param entityClass the entity class to look up 27 | * @param entityId the entity id to look up 28 | * @return the found entity 29 | * @throws ObjectRetrievalFailureException if the entity was not found 30 | */ 31 | public static T getById(Collection entities, Class entityClass, int entityId) 32 | throws ObjectRetrievalFailureException { 33 | for (T entity : entities) { 34 | if (entity.getId().intValue() == entityId && entityClass.isInstance(entity)) { 35 | return entity; 36 | } 37 | } 38 | throw new ObjectRetrievalFailureException(entityClass, new Integer(entityId)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/validation/OwnerValidator.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.validation; 2 | 3 | import org.springframework.samples.petclinic.Owner; 4 | import org.springframework.util.StringUtils; 5 | import org.springframework.validation.Errors; 6 | 7 | /** 8 | * Validator for Owner forms. 9 | * 10 | * @author Ken Krebs 11 | * @author Juergen Hoeller 12 | */ 13 | public class OwnerValidator { 14 | 15 | public void validate(Owner owner, Errors errors) { 16 | if (!StringUtils.hasLength(owner.getFirstName())) { 17 | errors.rejectValue("firstName", "required", "required"); 18 | } 19 | if (!StringUtils.hasLength(owner.getLastName())) { 20 | errors.rejectValue("lastName", "required", "required"); 21 | } 22 | if (!StringUtils.hasLength(owner.getAddress())) { 23 | errors.rejectValue("address", "required", "required"); 24 | } 25 | if (!StringUtils.hasLength(owner.getCity())) { 26 | errors.rejectValue("city", "required", "required"); 27 | } 28 | 29 | String telephone = owner.getTelephone(); 30 | if (!StringUtils.hasLength(telephone)) { 31 | errors.rejectValue("telephone", "required", "required"); 32 | } 33 | else { 34 | for (int i = 0; i < telephone.length(); ++i) { 35 | if ((Character.isDigit(telephone.charAt(i))) == false) { 36 | errors.rejectValue("telephone", "nonNumeric", "non-numeric"); 37 | break; 38 | } 39 | } 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/validation/PetValidator.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.validation; 2 | 3 | import org.springframework.samples.petclinic.Pet; 4 | import org.springframework.util.StringUtils; 5 | import org.springframework.validation.Errors; 6 | 7 | /** 8 | * Validator for Pet forms. 9 | * 10 | * @author Ken Krebs 11 | * @author Juergen Hoeller 12 | */ 13 | public class PetValidator { 14 | 15 | public void validate(Pet pet, Errors errors) { 16 | String name = pet.getName(); 17 | if (!StringUtils.hasLength(name)) { 18 | errors.rejectValue("name", "required", "required"); 19 | } 20 | else if (pet.isNew() && pet.getOwner().getPet(name, true) != null) { 21 | errors.rejectValue("name", "duplicate", "already exists"); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/validation/VisitValidator.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.validation; 2 | 3 | import org.springframework.samples.petclinic.Visit; 4 | import org.springframework.util.StringUtils; 5 | import org.springframework.validation.Errors; 6 | 7 | /** 8 | * Validator for Visit forms. 9 | * 10 | * @author Ken Krebs 11 | * @author Juergen Hoeller 12 | */ 13 | public class VisitValidator { 14 | 15 | public void validate(Visit visit, Errors errors) { 16 | if (!StringUtils.hasLength(visit.getDescription())) { 17 | errors.rejectValue("description", "required", "required"); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/validation/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package represent the set of Validator objects 5 | * the Business Layer makes available to the Presentation Layer. 6 | * 7 | */ 8 | package org.springframework.samples.petclinic.validation; 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/AddOwnerForm.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.samples.petclinic.Clinic; 6 | import org.springframework.samples.petclinic.Owner; 7 | import org.springframework.samples.petclinic.validation.OwnerValidator; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.WebDataBinder; 12 | import org.springframework.web.bind.annotation.InitBinder; 13 | import org.springframework.web.bind.annotation.ModelAttribute; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.SessionAttributes; 17 | import org.springframework.web.bind.support.SessionStatus; 18 | 19 | /** 20 | * JavaBean form controller that is used to add a new Owner to the 21 | * system. 22 | * 23 | * @author Juergen Hoeller 24 | * @author Ken Krebs 25 | * @author Arjen Poutsma 26 | */ 27 | @Controller 28 | @RequestMapping("/owners/new") 29 | @SessionAttributes(types = Owner.class) 30 | public class AddOwnerForm { 31 | 32 | private final Clinic clinic; 33 | 34 | 35 | @Autowired 36 | public AddOwnerForm(Clinic clinic) { 37 | this.clinic = clinic; 38 | } 39 | 40 | @InitBinder 41 | public void setAllowedFields(WebDataBinder dataBinder) { 42 | dataBinder.setDisallowedFields("id"); 43 | } 44 | 45 | @RequestMapping(method = RequestMethod.GET) 46 | public String setupForm(Model model) { 47 | Owner owner = new Owner(); 48 | model.addAttribute(owner); 49 | return "owners/form"; 50 | } 51 | 52 | @RequestMapping(method = RequestMethod.POST) 53 | public String processSubmit(@ModelAttribute Owner owner, BindingResult result, SessionStatus status) { 54 | new OwnerValidator().validate(owner, result); 55 | if (result.hasErrors()) { 56 | return "owners/form"; 57 | } 58 | else { 59 | this.clinic.storeOwner(owner); 60 | status.setComplete(); 61 | return "redirect:/owners/" + owner.getId(); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/AddPetForm.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import java.util.Collection; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.samples.petclinic.Clinic; 8 | import org.springframework.samples.petclinic.Owner; 9 | import org.springframework.samples.petclinic.Pet; 10 | import org.springframework.samples.petclinic.PetType; 11 | import org.springframework.samples.petclinic.validation.PetValidator; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.ui.Model; 14 | import org.springframework.validation.BindingResult; 15 | import org.springframework.web.bind.WebDataBinder; 16 | import org.springframework.web.bind.annotation.InitBinder; 17 | import org.springframework.web.bind.annotation.ModelAttribute; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestMethod; 21 | import org.springframework.web.bind.annotation.SessionAttributes; 22 | import org.springframework.web.bind.support.SessionStatus; 23 | 24 | /** 25 | * JavaBean form controller that is used to add a new Pet to the 26 | * system. 27 | * 28 | * @author Juergen Hoeller 29 | * @author Ken Krebs 30 | * @author Arjen Poutsma 31 | */ 32 | @Controller 33 | @RequestMapping("/owners/{ownerId}/pets/new") 34 | @SessionAttributes("pet") 35 | public class AddPetForm { 36 | 37 | private final Clinic clinic; 38 | 39 | 40 | @Autowired 41 | public AddPetForm(Clinic clinic) { 42 | this.clinic = clinic; 43 | } 44 | 45 | @ModelAttribute("types") 46 | public Collection populatePetTypes() { 47 | return this.clinic.getPetTypes(); 48 | } 49 | 50 | @InitBinder 51 | public void setAllowedFields(WebDataBinder dataBinder) { 52 | dataBinder.setDisallowedFields("id"); 53 | } 54 | 55 | @RequestMapping(method = RequestMethod.GET) 56 | public String setupForm(@PathVariable("ownerId") int ownerId, Model model) { 57 | Owner owner = this.clinic.loadOwner(ownerId); 58 | Pet pet = new Pet(); 59 | owner.addPet(pet); 60 | model.addAttribute("pet", pet); 61 | return "pets/form"; 62 | } 63 | 64 | @RequestMapping(method = RequestMethod.POST) 65 | public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) { 66 | new PetValidator().validate(pet, result); 67 | if (result.hasErrors()) { 68 | return "pets/form"; 69 | } 70 | else { 71 | this.clinic.storePet(pet); 72 | status.setComplete(); 73 | return "redirect:/owners/" + pet.getOwner().getId(); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/AddVisitForm.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.samples.petclinic.Clinic; 6 | import org.springframework.samples.petclinic.Pet; 7 | import org.springframework.samples.petclinic.Visit; 8 | import org.springframework.samples.petclinic.validation.VisitValidator; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.validation.BindingResult; 12 | import org.springframework.web.bind.WebDataBinder; 13 | import org.springframework.web.bind.annotation.InitBinder; 14 | import org.springframework.web.bind.annotation.ModelAttribute; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.bind.annotation.SessionAttributes; 19 | import org.springframework.web.bind.support.SessionStatus; 20 | 21 | /** 22 | * JavaBean form controller that is used to add a new Visit to the 23 | * system. 24 | * 25 | * @author Juergen Hoeller 26 | * @author Ken Krebs 27 | * @author Arjen Poutsma 28 | */ 29 | @Controller 30 | @RequestMapping("/owners/*/pets/{petId}/visits/new") 31 | @SessionAttributes("visit") 32 | public class AddVisitForm { 33 | 34 | private final Clinic clinic; 35 | 36 | 37 | @Autowired 38 | public AddVisitForm(Clinic clinic) { 39 | this.clinic = clinic; 40 | } 41 | 42 | @InitBinder 43 | public void setAllowedFields(WebDataBinder dataBinder) { 44 | dataBinder.setDisallowedFields("id"); 45 | } 46 | 47 | @RequestMapping(method = RequestMethod.GET) 48 | public String setupForm(@PathVariable("petId") int petId, Model model) { 49 | Pet pet = this.clinic.loadPet(petId); 50 | Visit visit = new Visit(); 51 | pet.addVisit(visit); 52 | model.addAttribute("visit", visit); 53 | return "pets/visitForm"; 54 | } 55 | 56 | @RequestMapping(method = RequestMethod.POST) 57 | public String processSubmit(@ModelAttribute("visit") Visit visit, BindingResult result, SessionStatus status) { 58 | new VisitValidator().validate(visit, result); 59 | if (result.hasErrors()) { 60 | return "pets/visitForm"; 61 | } 62 | else { 63 | this.clinic.storeVisit(visit); 64 | status.setComplete(); 65 | return "redirect:/owners/" + visit.getPet().getOwner().getId(); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/ClinicBindingInitializer.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.web; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.propertyeditors.CustomDateEditor; 8 | import org.springframework.beans.propertyeditors.StringTrimmerEditor; 9 | import org.springframework.samples.petclinic.Clinic; 10 | import org.springframework.samples.petclinic.PetType; 11 | import org.springframework.web.bind.WebDataBinder; 12 | import org.springframework.web.bind.support.WebBindingInitializer; 13 | import org.springframework.web.context.request.WebRequest; 14 | 15 | /** 16 | * Shared WebBindingInitializer for PetClinic's custom editors. 17 | * 18 | *

Alternatively, such init-binder code may be put into 19 | * {@link org.springframework.web.bind.annotation.InitBinder} 20 | * annotated methods on the controller classes themselves. 21 | * 22 | * @author Juergen Hoeller 23 | */ 24 | public class ClinicBindingInitializer implements WebBindingInitializer { 25 | 26 | @Autowired 27 | private Clinic clinic; 28 | 29 | public void initBinder(WebDataBinder binder, WebRequest request) { 30 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 31 | dateFormat.setLenient(false); 32 | binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); 33 | binder.registerCustomEditor(String.class, new StringTrimmerEditor(false)); 34 | binder.registerCustomEditor(PetType.class, new PetTypeEditor(this.clinic)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/ClinicController.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.samples.petclinic.Clinic; 6 | import org.springframework.samples.petclinic.Vets; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.ModelMap; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.servlet.ModelAndView; 13 | 14 | /** 15 | * Annotation-driven MultiActionController that handles all non-form 16 | * URL's. 17 | * 18 | * @author Juergen Hoeller 19 | * @author Mark Fisher 20 | * @author Ken Krebs 21 | * @author Arjen Poutsma 22 | */ 23 | @Controller 24 | public class ClinicController { 25 | 26 | private final Clinic clinic; 27 | 28 | 29 | @Autowired 30 | public ClinicController(Clinic clinic) { 31 | this.clinic = clinic; 32 | } 33 | 34 | /** 35 | * Custom handler for the welcome view. 36 | *

37 | * Note that this handler relies on the RequestToViewNameTranslator to 38 | * determine the logical view name based on the request URL: "/welcome.do" 39 | * -> "welcome". 40 | */ 41 | @RequestMapping("/") 42 | public String welcomeHandler() { 43 | return "welcome"; 44 | } 45 | 46 | /** 47 | * Custom handler for displaying vets. 48 | * 49 | *

Note that this handler returns a plain {@link ModelMap} object instead of 50 | * a ModelAndView, thus leveraging convention-based model attribute names. 51 | * It relies on the RequestToViewNameTranslator to determine the logical 52 | * view name based on the request URL: "/vets.do" -> "vets". 53 | * 54 | * @return a ModelMap with the model attributes for the view 55 | */ 56 | @RequestMapping("/vets") 57 | public ModelMap vetsHandler() { 58 | Vets vets = new Vets(); 59 | vets.getVetList().addAll(this.clinic.getVets()); 60 | return new ModelMap(vets); 61 | } 62 | 63 | /** 64 | * Custom handler for displaying an owner. 65 | * 66 | * @param ownerId the ID of the owner to display 67 | * @return a ModelMap with the model attributes for the view 68 | */ 69 | @RequestMapping("/owners/{ownerId}") 70 | public ModelAndView ownerHandler(@PathVariable("ownerId") int ownerId) { 71 | ModelAndView mav = new ModelAndView("owners/show"); 72 | mav.addObject(this.clinic.loadOwner(ownerId)); 73 | return mav; 74 | } 75 | 76 | /** 77 | * Custom handler for displaying an list of visits. 78 | * 79 | * @param petId the ID of the pet whose visits to display 80 | * @return a ModelMap with the model attributes for the view 81 | */ 82 | @RequestMapping(value="/owners/*/pets/{petId}/visits", method=RequestMethod.GET) 83 | public ModelAndView visitsHandler(@PathVariable int petId) { 84 | ModelAndView mav = new ModelAndView("visits"); 85 | mav.addObject("visits", this.clinic.loadPet(petId).getVisits()); 86 | return mav; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/EditOwnerForm.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.samples.petclinic.Clinic; 6 | import org.springframework.samples.petclinic.Owner; 7 | import org.springframework.samples.petclinic.validation.OwnerValidator; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.WebDataBinder; 12 | import org.springframework.web.bind.annotation.InitBinder; 13 | import org.springframework.web.bind.annotation.ModelAttribute; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.SessionAttributes; 18 | import org.springframework.web.bind.support.SessionStatus; 19 | 20 | /** 21 | * JavaBean Form controller that is used to edit an existing Owner. 22 | * 23 | * @author Juergen Hoeller 24 | * @author Ken Krebs 25 | * @author Arjen Poutsma 26 | */ 27 | @Controller 28 | @RequestMapping("/owners/{ownerId}/edit") 29 | @SessionAttributes(types = Owner.class) 30 | public class EditOwnerForm { 31 | 32 | private final Clinic clinic; 33 | 34 | 35 | @Autowired 36 | public EditOwnerForm(Clinic clinic) { 37 | this.clinic = clinic; 38 | } 39 | 40 | @InitBinder 41 | public void setAllowedFields(WebDataBinder dataBinder) { 42 | dataBinder.setDisallowedFields("id"); 43 | } 44 | 45 | @RequestMapping(method = RequestMethod.GET) 46 | public String setupForm(@PathVariable("ownerId") int ownerId, Model model) { 47 | Owner owner = this.clinic.loadOwner(ownerId); 48 | model.addAttribute(owner); 49 | return "owners/form"; 50 | } 51 | 52 | @RequestMapping(method = RequestMethod.PUT) 53 | public String processSubmit(@ModelAttribute Owner owner, BindingResult result, SessionStatus status) { 54 | new OwnerValidator().validate(owner, result); 55 | if (result.hasErrors()) { 56 | return "owners/form"; 57 | } 58 | else { 59 | this.clinic.storeOwner(owner); 60 | status.setComplete(); 61 | return "redirect:/owners/" + owner.getId(); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/EditPetForm.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import java.util.Collection; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.samples.petclinic.Clinic; 8 | import org.springframework.samples.petclinic.Pet; 9 | import org.springframework.samples.petclinic.PetType; 10 | import org.springframework.samples.petclinic.validation.PetValidator; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.validation.BindingResult; 14 | import org.springframework.web.bind.WebDataBinder; 15 | import org.springframework.web.bind.annotation.InitBinder; 16 | import org.springframework.web.bind.annotation.ModelAttribute; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.SessionAttributes; 21 | import org.springframework.web.bind.support.SessionStatus; 22 | 23 | /** 24 | * JavaBean Form controller that is used to edit an existing Pet. 25 | * 26 | * @author Juergen Hoeller 27 | * @author Ken Krebs 28 | * @author Arjen Poutsma 29 | */ 30 | @Controller 31 | @RequestMapping("/owners/*/pets/{petId}/edit") 32 | @SessionAttributes("pet") 33 | public class EditPetForm { 34 | 35 | private final Clinic clinic; 36 | 37 | 38 | @Autowired 39 | public EditPetForm(Clinic clinic) { 40 | this.clinic = clinic; 41 | } 42 | 43 | @ModelAttribute("types") 44 | public Collection populatePetTypes() { 45 | return this.clinic.getPetTypes(); 46 | } 47 | 48 | @InitBinder 49 | public void setAllowedFields(WebDataBinder dataBinder) { 50 | dataBinder.setDisallowedFields("id"); 51 | } 52 | 53 | @RequestMapping(method = RequestMethod.GET) 54 | public String setupForm(@PathVariable("petId") int petId, Model model) { 55 | Pet pet = this.clinic.loadPet(petId); 56 | model.addAttribute("pet", pet); 57 | return "pets/form"; 58 | } 59 | 60 | @RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST }) 61 | public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) { 62 | new PetValidator().validate(pet, result); 63 | if (result.hasErrors()) { 64 | return "pets/form"; 65 | } 66 | else { 67 | this.clinic.storePet(pet); 68 | status.setComplete(); 69 | return "redirect:/owners/" + pet.getOwner().getId(); 70 | } 71 | } 72 | 73 | @RequestMapping(method = RequestMethod.DELETE) 74 | public String deletePet(@PathVariable int petId) { 75 | Pet pet = this.clinic.loadPet(petId); 76 | this.clinic.deletePet(petId); 77 | return "redirect:/owners/" + pet.getOwner().getId(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/FindOwnersForm.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.web; 3 | 4 | import java.util.Collection; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.samples.petclinic.Clinic; 8 | import org.springframework.samples.petclinic.Owner; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.validation.BindingResult; 12 | import org.springframework.web.bind.WebDataBinder; 13 | import org.springframework.web.bind.annotation.InitBinder; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | 17 | /** 18 | * JavaBean Form controller that is used to search for Owners by 19 | * last name. 20 | * 21 | * @author Juergen Hoeller 22 | * @author Ken Krebs 23 | * @author Arjen Poutsma 24 | */ 25 | @Controller 26 | public class FindOwnersForm { 27 | 28 | private final Clinic clinic; 29 | 30 | 31 | @Autowired 32 | public FindOwnersForm(Clinic clinic) { 33 | this.clinic = clinic; 34 | } 35 | 36 | @InitBinder 37 | public void setAllowedFields(WebDataBinder dataBinder) { 38 | dataBinder.setDisallowedFields("id"); 39 | } 40 | 41 | @RequestMapping(value = "/owners/search", method = RequestMethod.GET) 42 | public String setupForm(Model model) { 43 | model.addAttribute("owner", new Owner()); 44 | return "owners/search"; 45 | } 46 | 47 | @RequestMapping(value = "/owners", method = RequestMethod.GET) 48 | public String processSubmit(Owner owner, BindingResult result, Model model) { 49 | 50 | // allow parameterless GET request for /owners to return all records 51 | if (owner.getLastName() == null) { 52 | owner.setLastName(""); // empty string signifies broadest possible search 53 | } 54 | 55 | // find owners by last name 56 | Collection results = this.clinic.findOwners(owner.getLastName()); 57 | if (results.size() < 1) { 58 | // no owners found 59 | result.rejectValue("lastName", "notFound", "not found"); 60 | return "owners/search"; 61 | } 62 | if (results.size() > 1) { 63 | // multiple owners found 64 | model.addAttribute("selections", results); 65 | return "owners/list"; 66 | } 67 | else { 68 | // 1 owner found 69 | owner = results.iterator().next(); 70 | return "redirect:/owners/" + owner.getId(); 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/PetTypeEditor.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.web; 2 | 3 | import java.beans.PropertyEditorSupport; 4 | 5 | import org.springframework.samples.petclinic.Clinic; 6 | import org.springframework.samples.petclinic.PetType; 7 | 8 | /** 9 | * @author Mark Fisher 10 | * @author Juergen Hoeller 11 | */ 12 | public class PetTypeEditor extends PropertyEditorSupport { 13 | 14 | private final Clinic clinic; 15 | 16 | 17 | public PetTypeEditor(Clinic clinic) { 18 | this.clinic = clinic; 19 | } 20 | 21 | @Override 22 | public void setAsText(String text) throws IllegalArgumentException { 23 | for (PetType type : this.clinic.getPetTypes()) { 24 | if (type.getName().equals(text)) { 25 | setValue(type); 26 | } 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/VisitsAtomView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2009 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.web; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Date; 21 | import java.util.List; 22 | import java.util.Map; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | 26 | import com.sun.syndication.feed.atom.Content; 27 | import com.sun.syndication.feed.atom.Entry; 28 | import com.sun.syndication.feed.atom.Feed; 29 | 30 | import org.springframework.samples.petclinic.Visit; 31 | import org.springframework.web.servlet.view.feed.AbstractAtomFeedView; 32 | 33 | /** 34 | * A view creating a Atom representation from a list of Visit objects. 35 | * 36 | * @author Alef Arendsen 37 | * @author Arjen Poutsma 38 | */ 39 | public class VisitsAtomView extends AbstractAtomFeedView { 40 | 41 | @Override 42 | protected void buildFeedMetadata(Map model, Feed feed, HttpServletRequest request) { 43 | feed.setId("tag:springsource.com"); 44 | feed.setTitle("Pet Clinic Visits"); 45 | @SuppressWarnings("unchecked") 46 | List visits = (List) model.get("visits"); 47 | for (Visit visit : visits) { 48 | Date date = visit.getDate(); 49 | if (feed.getUpdated() == null || date.compareTo(feed.getUpdated()) > 0) { 50 | feed.setUpdated(date); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | protected List buildFeedEntries(Map model, 57 | HttpServletRequest request, HttpServletResponse response) throws Exception { 58 | 59 | @SuppressWarnings("unchecked") 60 | List visits = (List) model.get("visits"); 61 | List entries = new ArrayList(visits.size()); 62 | 63 | for (Visit visit : visits) { 64 | Entry entry = new Entry(); 65 | String date = String.format("%1$tY-%1$tm-%1$td", visit.getDate()); 66 | // see http://diveintomark.org/archives/2004/05/28/howto-atom-id#other 67 | entry.setId(String.format("tag:springsource.com,%s:%d", date, visit.getId())); 68 | entry.setTitle(String.format("%s visit on %s", visit.getPet().getName(), date)); 69 | entry.setUpdated(visit.getDate()); 70 | 71 | Content summary = new Content(); 72 | summary.setValue(visit.getDescription()); 73 | entry.setSummary(summary); 74 | 75 | entries.add(entry); 76 | } 77 | 78 | return entries; 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/web/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * The classes in this package represent PetClinic's web presentation layer. 5 | * 6 | */ 7 | package org.springframework.samples.petclinic.web; 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | The Spring Data Binding framework, an internal library used by Spring Web Flow. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/aop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/jpa-persistence.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | META-INF/orm.xml 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | PROPERTY 11 | 12 | 13 | 14 | org.springframework.samples.petclinic 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 | 80 | 81 | 82 | DATE 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
105 | 106 | 107 | 108 |
109 | 110 | 111 | 112 | DATE 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/main/resources/db/db_readme.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | === Spring PetClinic sample application - Database Configuration === 3 | ================================================================================ 4 | 5 | @author Costin Leau 6 | 7 | -------------------------------------------------------------------------------- 8 | 9 | In its default configuration, Petclinic uses an in-memory database (HSQLDB) which 10 | gets populated at startup with data. A similar setup is provided for Mysql in case 11 | a persistent database configuration is needed. 12 | Note that whenever the database type is changed, the jdbc.properties file needs to 13 | be updated. -------------------------------------------------------------------------------- /src/main/resources/db/hsqldb/initDB.txt: -------------------------------------------------------------------------------- 1 | CREATE TABLE vets ( 2 | id INTEGER NOT NULL IDENTITY PRIMARY KEY, 3 | first_name VARCHAR(30), 4 | last_name VARCHAR(30) 5 | ); 6 | CREATE INDEX vets_last_name ON vets(last_name); 7 | 8 | CREATE TABLE specialties ( 9 | id INTEGER NOT NULL IDENTITY PRIMARY KEY, 10 | name VARCHAR(80) 11 | ); 12 | CREATE INDEX specialties_name ON specialties(name); 13 | 14 | CREATE TABLE vet_specialties ( 15 | vet_id INTEGER NOT NULL, 16 | specialty_id INTEGER NOT NULL 17 | ); 18 | alter table vet_specialties add constraint fk_vet_specialties_vets foreign key (vet_id) references vets(id); 19 | alter table vet_specialties add constraint fk_vet_specialties_specialties foreign key (specialty_id) references specialties(id); 20 | 21 | CREATE TABLE types ( 22 | id INTEGER NOT NULL IDENTITY PRIMARY KEY, 23 | name VARCHAR(80) 24 | ); 25 | CREATE INDEX types_name ON types(name); 26 | 27 | CREATE TABLE owners ( 28 | id INTEGER NOT NULL IDENTITY PRIMARY KEY, 29 | first_name VARCHAR(30), 30 | last_name VARCHAR(30), 31 | address VARCHAR(255), 32 | city VARCHAR(80), 33 | telephone VARCHAR(20) 34 | ); 35 | CREATE INDEX owners_last_name ON owners(last_name); 36 | 37 | CREATE TABLE pets ( 38 | id INTEGER NOT NULL IDENTITY PRIMARY KEY, 39 | name VARCHAR(30), 40 | birth_date DATE, 41 | type_id INTEGER NOT NULL, 42 | owner_id INTEGER NOT NULL 43 | ); 44 | alter table pets add constraint fk_pets_owners foreign key (owner_id) references owners(id); 45 | alter table pets add constraint fk_pets_types foreign key (type_id) references types(id); 46 | CREATE INDEX pets_name ON pets(name); 47 | 48 | CREATE TABLE visits ( 49 | id INTEGER NOT NULL IDENTITY PRIMARY KEY, 50 | pet_id INTEGER NOT NULL, 51 | visit_date DATE, 52 | description VARCHAR(255) 53 | ); 54 | alter table visits add constraint fk_visits_pets foreign key (pet_id) references pets(id); 55 | CREATE INDEX visits_pet_id ON visits(pet_id); 56 | -------------------------------------------------------------------------------- /src/main/resources/db/hsqldb/populateDB.txt: -------------------------------------------------------------------------------- 1 | INSERT INTO vets VALUES (1, 'James', 'Carter'); 2 | INSERT INTO vets VALUES (2, 'Helen', 'Leary'); 3 | INSERT INTO vets VALUES (3, 'Linda', 'Douglas'); 4 | INSERT INTO vets VALUES (4, 'Rafael', 'Ortega'); 5 | INSERT INTO vets VALUES (5, 'Henry', 'Stevens'); 6 | INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins'); 7 | 8 | INSERT INTO specialties VALUES (1, 'radiology'); 9 | INSERT INTO specialties VALUES (2, 'surgery'); 10 | INSERT INTO specialties VALUES (3, 'dentistry'); 11 | 12 | INSERT INTO vet_specialties VALUES (2, 1); 13 | INSERT INTO vet_specialties VALUES (3, 2); 14 | INSERT INTO vet_specialties VALUES (3, 3); 15 | INSERT INTO vet_specialties VALUES (4, 2); 16 | INSERT INTO vet_specialties VALUES (5, 1); 17 | 18 | INSERT INTO types VALUES (1, 'cat'); 19 | INSERT INTO types VALUES (2, 'dog'); 20 | INSERT INTO types VALUES (3, 'lizard'); 21 | INSERT INTO types VALUES (4, 'snake'); 22 | INSERT INTO types VALUES (5, 'bird'); 23 | INSERT INTO types VALUES (6, 'hamster'); 24 | 25 | INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); 26 | INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); 27 | INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); 28 | INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); 29 | INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); 30 | INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); 31 | INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); 32 | INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); 33 | INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); 34 | INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); 35 | 36 | INSERT INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); 37 | INSERT INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); 38 | INSERT INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); 39 | INSERT INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); 40 | INSERT INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); 41 | INSERT INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); 42 | INSERT INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); 43 | INSERT INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); 44 | INSERT INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); 45 | INSERT INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); 46 | INSERT INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); 47 | INSERT INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); 48 | INSERT INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); 49 | 50 | INSERT INTO visits VALUES (1, 7, '1996-03-04', 'rabies shot'); 51 | INSERT INTO visits VALUES (2, 8, '1996-03-04', 'rabies shot'); 52 | INSERT INTO visits VALUES (3, 8, '1996-06-04', 'neutered'); 53 | INSERT INTO visits VALUES (4, 7, '1996-09-04', 'spayed'); 54 | -------------------------------------------------------------------------------- /src/main/resources/db/mysql/initDB.txt: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS petclinic; 2 | GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; 3 | 4 | USE petclinic; 5 | 6 | CREATE TABLE IF NOT EXISTS vets ( 7 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 8 | first_name VARCHAR(30), 9 | last_name VARCHAR(30), 10 | INDEX(last_name) 11 | ) engine=InnoDB; 12 | 13 | CREATE TABLE IF NOT EXISTS specialties ( 14 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 15 | name VARCHAR(80), 16 | INDEX(name) 17 | ) engine=InnoDB; 18 | 19 | CREATE TABLE IF NOT EXISTS vet_specialties ( 20 | vet_id INT(4) UNSIGNED NOT NULL, 21 | specialty_id INT(4) UNSIGNED NOT NULL, 22 | FOREIGN KEY (vet_id) REFERENCES vets(id), 23 | FOREIGN KEY (specialty_id) REFERENCES specialties(id), 24 | UNIQUE (vet_id,specialty_id) 25 | ) engine=InnoDB; 26 | 27 | CREATE TABLE IF NOT EXISTS types ( 28 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 29 | name VARCHAR(80), 30 | INDEX(name) 31 | ) engine=InnoDB; 32 | 33 | CREATE TABLE IF NOT EXISTS owners ( 34 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 35 | first_name VARCHAR(30), 36 | last_name VARCHAR(30), 37 | address VARCHAR(255), 38 | city VARCHAR(80), 39 | telephone VARCHAR(20), 40 | INDEX(last_name) 41 | ) engine=InnoDB; 42 | 43 | CREATE TABLE IF NOT EXISTS pets ( 44 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 45 | name VARCHAR(30), 46 | birth_date DATE, 47 | type_id INT(4) UNSIGNED NOT NULL, 48 | owner_id INT(4) UNSIGNED NOT NULL, 49 | INDEX(name), 50 | FOREIGN KEY (owner_id) REFERENCES owners(id), 51 | FOREIGN KEY (type_id) REFERENCES types(id) 52 | ) engine=InnoDB; 53 | 54 | CREATE TABLE IF NOT EXISTS visits ( 55 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 56 | pet_id INT(4) UNSIGNED NOT NULL, 57 | visit_date DATE, 58 | description VARCHAR(255), 59 | FOREIGN KEY (pet_id) REFERENCES pets(id) 60 | ) engine=InnoDB; -------------------------------------------------------------------------------- /src/main/resources/db/mysql/petclinic_db_setup_mysql.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | === Spring PetClinic sample application - MySQL Configuration === 3 | ================================================================================ 4 | 5 | @author Sam Brannen 6 | @author Costin Leau 7 | 8 | -------------------------------------------------------------------------------- 9 | 10 | 1) Download and install the MySQL database (e.g., MySQL Community Server 5.1.x), 11 | which can be found here: http://dev.mysql.com/downloads/ 12 | 13 | 2) Download Connector/J, the MySQL JDBC driver (e.g., Connector/J 5.1.x), which 14 | can be found here: http://dev.mysql.com/downloads/connector/j/ 15 | Copy the Connector/J JAR file (e.g., mysql-connector-java-5.1.5-bin.jar) into 16 | the db/mysql directory. Alternatively, uncomment the mysql-connector from the 17 | Petclinic pom. 18 | 19 | 3) Create the PetClinic database and user by executing the "db/mysql/createDB.txt" 20 | script. 21 | 22 | 4) Open "src/main/resources/jdbc.properties"; comment out all properties in the 23 | "HSQL Settings" section; uncomment all properties in the "MySQL Settings" 24 | section. -------------------------------------------------------------------------------- /src/main/resources/db/mysql/populateDB.txt: -------------------------------------------------------------------------------- 1 | INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter'); 2 | INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary'); 3 | INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas'); 4 | INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega'); 5 | INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens'); 6 | INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins'); 7 | 8 | INSERT IGNORE INTO specialties VALUES (1, 'radiology'); 9 | INSERT IGNORE INTO specialties VALUES (2, 'surgery'); 10 | INSERT IGNORE INTO specialties VALUES (3, 'dentistry'); 11 | 12 | INSERT IGNORE INTO vet_specialties VALUES (2, 1); 13 | INSERT IGNORE INTO vet_specialties VALUES (3, 2); 14 | INSERT IGNORE INTO vet_specialties VALUES (3, 3); 15 | INSERT IGNORE INTO vet_specialties VALUES (4, 2); 16 | INSERT IGNORE INTO vet_specialties VALUES (5, 1); 17 | 18 | INSERT IGNORE INTO types VALUES (1, 'cat'); 19 | INSERT IGNORE INTO types VALUES (2, 'dog'); 20 | INSERT IGNORE INTO types VALUES (3, 'lizard'); 21 | INSERT IGNORE INTO types VALUES (4, 'snake'); 22 | INSERT IGNORE INTO types VALUES (5, 'bird'); 23 | INSERT IGNORE INTO types VALUES (6, 'hamster'); 24 | 25 | INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); 26 | INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); 27 | INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); 28 | INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); 29 | INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); 30 | INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); 31 | INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); 32 | INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); 33 | INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); 34 | INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); 35 | 36 | INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); 37 | INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); 38 | INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); 39 | INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); 40 | INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); 41 | INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); 42 | INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); 43 | INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); 44 | INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); 45 | INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); 46 | INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); 47 | INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); 48 | INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); 49 | 50 | INSERT IGNORE INTO visits VALUES (1, 7, '1996-03-04', 'rabies shot'); 51 | INSERT IGNORE INTO visits VALUES (2, 8, '1996-03-04', 'rabies shot'); 52 | INSERT IGNORE INTO visits VALUES (3, 8, '1996-06-04', 'neutered'); 53 | INSERT IGNORE INTO visits VALUES (4, 7, '1996-09-04', 'spayed'); 54 | -------------------------------------------------------------------------------- /src/main/resources/db/petclinic_tomcat_all.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | factory 12 | org.apache.commons.dbcp.BasicDataSourceFactory 13 | 14 | 15 | 16 | driverClassName 17 | org.hsqldb.jdbcDriver 18 | 19 | 20 | url 21 | jdbc:hsqldb:hsql://localhost:9001 22 | 23 | 24 | username 25 | sa 26 | 27 | 28 | 29 | maxActive 30 | 50 31 | 32 | 33 | maxIdle 34 | 10 35 | 36 | 37 | maxWait 38 | 10000 39 | 40 | 41 | removeAbandoned 42 | true 43 | 44 | 45 | removeAbandonedTimeout 46 | 60 47 | 48 | 49 | logAbandoned 50 | true 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | factory 59 | org.apache.commons.dbcp.BasicDataSourceFactory 60 | 61 | 62 | 63 | driverClassName 64 | org.gjt.mm.mysql.Driver 65 | 66 | 72 | 73 | url 74 | jdbc:mysql://localhost:3306/petclinic?autoReconnect=true 75 | 76 | 77 | username 78 | pc 79 | 80 | 81 | password 82 | pc 83 | 84 | 85 | 86 | maxActive 87 | 50 88 | 89 | 90 | maxIdle 91 | 10 92 | 93 | 94 | maxWait 95 | 10000 96 | 97 | 98 | removeAbandoned 99 | true 100 | 101 | 102 | removeAbandonedTimeout 103 | 60 104 | 105 | 106 | logAbandoned 107 | true 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/resources/jdbc.properties: -------------------------------------------------------------------------------- 1 | # Properties file with JDBC and JPA settings. 2 | # 3 | # Applied by from 4 | # various application context XML files (e.g., "applicationContext-*.xml"). 5 | # Targeted at system administrators, to avoid touching the context XML files. 6 | 7 | 8 | #------------------------------------------------------------------------------- 9 | # Common Settings 10 | 11 | hibernate.generate_statistics=true 12 | hibernate.show_sql=true 13 | jpa.showSql=true 14 | 15 | 16 | #------------------------------------------------------------------------------- 17 | # HSQL Settings 18 | 19 | # jdbc.driverClassName=org.hsqldb.jdbcDriver 20 | # jdbc.url=jdbc:hsqldb:mem:petclinic 21 | # jdbc.username=sa 22 | # jdbc.password= 23 | 24 | # # Properties that control the population of schema and data for a new data source 25 | # jdbc.initLocation=classpath:db/hsqldb/initDB.txt 26 | # jdbc.dataLocation=classpath:db/hsqldb/populateDB.txt 27 | 28 | # # Property that determines which Hibernate dialect to use 29 | # # (only applied with "applicationContext-hibernate.xml") 30 | # hibernate.dialect=org.hibernate.dialect.HSQLDialect 31 | 32 | # # Property that determines which JPA DatabasePlatform to use with TopLink Essentials 33 | # jpa.databasePlatform=org.springframework.samples.petclinic.toplink.EssentialsHSQLPlatformWithNativeSequence 34 | 35 | # # Property that determines which database to use with an AbstractJpaVendorAdapter 36 | # jpa.database=HSQL 37 | 38 | 39 | #------------------------------------------------------------------------------- 40 | # MySQL Settings 41 | 42 | jdbc.driverClassName=com.mysql.jdbc.Driver 43 | jdbc.url=jdbc:mysql://localhost:3306/petclinic 44 | jdbc.username=root 45 | jdbc.password= 46 | 47 | # Properties that control the population of schema and data for a new data source 48 | jdbc.initLocation=classpath:db/mysql/initDB.txt 49 | jdbc.dataLocation=classpath:db/mysql/populateDB.txt 50 | 51 | # Property that determines which Hibernate dialect to use 52 | # (only applied with "applicationContext-hibernate.xml") 53 | hibernate.dialect=org.hibernate.dialect.MySQLDialect 54 | 55 | # Property that determines which JPA DatabasePlatform to use with TopLink Essentials 56 | jpa.databasePlatform=oracle.toplink.essentials.platform.database.MySQL4Platform 57 | 58 | # Property that determines which database to use with an AbstractJpaVendorAdapter 59 | jpa.database=MYSQL 60 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml! 2 | # For all other servers: Comment out the Log4J listener in web.xml to activate Log4J. 3 | log4j.rootLogger=INFO, stdout, logfile 4 | 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n 8 | 9 | log4j.appender.logfile=org.apache.log4j.RollingFileAppender 10 | log4j.appender.logfile.File=${petclinic.root}/WEB-INF/petclinic.log 11 | log4j.appender.logfile.MaxFileSize=512KB 12 | # Keep three backup files. 13 | log4j.appender.logfile.MaxBackupIndex=3 14 | # Pattern to output: date priority [category] - message 15 | log4j.appender.logfile.layout=org.apache.log4j.PatternLayout 16 | log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n 17 | 18 | log4j.logger.org.springframework.samples.petclinic.aspects=DEBUG 19 | -------------------------------------------------------------------------------- /src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | welcome=Welcome 2 | required=is required 3 | notFound=has not been found 4 | duplicate=is already in use 5 | nonNumeric=must be all numeric 6 | duplicateFormSubmission=Duplicate form submission is not allowed 7 | typeMismatch.date=invalid date 8 | typeMismatch.birthDate=invalid date 9 | -------------------------------------------------------------------------------- /src/main/resources/messages_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/src/main/resources/messages_de.properties -------------------------------------------------------------------------------- /src/main/resources/messages_en.properties: -------------------------------------------------------------------------------- 1 | # This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file. -------------------------------------------------------------------------------- /src/main/resources/petclinic.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/OwnerTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNull; 5 | 6 | import org.junit.Test; 7 | 8 | /** 9 | * JUnit test for the {@link Owner} class. 10 | * 11 | * @author Ken Krebs 12 | */ 13 | public class OwnerTests { 14 | 15 | @Test 16 | public void testHasPet() { 17 | Owner owner = new Owner(); 18 | Pet fido = new Pet(); 19 | fido.setName("Fido"); 20 | assertNull(owner.getPet("Fido")); 21 | assertNull(owner.getPet("fido")); 22 | owner.addPet(fido); 23 | assertEquals(fido, owner.getPet("Fido")); 24 | assertEquals(fido, owner.getPet("fido")); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/hibernate/HibernateClinicTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.hibernate; 2 | 3 | import org.springframework.samples.petclinic.AbstractClinicTests; 4 | import org.springframework.test.annotation.DirtiesContext; 5 | import org.springframework.test.context.ContextConfiguration; 6 | 7 | /** 8 | *

9 | * Integration tests for the {@link HibernateClinic} implementation. 10 | *

11 | *

12 | * "HibernateClinicTests-context.xml" determines the actual beans to test. 13 | *

14 | * 15 | * @author Juergen Hoeller 16 | * @author Sam Brannen 17 | */ 18 | @ContextConfiguration 19 | @DirtiesContext 20 | public class HibernateClinicTests extends AbstractClinicTests { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.jdbc; 2 | 3 | import org.springframework.samples.petclinic.AbstractClinicTests; 4 | import org.springframework.test.annotation.DirtiesContext; 5 | import org.springframework.test.context.ContextConfiguration; 6 | 7 | /** 8 | *

9 | * Integration tests for the {@link SimpleJdbcClinic} implementation. 10 | *

11 | *

12 | * "SimpleJdbcClinicTests-context.xml" determines the actual beans to test. 13 | *

14 | * 15 | * @author Thomas Risberg 16 | */ 17 | @ContextConfiguration 18 | @DirtiesContext 19 | public class SimpleJdbcClinicTests extends AbstractClinicTests { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/jpa/EntityManagerClinicTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.jpa; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.samples.petclinic.aspects.UsageLogAspect; 6 | 7 | /** 8 | *

9 | * Tests for the DAO variant based on the shared EntityManager approach. Uses 10 | * TopLink Essentials (the reference implementation) for testing. 11 | *

12 | *

13 | * Specifically tests usage of an orm.xml file, loaded by the 14 | * persistence provider through the Spring-provided persistence unit root URL. 15 | *

16 | * 17 | * @author Rod Johnson 18 | * @author Juergen Hoeller 19 | */ 20 | public class EntityManagerClinicTests extends AbstractJpaClinicTests { 21 | 22 | private UsageLogAspect usageLogAspect; 23 | 24 | public void setUsageLogAspect(UsageLogAspect usageLogAspect) { 25 | this.usageLogAspect = usageLogAspect; 26 | } 27 | 28 | @Override 29 | protected String[] getConfigPaths() { 30 | return new String[] { 31 | "applicationContext-jpaCommon.xml", 32 | "applicationContext-toplinkAdapter.xml", 33 | "applicationContext-entityManager.xml" 34 | }; 35 | } 36 | 37 | public void testUsageLogAspectIsInvoked() { 38 | String name1 = "Schuurman"; 39 | String name2 = "Greenwood"; 40 | String name3 = "Leau"; 41 | 42 | assertTrue(this.clinic.findOwners(name1).isEmpty()); 43 | assertTrue(this.clinic.findOwners(name2).isEmpty()); 44 | 45 | List namesRequested = this.usageLogAspect.getNamesRequested(); 46 | assertTrue(namesRequested.contains(name1)); 47 | assertTrue(namesRequested.contains(name2)); 48 | assertFalse(namesRequested.contains(name3)); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/jpa/HibernateEntityManagerClinicTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.jpa; 2 | 3 | /** 4 | *

5 | * Tests for the DAO variant based on the shared EntityManager approach, using 6 | * Hibernate EntityManager for testing instead of the reference implementation. 7 | *

8 | *

9 | * Specifically tests usage of an orm.xml file, loaded by the 10 | * persistence provider through the Spring-provided persistence unit root URL. 11 | *

12 | * 13 | * @author Juergen Hoeller 14 | */ 15 | public class HibernateEntityManagerClinicTests extends EntityManagerClinicTests { 16 | 17 | @Override 18 | protected String[] getConfigPaths() { 19 | return new String[] { 20 | "applicationContext-jpaCommon.xml", 21 | "applicationContext-hibernateAdapter.xml", 22 | "applicationContext-entityManager.xml" 23 | }; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/jpa/OpenJpaEntityManagerClinicTests.java: -------------------------------------------------------------------------------- 1 | 2 | package org.springframework.samples.petclinic.jpa; 3 | 4 | /** 5 | *

6 | * Tests for the DAO variant based on the shared EntityManager approach, using 7 | * Apache OpenJPA for testing instead of the reference implementation. 8 | *

9 | *

10 | * Specifically tests usage of an orm.xml file, loaded by the 11 | * persistence provider through the Spring-provided persistence unit root URL. 12 | *

13 | * 14 | * @author Juergen Hoeller 15 | */ 16 | public class OpenJpaEntityManagerClinicTests extends EntityManagerClinicTests { 17 | 18 | @Override 19 | protected String[] getConfigPaths() { 20 | return new String[] { 21 | "applicationContext-jpaCommon.xml", 22 | "applicationContext-openJpaAdapter.xml", 23 | "applicationContext-entityManager.xml" 24 | }; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/web/VisitsAtomViewTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2009 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.web; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Date; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import com.sun.syndication.feed.atom.Entry; 26 | import com.sun.syndication.feed.atom.Feed; 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertNotNull; 29 | import org.junit.Before; 30 | import org.junit.Test; 31 | 32 | import org.springframework.samples.petclinic.Pet; 33 | import org.springframework.samples.petclinic.PetType; 34 | import org.springframework.samples.petclinic.Visit; 35 | 36 | /** 37 | * @author Arjen Poutsma 38 | */ 39 | public class VisitsAtomViewTest { 40 | 41 | private VisitsAtomView visitView; 42 | 43 | private Map model; 44 | 45 | private Feed feed; 46 | 47 | @Before 48 | public void setUp() { 49 | visitView = new VisitsAtomView(); 50 | PetType dog = new PetType(); 51 | dog.setName("dog"); 52 | Pet bello = new Pet(); 53 | bello.setName("Bello"); 54 | bello.setType(dog); 55 | Visit belloVisit = new Visit(); 56 | belloVisit.setPet(bello); 57 | belloVisit.setDate(new Date(2009, 0, 1)); 58 | belloVisit.setDescription("Bello visit"); 59 | Pet wodan = new Pet(); 60 | wodan.setName("Wodan"); 61 | wodan.setType(dog); 62 | Visit wodanVisit = new Visit(); 63 | wodanVisit.setPet(wodan); 64 | wodanVisit.setDate(new Date(2009, 0, 2)); 65 | wodanVisit.setDescription("Wodan visit"); 66 | List visits = new ArrayList(); 67 | visits.add(belloVisit); 68 | visits.add(wodanVisit); 69 | 70 | model = new HashMap(); 71 | model.put("visits", visits); 72 | feed = new Feed(); 73 | 74 | } 75 | 76 | @Test 77 | public void buildFeedMetadata() { 78 | visitView.buildFeedMetadata(model, feed, null); 79 | 80 | assertNotNull("No id set", feed.getId()); 81 | assertNotNull("No title set", feed.getTitle()); 82 | assertEquals("Invalid update set", new Date(2009, 0, 2), feed.getUpdated()); 83 | } 84 | 85 | @Test 86 | public void buildFeedEntries() throws Exception { 87 | List entries = visitView.buildFeedEntries(model, null, null); 88 | assertEquals("Invalid amount of entries", 2, entries.size()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/AbstractClinicTests-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/hibernate/HibernateClinicTests-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | ${hibernate.dialect} 15 | ${hibernate.show_sql} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-entityManager.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-hibernateAdapter.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-jpaCommon.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-openJpaAdapter.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-toplinkAdapter.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | # Profiling results for each test method are written to tmp/performance. 5 | class BrowsingTest < ActionDispatch::PerformanceTest 6 | def test_homepage 7 | get '/' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Add more helper methods to be used by all tests here... 7 | end 8 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicksieger/refactoring-to-rails/6fa63e5e35b3ab054f8f3008689b44d267387db7/vendor/plugins/.gitkeep --------------------------------------------------------------------------------