Compare commits

...

16 Commits

  1. 83
      .classpath
  2. 8
      .project
  3. 26
      README.md
  4. 21
      application.properties
  5. 106
      pom.xml
  6. 122
      src/main/java/com/mousetech/gourmetj/AdminMainBean.java
  7. 51
      src/main/java/com/mousetech/gourmetj/AppBean.java
  8. 8
      src/main/java/com/mousetech/gourmetj/CategoryView.java
  9. 142
      src/main/java/com/mousetech/gourmetj/CookieBean.java
  10. 6
      src/main/java/com/mousetech/gourmetj/CuisineBean.java
  11. 12
      src/main/java/com/mousetech/gourmetj/EditShopcatBean.java
  12. 70
      src/main/java/com/mousetech/gourmetj/JSFUtils.java
  13. 49
      src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java
  14. 8
      src/main/java/com/mousetech/gourmetj/ShoppingListBean.java
  15. 23
      src/main/java/com/mousetech/gourmetj/SpringPrimeFacesApplication.java
  16. 91
      src/main/java/com/mousetech/gourmetj/SpringSecurityConfig.java
  17. 46
      src/main/java/com/mousetech/gourmetj/UserSession.java
  18. 2
      src/main/java/com/mousetech/gourmetj/persistence/dao/PantryRepository.java
  19. 2
      src/main/java/com/mousetech/gourmetj/persistence/dao/ShopcatRepository.java
  20. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Category.java
  21. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Convtable.java
  22. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Crossunitdict.java
  23. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Density.java
  24. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Info.java
  25. 4
      src/main/java/com/mousetech/gourmetj/persistence/model/Ingredient.java
  26. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Keylookup.java
  27. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Pantry.java
  28. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/PluginInfo.java
  29. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Recipe.java
  30. 22
      src/main/java/com/mousetech/gourmetj/persistence/model/Shopcat.java
  31. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Shopcatsorder.java
  32. 2
      src/main/java/com/mousetech/gourmetj/persistence/model/Unitdict.java
  33. 6
      src/main/java/com/mousetech/gourmetj/persistence/service/CategoryService.java
  34. 6
      src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java
  35. 8
      src/main/java/com/mousetech/gourmetj/springweb/PictureController.java
  36. 17
      src/main/java/com/mousetech/gourmetj/utils/TimeConverter.java
  37. 28
      src/main/java/com/mousetech/gourmetj/utils/YamlShoppingList.java
  38. 45
      src/main/resources/META-INF/resources/WEB-INF/layout/layout.xhtml
  39. 43
      src/main/resources/META-INF/resources/WEB-INF/layout/misctabs/ingshopkey.xhtml
  40. 76
      src/main/resources/META-INF/resources/detailEdit.xhtml
  41. 8
      src/main/resources/META-INF/resources/main.xhtml
  42. 307
      src/main/resources/META-INF/resources/recipeDetails.xhtml
  43. 18
      src/main/resources/META-INF/resources/shoppingList.xhtml
  44. 12
      src/main/resources/application.yml
  45. 0
      xxx

@ -9,89 +9,27 @@
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"> <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes> <attributes>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="var" path="M2_REPO/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar" sourcepath="M2_REPO/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2-sources.jar"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<classpathentry kind="var" path="M2_REPO/javax/enterprise/cdi-api/2.0/cdi-api-2.0.jar" sourcepath="M2_REPO/javax/enterprise/cdi-api/2.0/cdi-api-2.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/interceptor/javax.interceptor-api/1.2/javax.interceptor-api-1.2.jar" sourcepath="M2_REPO/javax/interceptor/javax.interceptor-api/1.2/javax.interceptor-api-1.2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/inject/javax.inject/1/javax.inject-1.jar" sourcepath="M2_REPO/javax/inject/javax.inject/1/javax.inject-1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/transaction/javax.transaction-api/1.3/javax.transaction-api-1.3.jar" sourcepath="M2_REPO/javax/transaction/javax.transaction-api/1.3/javax.transaction-api-1.3-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" sourcepath="M2_REPO/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar" sourcepath="M2_REPO/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/persistence/javax.persistence-api/2.2/javax.persistence-api-2.2.jar" sourcepath="M2_REPO/javax/persistence/javax.persistence-api/2.2/javax.persistence-api-2.2-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes> <attributes>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/primefaces-spring-boot-starter/3.3.0-rc2/primefaces-spring-boot-starter-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/primefaces-spring-boot-starter/3.3.0-rc2/primefaces-spring-boot-starter-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/primefaces/extensions/resources-ckeditor/6.2.9/resources-ckeditor-6.2.9.jar" sourcepath="M2_REPO/org/primefaces/extensions/resources-ckeditor/6.2.9/resources-ckeditor-6.2.9-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/commons-io/commons-io/2.6/commons-io-2.6.jar" sourcepath="M2_REPO/commons-io/commons-io/2.6/commons-io-2.6-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar" sourcepath="M2_REPO/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/jsf-spring-boot-starter/3.3.0-rc2/jsf-spring-boot-starter-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/jsf-spring-boot-starter/3.3.0-rc2/jsf-spring-boot-starter-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/security/spring-security-taglibs/5.1.1.RELEASE/spring-security-taglibs-5.1.1.RELEASE.jar" sourcepath="M2_REPO/org/springframework/security/spring-security-taglibs/5.1.1.RELEASE/spring-security-taglibs-5.1.1.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-aop/5.1.2.RELEASE/spring-aop-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-aop/5.1.2.RELEASE/spring-aop-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-beans/5.1.2.RELEASE/spring-beans-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-beans/5.1.2.RELEASE/spring-beans-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-core/5.1.2.RELEASE/spring-core-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-core/5.1.2.RELEASE/spring-core-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-jcl/5.1.2.RELEASE/spring-jcl-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-jcl/5.1.2.RELEASE/spring-jcl-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-context/5.1.2.RELEASE/spring-context-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-context/5.1.2.RELEASE/spring-context-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-expression/5.1.2.RELEASE/spring-expression-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-expression/5.1.2.RELEASE/spring-expression-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-web/5.1.2.RELEASE/spring-web-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-web/5.1.2.RELEASE/spring-web-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/joinfaces-autoconfigure/3.3.0-rc2/joinfaces-autoconfigure-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/joinfaces-autoconfigure/3.3.0-rc2/joinfaces-autoconfigure-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/2.1.0.RELEASE/spring-boot-autoconfigure-2.1.0.RELEASE.jar" sourcepath="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/2.1.0.RELEASE/spring-boot-autoconfigure-2.1.0.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot/2.1.0.RELEASE/spring-boot-2.1.0.RELEASE.jar" sourcepath="M2_REPO/org/springframework/boot/spring-boot/2.1.0.RELEASE/spring-boot-2.1.0.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/reflections/reflections/0.9.11/reflections-0.9.11.jar" sourcepath="M2_REPO/org/reflections/reflections/0.9.11/reflections-0.9.11-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/com/google/guava/guava/20.0/guava-20.0.jar" sourcepath="M2_REPO/com/google/guava/guava/20.0/guava-20.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/javassist/javassist/3.23.1-GA/javassist-3.23.1-GA.jar" sourcepath="M2_REPO/org/javassist/javassist/3.23.1-GA/javassist-3.23.1-GA-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-starter/2.1.0.RELEASE/spring-boot-starter-2.1.0.RELEASE.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-starter-logging/2.1.0.RELEASE/spring-boot-starter-logging-2.1.0.RELEASE.jar"/>
<classpathentry kind="var" path="M2_REPO/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar" sourcepath="M2_REPO/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar" sourcepath="M2_REPO/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar" sourcepath="M2_REPO/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.11.1/log4j-to-slf4j-2.11.1.jar" sourcepath="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.11.1/log4j-to-slf4j-2.11.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/logging/log4j/log4j-api/2.11.1/log4j-api-2.11.1.jar" sourcepath="M2_REPO/org/apache/logging/log4j/log4j-api/2.11.1/log4j-api-2.11.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar" sourcepath="M2_REPO/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar" sourcepath="M2_REPO/org/yaml/snakeyaml/1.23/snakeyaml-1.23-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/tomcat-spring-boot-starter/3.3.0-rc2/tomcat-spring-boot-starter-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/tomcat-spring-boot-starter/3.3.0-rc2/tomcat-spring-boot-starter-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-starter-tomcat/2.1.0.RELEASE/spring-boot-starter-tomcat-2.1.0.RELEASE.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/9.0.12/tomcat-embed-core-9.0.12.jar" sourcepath="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/9.0.12/tomcat-embed-core-9.0.12-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/9.0.12/tomcat-embed-el-9.0.12.jar" sourcepath="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/9.0.12/tomcat-embed-el-9.0.12-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.12/tomcat-embed-websocket-9.0.12.jar" sourcepath="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.12/tomcat-embed-websocket-9.0.12-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apache/tomcat/embed/tomcat-embed-jasper/9.0.12/tomcat-embed-jasper-9.0.12.jar" sourcepath="M2_REPO/org/apache/tomcat/embed/tomcat-embed-jasper/9.0.12/tomcat-embed-jasper-9.0.12-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/eclipse/jdt/ecj/3.13.102/ecj-3.13.102.jar" sourcepath="M2_REPO/org/eclipse/jdt/ecj/3.13.102/ecj-3.13.102-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/mojarra-spring-boot-starter/3.3.0-rc2/mojarra-spring-boot-starter-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/mojarra-spring-boot-starter/3.3.0-rc2/mojarra-spring-boot-starter-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/glassfish/javax.faces/2.3.7/javax.faces-2.3.7.jar" sourcepath="M2_REPO/org/glassfish/javax.faces/2.3.7/javax.faces-2.3.7-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/omnifaces1-spring-boot-starter/3.3.0-rc2/omnifaces1-spring-boot-starter-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/omnifaces1-spring-boot-starter/3.3.0-rc2/omnifaces1-spring-boot-starter-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/omnifaces/omnifaces/1.14.1/omnifaces-1.14.1.jar" sourcepath="M2_REPO/org/omnifaces/omnifaces/1.14.1/omnifaces-1.14.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/joinfaces/spring-security-jsf-taglib/3.3.0-rc2/spring-security-jsf-taglib-3.3.0-rc2.jar" sourcepath="M2_REPO/org/joinfaces/spring-security-jsf-taglib/3.3.0-rc2/spring-security-jsf-taglib-3.3.0-rc2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/primefaces/primefaces/6.2/primefaces-6.2.jar" sourcepath="M2_REPO/org/primefaces/primefaces/6.2/primefaces-6.2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/primefaces/extensions/primefaces-extensions/6.2.9/primefaces-extensions-6.2.9.jar" sourcepath="M2_REPO/org/primefaces/extensions/primefaces-extensions/6.2.9/primefaces-extensions-6.2.9-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar" sourcepath="M2_REPO/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-starter-data-jpa/2.1.0.RELEASE/spring-boot-starter-data-jpa-2.1.0.RELEASE.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-starter-aop/2.1.0.RELEASE/spring-boot-starter-aop-2.1.0.RELEASE.jar"/>
<classpathentry kind="var" path="M2_REPO/org/aspectj/aspectjweaver/1.9.2/aspectjweaver-1.9.2.jar" sourcepath="M2_REPO/org/aspectj/aspectjweaver/1.9.2/aspectjweaver-1.9.2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/boot/spring-boot-starter-jdbc/2.1.0.RELEASE/spring-boot-starter-jdbc-2.1.0.RELEASE.jar"/>
<classpathentry kind="var" path="M2_REPO/com/zaxxer/HikariCP/3.2.0/HikariCP-3.2.0.jar" sourcepath="M2_REPO/com/zaxxer/HikariCP/3.2.0/HikariCP-3.2.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-jdbc/5.1.2.RELEASE/spring-jdbc-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-jdbc/5.1.2.RELEASE/spring-jdbc-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-tx/5.1.2.RELEASE/spring-tx-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-tx/5.1.2.RELEASE/spring-tx-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/hibernate/hibernate-core/5.3.7.Final/hibernate-core-5.3.7.Final.jar" sourcepath="M2_REPO/org/hibernate/hibernate-core/5.3.7.Final/hibernate-core-5.3.7.Final-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar" sourcepath="M2_REPO/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/net/bytebuddy/byte-buddy/1.9.3/byte-buddy-1.9.3.jar" sourcepath="M2_REPO/net/bytebuddy/byte-buddy/1.9.3/byte-buddy-1.9.3-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/antlr/antlr/2.7.7/antlr-2.7.7.jar"/>
<classpathentry kind="var" path="M2_REPO/org/jboss/jandex/2.0.5.Final/jandex-2.0.5.Final.jar" sourcepath="M2_REPO/org/jboss/jandex/2.0.5.Final/jandex-2.0.5.Final-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar" sourcepath="M2_REPO/com/fasterxml/classmate/1.4.0/classmate-1.4.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/dom4j/dom4j/2.1.1/dom4j-2.1.1.jar" sourcepath="M2_REPO/org/dom4j/dom4j/2.1.1/dom4j-2.1.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/hibernate/common/hibernate-commons-annotations/5.0.4.Final/hibernate-commons-annotations-5.0.4.Final.jar" sourcepath="M2_REPO/org/hibernate/common/hibernate-commons-annotations/5.0.4.Final/hibernate-commons-annotations-5.0.4.Final-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-jpa/2.1.2.RELEASE/spring-data-jpa-2.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/data/spring-data-jpa/2.1.2.RELEASE/spring-data-jpa-2.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-commons/2.1.2.RELEASE/spring-data-commons-2.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/data/spring-data-commons/2.1.2.RELEASE/spring-data-commons-2.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-orm/5.1.2.RELEASE/spring-orm-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-orm/5.1.2.RELEASE/spring-orm-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/springframework/spring-aspects/5.1.2.RELEASE/spring-aspects-5.1.2.RELEASE.jar" sourcepath="M2_REPO/org/springframework/spring-aspects/5.1.2.RELEASE/spring-aspects-5.1.2.RELEASE-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/com/h2database/h2/1.4.197/h2-1.4.197.jar" sourcepath="M2_REPO/com/h2database/h2/1.4.197/h2-1.4.197-sources.jar"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"> <classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes> <attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/> <attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="test" value="true"/> <attribute name="test" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
@ -99,6 +37,5 @@
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
<classpathentry kind="output" path="target/classes"/> <classpathentry kind="output" path="target/classes"/>
</classpath> </classpath>

@ -5,6 +5,11 @@
<projects> <projects>
</projects> </projects>
<buildSpec> <buildSpec>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand> <buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name> <name>org.eclipse.jdt.core.javabuilder</name>
<arguments> <arguments>
@ -17,7 +22,8 @@
</buildCommand> </buildCommand>
</buildSpec> </buildSpec>
<natures> <natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures> </natures>
</projectDescription> </projectDescription>

@ -1,4 +1,4 @@
# Gourmet Recipe Manager - Spring Boot # Gourmet Recipe Manager - Spring Boot - Version 2
This is a port of Thomas Hinkle (thinkle) Gourmet Recipe Manager. This is a port of Thomas Hinkle (thinkle) Gourmet Recipe Manager.
@ -85,4 +85,26 @@ employed when you run this app on your local desktop.
### Improved graphics support ### Improved graphics support
A lot of recipe websites publish images in webp form. Support A lot of recipe websites publish images in webp form. Support
for webp has now been added. for webp has now been added.
### Better session management
JSF tends to depend on session-scope context. Sessions, however
time out and this has been an annoyance when a recipe is being
displayed. To minimize this, better timeout mechanisms have been
installed and the recipe browser keeps last-search and search-type
values in long-lived cookies on the client. The server will read
and cache them, but if the server times out, it will automatically
re-read the cookies on the next request.
When editing, the AJAX controls tended to get confused when a
session timed out. New changes give a "save work" warning 5 minutes
before timeout, and force a timeout from JavaScript that
exits the unattended page before the user can run afoul of the
lost session.
Note that by default, JSF caches ViewState in a session so every
JSF View can cause a session to be created, not just Views that
reference View- or SessionScoped backing beans. This is alterable
by setting an option in the faces-config.
force

@ -1,13 +1,8 @@
# THIS is the application properties used when testing in the IDE # THIS is the application properties used when testing in the IDE
# The application.yml (production) is ignored. # or running stand-alone from the command line.
#joinfaces.jsf.project-stage=development # It augments/overrides application.yml in the JAR
# They lied. This doesn't work:
joinfaces.primefaces.theme=cupertino
joinfaces.jsf.webapp-resources-directory=/resources joinfaces.jsf.webapp-resources-directory=/resources
# This works. Note that ONLY THE FIRST theme set will work unless
# you delete the old primefaces.THEME from ServletContext
server.servlet.context-parameters.primefaces.THEME=omega
server.servlet.session.timeout=30m server.servlet.session.timeout=30m
spring.thymeleaf.enabled=false spring.thymeleaf.enabled=false
@ -18,11 +13,17 @@ spring.datasource.url=jdbc:mysql://dbase/recipes
spring.datasource.username=recipes spring.datasource.username=recipes
pring.datasource.password=yumyumyum pring.datasource.password=yumyumyum
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
#org.sqlite.JDBC
#spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect #Runtime lies and says no longer required, but it defaults to MySQL5.5.0!:
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
#org.sqlite.hibernate.dialect.SQLiteDialect #org.sqlite.hibernate.dialect.SQLiteDialect
#spring.jpa.show-sql: true #spring.jpa.show-sql: true
# My special properties # My special properties
gourmet.password.file=${user.home}/.gourmetpw gourmet.password.file=${user.home}/.gourmetpw
# This will override aplication.yml
#server.servlet.context-parameters.primefaces.THEME=le-frog
### HttpSession timeout (note effects on detailEdit idleMonitors)
server.servlet.session.timeout=35m

@ -7,7 +7,7 @@
<groupId>com.mousetech.gourmet</groupId> <groupId>com.mousetech.gourmet</groupId>
<artifactId>gourmetj</artifactId> <artifactId>gourmetj</artifactId>
<version>0.1.4-SNAPSHOT</version> <version>0.2.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>GourmetJ</name> <name>GourmetJ</name>
@ -17,7 +17,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version> <version>3.2.2</version>
<relativePath /> <!-- lookup parent from repository --> <relativePath /> <!-- lookup parent from repository -->
</parent> </parent>
@ -30,25 +30,44 @@
</repositories> </repositories>
<properties> <properties>
<!-- compile library versions -->
<jakarta.validation-api.version>3.0.2</jakarta.validation-api.version>
<joinfaces.version>5.2.2</joinfaces.version>
<maven-model.version>3.9.6</maven-model.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version> <java.version>17</java.version>
<joinfaces.version>3.3.0-rc2</joinfaces.version> <spring-boot.version>3.2.2</spring-boot.version>
<faces.version>4.0.5</faces.version>
<joinfaces.version>5.2.2</joinfaces.version>
<twelvemonkeys.version>3.10.1</twelvemonkeys.version>
<junit.jupiter.version>5.4.0</junit.jupiter.version> <junit.jupiter.version>5.4.0</junit.jupiter.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${jakarta.validation-api.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.joinfaces</groupId> <groupId>org.joinfaces</groupId>
<artifactId>joinfaces-dependencies</artifactId> <artifactId>joinfaces-bom</artifactId>
<version>${joinfaces.version}</version> <version>${joinfaces.version}</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>${maven-model.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.joinfaces</groupId> <groupId>org.joinfaces</groupId>
@ -60,9 +79,15 @@
<version>1.0.10</version> <version>1.0.10</version>
</dependency> </dependency>
<dependency> <dependency>
<!-- Primefaces theme won't work without this! -->
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- <dependency>
<groupId>javax.enterprise</groupId> <groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId> <artifactId>cdi-api</artifactId>
</dependency> </dependency>-->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
@ -71,55 +96,73 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<!--
https://mvnrepository.com/artifact/jakarta.persistence/jakarta.persistence-api -->
<!-- <dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>-->
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>jakarta.validation</groupId>
<artifactId>validation-api</artifactId> <artifactId>jakarta.validation-api</artifactId>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator --> <!--
https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency> <dependency>
<groupId>org.hibernate.validator</groupId> <groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <!--
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency> <dependency>
<groupId>javax.servlet.jsp</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>javax.servlet.jsp-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.servlet</groupId> <!-- <dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jstl</artifactId> <artifactId>jstl</artifactId>
</dependency> </dependency>
-->
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId> <artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <!-- <dependency>
<groupId>org.glassfish</groupId> <groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId> <artifactId>jakarta.el</artifactId>
</dependency> </dependency>
-->
<!-- https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-core --> <!--
https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-core -->
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId> <artifactId>imageio-core</artifactId>
<version>3.8.1</version> <version>${twelvemonkeys.version}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-webp --> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--
https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-webp -->
<!-- In Core??? --> <!-- In Core??? -->
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId> <artifactId>imageio-webp</artifactId>
<version>3.8.1</version> <version>${twelvemonkeys.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- Needed only if you deploy ImageIO plugins as part of a web app. <!-- Needed only if you deploy ImageIO plugins as part of a web app.
@ -128,7 +171,7 @@
<dependency> <dependency>
<groupId>com.twelvemonkeys.servlet</groupId> <groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId> <artifactId>servlet</artifactId>
<version>3.8.1</version> <version>${twelvemonkeys.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@ -141,21 +184,15 @@
<version>8.0.30</version> <version>8.0.30</version>
</dependency> </dependency>
<!-- SQLite DB --> <!-- <dependency>
<dependency> <groupId>org.glassfish</groupId>
<groupId>org.xerial</groupId> <artifactId>jakarta.faces</artifactId>
<artifactId>sqlite-jdbc</artifactId> <version>${com.sun.faces.version}</version>
</dependency> </dependency>-->
<dependency>
<groupId>com.github.gwenn</groupId>
<artifactId>sqlite-dialect</artifactId>
<version>0.1.2</version>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -164,7 +201,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

@ -1,33 +1,38 @@
package com.mousetech.gourmetj; package com.mousetech.gourmetj;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import javax.annotation.PostConstruct; import java.util.ArrayList;
import javax.faces.event.AjaxBehaviorEvent; import java.util.List;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import com.mousetech.gourmetj.persistence.model.Recipe; import com.mousetech.gourmetj.persistence.model.Recipe;
import com.mousetech.gourmetj.persistence.service.RecipeService; import com.mousetech.gourmetj.persistence.service.RecipeService;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.faces.model.DataModel;
import jakarta.faces.model.ListDataModel;
import jakarta.inject.Inject;
import jakarta.inject.Named;
/** /**
* Main control panel backing bean. * Main control panel backing bean.
* *
* The rare and fabled RequestScope, which is otherwise
* useless 90% of the time. Here we maintain no session
* state. so we can better support the session timeout
* for editing functions.
*
* @author timh * @author timh
* @since Jun 28, 2012 * @since Jun 28, 2012
*/ */
@Named @Named
@ViewScoped @RequestScoped
public class AdminMainBean implements Serializable { public class AdminMainBean implements Serializable {
/** /**
@ -44,8 +49,11 @@ public class AdminMainBean implements Serializable {
private static final Logger log = private static final Logger log =
LoggerFactory.getLogger(AdminMainBean.class); LoggerFactory.getLogger(AdminMainBean.class);
/** Cookie delimiter */
private static final String CKDLM = ",";
/** /**
* Persistency service for Recipes * Persistency service for Recipes.
*/ */
@Inject @Inject
@ -56,6 +64,24 @@ public class AdminMainBean implements Serializable {
this.recipeService = service; this.recipeService = service;
} }
// **
@Inject
private CookieBean cookieBean;
/**
* @return the cookieBean
*/
public CookieBean getCookieBean() {
return cookieBean;
}
/**
* @param cookieBean the cookieBean to set
*/
public void setCookieBean(CookieBean cookieBean) {
this.cookieBean = cookieBean;
}
// ** // **
@Inject @Inject
private UserSession userSession; private UserSession userSession;
@ -81,10 +107,10 @@ public class AdminMainBean implements Serializable {
* @return the searchText * @return the searchText
*/ */
public String getSearchText() { public String getSearchText() {
if (this.searchResults == null) { // if (this.searchResults == null) {
// Fake around broken @PostConstruct // this.setSearchText(cookieBean.getSearchText());
this.setSearchText(userSession.getLastSearch()); // }
} this.searchText = cookieBean.getSearchText();
return searchText; return searchText;
} }
@ -93,7 +119,7 @@ public class AdminMainBean implements Serializable {
*/ */
public void setSearchText(String searchText) { public void setSearchText(String searchText) {
this.searchText = searchText; this.searchText = searchText;
userSession.setLastSearch(searchText); cookieBean.setSearchText(searchText);
} }
private List<String> suggestionList = null; private List<String> suggestionList = null;
@ -104,7 +130,7 @@ public class AdminMainBean implements Serializable {
public List<String> searchSuggestionList(String query) { public List<String> searchSuggestionList(String query) {
if (suggestionList == null) { if (suggestionList == null) {
switch (this.userSession.getSearchType()) { switch (searchtypeEnum()) {
case rst_BY_CATEGORY: case rst_BY_CATEGORY:
suggestionList = recipeService.findCategories(); suggestionList = recipeService.findCategories();
break; break;
@ -118,6 +144,16 @@ public class AdminMainBean implements Serializable {
return suggestionList; return suggestionList;
} }
private RecipeSearchType searchtypeEnum() {
int stn = cookieBean.getSearchType();
return searchtypeEnum(stn);
}
private RecipeSearchType searchtypeEnum(int stn) {
RecipeSearchType st = RecipeSearchType.values()[stn];
return st;
}
/**/ /**/
transient DataModel<Recipe> searchResults; transient DataModel<Recipe> searchResults;
@ -127,7 +163,7 @@ public class AdminMainBean implements Serializable {
public DataModel<Recipe> getSearchResults() { public DataModel<Recipe> getSearchResults() {
if (searchResults == null) { if (searchResults == null) {
searchResults = new ListDataModel<Recipe>(); searchResults = new ListDataModel<Recipe>();
init(); // @PostConstruct is broken init(); // @PostConstruct is broken TODO: fixed??
} }
return searchResults; return searchResults;
} }
@ -147,7 +183,7 @@ public class AdminMainBean implements Serializable {
@PostConstruct @PostConstruct
void init() { void init() {
log.debug("Initializing AdminMainBean " + this); log.debug("Initializing AdminMainBean " + this);
this.setSearchText(userSession.getLastSearch()); this.setSearchText(cookieBean.getSearchText());
// Clean up from any previous operations. // Clean up from any previous operations.
this.userSession.setRecipe(null); this.userSession.setRecipe(null);
doFind(); doFind();
@ -177,8 +213,20 @@ public class AdminMainBean implements Serializable {
*/ */
public String doFind() { public String doFind() {
List<Recipe> recipes = null; List<Recipe> recipes = null;
if ( searchText == null ) {
switch (this.getUserSession().getSearchType()) { setSearchText("");
}
searchText = searchText.trim();
// Persist current settings
try {
cookieBean.saveCookies();
} catch (UnsupportedEncodingException e) {
// Something is really wrong if we can't create UTF-8!
log.error("Unable to save cookies!", e);
}
RecipeSearchType st = searchtypeEnum();
switch (st) {
case rst_BY_NAME: case rst_BY_NAME:
recipes = recipeService.findByTitle(searchText); recipes = recipeService.findByTitle(searchText);
break; break;
@ -196,12 +244,11 @@ public class AdminMainBean implements Serializable {
break; break;
default: default:
log.error("Invalid recipe search type: " log.error("Invalid recipe search type: "
+ this.getUserSession().getSearchType()); + st);
break; break;
} }
getSearchResults().setWrappedData(recipes); getSearchResults().setWrappedData(recipes);
this.userSession.setLastSearch(this.getSearchText());
return null; // Stay on page return null; // Stay on page
} }
@ -242,27 +289,4 @@ public class AdminMainBean implements Serializable {
// items. // items.
return "recipeDetails?faces-redirect=true"; return "recipeDetails?faces-redirect=true";
} }
/**
* Get printable preptime. Database version is in seconds.
*
* @deprecated User {@link UserSession#formatTime(Long)}
*
* @return Formatted time. Called from EL on main page.
*/
public String formatPreptime(int timesec) {
StringBuffer sb = new StringBuffer(20);
int preptime = timesec / 60;
if (preptime > 60) {
int hours = preptime / 60;
sb.append(hours);
sb.append(" h. ");
preptime %= 60;
}
if (preptime > 0) {
sb.append(preptime);
sb.append(" min.");
}
return sb.toString();
}
} }

@ -0,0 +1,51 @@
package com.mousetech.gourmetj;
import java.util.ArrayList;
import java.util.List;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.model.SelectItem;
import jakarta.inject.Named;
/**
* Appplication-scope data (mostly constants)
*
* @author timh
* @since Feb 1, 2024
*/
@Named
@ApplicationScoped
public class AppBean {
public AppBean() {
// TODO Auto-generated constructor stub
}
private List<SelectItem> searchTypeList;
/**
* @return the searchTypeList
* @see RecipeSearchType
* Used by main.xhtml
*/
public List<SelectItem> getSearchTypeList() {
if (searchTypeList == null) {
searchTypeList = loadSearchTypeList();
}
return searchTypeList;
}
private List<SelectItem> loadSearchTypeList() {
List<SelectItem> list = new ArrayList<SelectItem>(5);
list.add(new SelectItem(RecipeSearchType.rst_BY_NAME.ordinal(),
"Title"));
list.add(new SelectItem(RecipeSearchType.rst_BY_CATEGORY.ordinal(),
"Category"));
list.add(new SelectItem(RecipeSearchType.rst_BY_CUISINE.ordinal(),
"Cuisine"));
list.add(
new SelectItem(RecipeSearchType.rst_BY_INGREDIENT.ordinal(),
"Ingredient"));
return list;
}
}

@ -2,10 +2,10 @@ package com.mousetech.gourmetj;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import javax.faces.view.ViewScoped; import jakarta.faces.view.ViewScoped;
import javax.inject.Inject; import jakarta.inject.Inject;
import javax.inject.Named; import jakarta.inject.Named;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

@ -0,0 +1,142 @@
/**
* Copyright (C) 2024, Tim Holloway
*
* Manages app data persisted client-side in cookies.
*
* Date written: Jan 31, 2024
* Author: Tim Holloway <timh@mousetech.com>
*/
package com.mousetech.gourmetj;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
/**
* Request caching object for cookie data persistence.
* Does double-duty serving View Session timeouts.
*
* @author timh
* @since Jan 31, 2024
*/
@Named
@RequestScoped
public class CookieBean {
private static final String KEY_DISPLAY_ROWS = "displayRows";
private static final String KEY_SEARCH_TYPE = "searchType";
private static final String KEY_SEARCH_FOR = "searchFor";
/* Logger */
private static final Logger log =
LoggerFactory.getLogger(CookieBean.class);
private Map<String, String> cookieMap;
/**
* Constructor.
*/
public CookieBean() {
}
@PostConstruct
public void init() {
this.cookieMap = JSFUtils.getCookies();
}
/**
* Persist us to client cookie storage
*
* @throws UnsupportedEncodingException (which should never
* happen)
*/
public void saveCookies()
throws UnsupportedEncodingException {
final Map<String, Object> properties = new HashMap<>();
properties.put("maxAge", 31536000);
properties.put("path", "/");
properties.put("SameSite", "Strict");
for (Entry<String, String> e : cookieMap.entrySet()) {
JSFUtils.outputCookie(e.getKey(), e.getValue(),
properties);
}
}
/**
* Get Cookie value by name
*
* @param name Name of the cookie
* @return Value stored in the cookie
*/
public String getCookieValue(String name) {
return cookieMap.get(name);
}
public void setCookieValue(String name, String value) {
cookieMap.put(name, value);
}
// ************************
// App-specific properties
// ************************
public String getSearchText() {
return cookieMap.get(KEY_SEARCH_FOR);
}
public void setSearchText(String value) {
cookieMap.put(KEY_SEARCH_FOR, value);
}
// **
public Integer getSearchType() {
if (!cookieMap.containsKey(KEY_SEARCH_TYPE)) {
cookieMap.put(KEY_SEARCH_TYPE, "0");
}
String st = cookieMap.get(KEY_SEARCH_TYPE);
return Integer.valueOf(String.valueOf(st));
}
public void setSearchType(Integer value) {
cookieMap.put(KEY_SEARCH_TYPE, String.valueOf(value));
}
// **
public Integer getDisplayListSize() {
if (!cookieMap.containsKey(KEY_DISPLAY_ROWS)) {
cookieMap.put(KEY_DISPLAY_ROWS, "30");
}
String st = cookieMap.get(KEY_DISPLAY_ROWS);
return Integer.valueOf(String.valueOf(st));
}
public void setDisplayListSize(Integer value) {
cookieMap.put(KEY_DISPLAY_ROWS, String.valueOf(value));
}
/**
* IdleMonitor backing methods (session/View timeout)
*/
public void sessionIdleListener() {
log.info("Session Idle Listener fired.");
JSFUtils.addWarningMessage("Timeout approaching. Save your work!");
}
public void sessionTimeout() {
log.info("Session Timeout Listener fired.");
JSFUtils.logout();
}
}

@ -8,9 +8,9 @@ package com.mousetech.gourmetj;
import java.util.List; import java.util.List;
import javax.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import jakarta.inject.Inject;
import javax.inject.Named; import jakarta.inject.Named;
import com.mousetech.gourmetj.persistence.service.RecipeService; import com.mousetech.gourmetj.persistence.service.RecipeService;

@ -3,12 +3,12 @@ package com.mousetech.gourmetj;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import javax.faces.event.AjaxBehaviorEvent; import jakarta.faces.event.AjaxBehaviorEvent;
import javax.faces.event.ValueChangeEvent; import jakarta.faces.event.ValueChangeEvent;
import javax.faces.view.ViewScoped; import jakarta.faces.view.ViewScoped;
import javax.inject.Inject; import jakarta.inject.Inject;
import javax.inject.Named; import jakarta.inject.Named;
import com.mousetech.gourmetj.persistence.dao.ShopcatRepository; import com.mousetech.gourmetj.persistence.dao.ShopcatRepository;

@ -1,11 +1,18 @@
package com.mousetech.gourmetj; package com.mousetech.gourmetj;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.faces.application.FacesMessage; import jakarta.faces.application.FacesMessage;
import javax.faces.context.ExternalContext; import jakarta.faces.context.ExternalContext;
import javax.faces.context.FacesContext; import jakarta.faces.context.FacesContext;
import javax.faces.context.Flash; import jakarta.faces.context.Flash;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -55,6 +62,14 @@ public class JSFUtils {
message); message);
} }
public static void addWarningMessage(String msgString) {
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_WARN, "WARNING", msgString);
FacesContext.getCurrentInstance().addMessage(null,
message);
}
/** /**
* Post an error-level message to the FacesContext where the * Post an error-level message to the FacesContext where the
* &lt;h:messages&gt; tag can display it. * &lt;h:messages&gt; tag can display it.
@ -103,4 +118,51 @@ public class JSFUtils {
public static void putFlash(String key, Object value) { public static void putFlash(String key, Object value) {
flashScope().put(key, value); flashScope().put(key, value);
} }
//***********
//* COOKIE!!!
//***********
/**
* Get cookie values.
*/
public static Map<String, String> getCookies(){
Map<String, Object> m0 = getExternalContext().getRequestCookieMap();
Map<String, String>m1 = new HashMap<String, String>();
m1 = m0.entrySet()
.stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> ((Cookie)e.getValue()).getValue()));
return m1;
}
/**
* Set a cookie value in Response.
* @param name Cookie name
* @param value Cookie value
* @param properties Cookie property Map (timeout, <i>etc.</i>)
* @throws UnsupportedEncodingException
*/
public static void outputCookie(String name,
String value, Map<String, Object> properties) throws UnsupportedEncodingException {
getExternalContext().addResponseCookie(name,
URLEncoder.encode(value, "UTF-8"),
properties);
}
/**
* Destroy current session, logging user out.
*/
public static void logout() {
log.warn("Logging out session");
jakarta.servlet.http.HttpSession session =
(HttpSession) getExternalContext().getSession(false);
if ( session != null ) {
session.invalidate();
} else {
log.warn("Session did not exist.");
}
}
} }

@ -7,24 +7,23 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import javax.faces.event.AjaxBehaviorEvent; import jakarta.faces.model.DataModel;
import javax.faces.model.DataModel; import jakarta.faces.model.ListDataModel;
import javax.faces.model.ListDataModel; import jakarta.faces.view.ViewScoped;
import javax.faces.view.ViewScoped; import jakarta.inject.Inject;
import javax.inject.Inject; import jakarta.inject.Named;
import javax.inject.Named; import jakarta.servlet.http.Part;
import javax.servlet.http.Part; import jakarta.faces.event.AjaxBehaviorEvent;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.primefaces.event.FileUploadEvent; import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.UploadedFile; import org.primefaces.model.file.UploadedFile;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.mousetech.gourmetj.IngredientUI;
import com.mousetech.gourmetj.UserSession;
import com.mousetech.gourmetj.persistence.model.Category; import com.mousetech.gourmetj.persistence.model.Category;
import com.mousetech.gourmetj.persistence.model.Ingredient; import com.mousetech.gourmetj.persistence.model.Ingredient;
import com.mousetech.gourmetj.persistence.model.Recipe; import com.mousetech.gourmetj.persistence.model.Recipe;
@ -391,7 +390,7 @@ public class RecipeDetailBean implements Serializable {
* *
* @param event Unused * @param event Unused
*/ */
public void ajaxAddIngredient(AjaxBehaviorEvent event) { public void ajaxAddIngredient(jakarta.faces.event.AjaxBehaviorEvent event) {
doAddIngredient(); doAddIngredient();
} }
@ -1064,39 +1063,21 @@ public class RecipeDetailBean implements Serializable {
// *** // ***
Part imageFile = null;
/**
* @return the imageFile set by the image upload control
*/
public Part getImageFile() {
return imageFile;
}
/**
* @param imageFile the imageFile to set
*/
public void setImageFile(Part imageFile) {
this.imageFile = imageFile;
}
/** /**
* Load/replace images. Computes thumbnail. * Load/replace images. Computes thumbnail.
* *
* @param event PrimeFaces file upload event object * @param event PrimeFaces file upload event object
*/ */
public void ajaxUploadImage(FileUploadEvent event) { public void ajaxUploadImage(FileUploadEvent event) {
UploadedFile foo = event.getFile(); PictureController.importImage(recipe,
event.getFile().getContent());
PictureController.importImage(recipe, foo.getContents());
} }
/** /**
* Remove images from recipe * Remove images from recipe
*
* @param event Notused
*/ */
public void ajaxDeleteImage(AjaxBehaviorEvent event) { public void ajaxDeleteImage() {
log.info("Deleting current recipe image");
this.recipe.setImage(null); this.recipe.setImage(null);
this.recipe.setThumb(null); this.recipe.setThumb(null);
} }

@ -6,10 +6,10 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import javax.faces.view.ViewScoped; import jakarta.faces.view.ViewScoped;
import javax.inject.Inject; import jakarta.inject.Inject;
import javax.inject.Named; import jakarta.inject.Named;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.primefaces.model.StreamedContent; import org.primefaces.model.StreamedContent;

@ -1,31 +1,29 @@
package com.mousetech.gourmetj; package com.mousetech.gourmetj;
import javax.faces.application.ViewExpiredException; import jakarta.faces.application.ViewExpiredException;
import javax.servlet.ServletContext; import jakarta.servlet.ServletContext;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import org.primefaces.application.resource.PrimeResourceHandler;
import org.primefaces.renderkit.HeadRenderer;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@SpringBootApplication @SpringBootApplication
@ServletComponentScan
@EntityScan(value = { @EntityScan(value = {
"com.mousetech.gourmetj.persistence.model" }) "com.mousetech.gourmetj.persistence.model" })
public class SpringPrimeFacesApplication { public class SpringPrimeFacesApplication {
private static final String IMAGE_IIO_PROVIDER_CONTEXT_LISTENER =
"com.twelvemonkeys.servlet.image.IIOProviderContextListener";
final String errorPage = "/error/error.html"; final String errorPage = "/error/error.html";
final String error404Page = "/error/error404.html"; final String error404Page = "/error/error404.html";
final String expiredPage = "/error/viewExpired.xhtml"; final String expiredPage = "/main.xhtml";
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringPrimeFacesApplication.class, SpringApplication.run(SpringPrimeFacesApplication.class,
@ -38,21 +36,14 @@ public class SpringPrimeFacesApplication {
@Override @Override
public void onStartup(ServletContext servletContext) public void onStartup(ServletContext servletContext)
throws ServletException { throws ServletException {
/* Note that we cannot set theme here since it was
* already set earlier. Default value is "aristo".
*/
// servletContext.setInitParameter(
// "primefaces.THEME", "bluesky");
servletContext.setInitParameter( servletContext.setInitParameter(
"javax.faces.FACELETS_SKIP_COMMENTS", "jakarta.faces.FACELETS_SKIP_COMMENTS",
"true"); "true");
servletContext.setInitParameter( servletContext.setInitParameter(
"com.sun.faces.expressionFactory", "com.sun.faces.expressionFactory",
"com.sun.el.ExpressionFactoryImpl"); "com.sun.el.ExpressionFactoryImpl");
servletContext.setInitParameter( servletContext.setInitParameter(
"primefaces.UPLOADER", "native"); "primefaces.UPLOADER", "native");
servletContext.addListener(IMAGE_IIO_PROVIDER_CONTEXT_LISTENER);
} }
}; };
} }

@ -2,7 +2,6 @@ package com.mousetech.gourmetj;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.LineNumberInputStream;
import java.io.LineNumberReader; import java.io.LineNumberReader;
import java.util.Arrays; import java.util.Arrays;
@ -10,52 +9,43 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer; import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import jakarta.servlet.DispatcherType;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
public class SpringSecurityConfig public class SpringSecurityConfig {
extends WebSecurityConfigurerAdapter { //extends WebSecurityConfigurerAdapter {
/* Logger */ /* Logger */
private static final Logger log = private static final Logger log =
LoggerFactory.getLogger(SpringSecurityConfig.class); LoggerFactory.getLogger(SpringSecurityConfig.class);
@Override
protected void configure(HttpSecurity http)
throws Exception {
// require all requests to be authenticated except
// for the resources
http.authorizeRequests()
.antMatchers("/javax.faces.resource/**",
"/",
"/index.jsf",
"/main.jsf",
"/img/**",
"/recipeDetails.jsf",
"/recipePrint.jsf")
.permitAll().anyRequest().authenticated();
// login
http.formLogin()// .loginPage("/login.xhtml")
.permitAll();
// .failureUrl("/login.xhtml?error=true");
// logout
// http.logout().logoutSuccessUrl("/login.xhtml");
// not needed as JSF 2.2 is implicitly protected
// against CSRF
http.csrf().disable();
}
@Value("${gourmet.password.file}") @Value("${gourmet.password.file}")
private String passwordFile; private String passwordFile;
/**
* Load in config file with in-memory credentials.
* For practical use, this should probably be
* replace with JdbcUserDetailsManager and credentials in
* the recipe database.
*
* @param auth Builder for the authenticator
* @throws Exception
*/
@Autowired @Autowired
public void configureGlobal( public void configureGlobal(
AuthenticationManagerBuilder auth) throws Exception { AuthenticationManagerBuilder auth) throws Exception {
@ -72,17 +62,21 @@ public class SpringSecurityConfig
LineNumberReader rdr = LineNumberReader rdr =
new LineNumberReader(new FileReader(pwFile)); new LineNumberReader(new FileReader(pwFile));
String pwLine; String pwLine;
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> authenticator = InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
authenticator =
auth.inMemoryAuthentication(); auth.inMemoryAuthentication();
while ((pwLine = rdr.readLine()) != null) { while ((pwLine = rdr.readLine()) != null) {
pwLine = pwLine.trim(); pwLine = pwLine.trim();
if (( pwLine.length() == 0) || (pwLine.charAt(0) == '#')) { if ((pwLine.length() == 0)
|| (pwLine.charAt(0) == '#')) {
continue; continue;
} }
String[] creds = parseCreds(pwLine); String[] creds = parseCreds(pwLine);
UserDetailsManagerConfigurer<AuthenticationManagerBuilder, InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>>.UserDetailsBuilder bar = UserDetailsManagerConfigurer<AuthenticationManagerBuilder,
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>>
.UserDetailsBuilder bar =
authenticator.withUser(creds[0]) authenticator.withUser(creds[0])
.password("{noop}"+creds[1]); .password("{noop}" + creds[1]);
int credlen = creds.length; int credlen = creds.length;
for (int i = 2; i < credlen; i++) { for (int i = 2; i < credlen; i++) {
bar.roles(creds[i]); bar.roles(creds[i]);
@ -104,4 +98,37 @@ public class SpringSecurityConfig
.toArray(String[]::new); .toArray(String[]::new);
return ocreds; return ocreds;
} }
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests((authorize)-> authorize
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
.anyRequest().authenticated()
);
return http.build();
}
/**
* Replaces old antMatchers for determining secured URLs.
* @return customizer
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/javax.faces.resource/**",
"/",
"/index.jsf",
"/index.xhtml",
"/main.jsf",
"/img/**",
"/recipeDetails.jsf",
"/shoppingList.jsf",
"/recipePrint.jsf");
}
} }

@ -4,9 +4,9 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.enterprise.context.SessionScoped; import jakarta.enterprise.context.SessionScoped;
import javax.faces.model.SelectItem; import jakarta.faces.model.SelectItem;
import javax.inject.Named; import jakarta.inject.Named;
import org.primefaces.PrimeFaces; import org.primefaces.PrimeFaces;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -124,37 +124,12 @@ public class UserSession implements Serializable {
/** /**
* @param searchType the searchType to set * @param searchType the searchType to set
* @deprecated. Use CookieBean TODO
*/ */
public void setSearchType(RecipeSearchType searchType) { public void setSearchType(RecipeSearchType searchType) {
this.searchType = searchType; this.searchType = searchType;
} }
private List<SelectItem> searchTypeList;
/**
* @return the searchTypeList
*/
public List<SelectItem> getSearchTypeList() {
if (searchTypeList == null) {
searchTypeList = loadSearchTypeList();
}
return searchTypeList;
}
private List<SelectItem> loadSearchTypeList() {
List<SelectItem> list = new ArrayList<SelectItem>(5);
list.add(new SelectItem(RecipeSearchType.rst_BY_NAME,
"Title"));
list.add(new SelectItem(RecipeSearchType.rst_BY_CATEGORY,
"Category"));
list.add(new SelectItem(RecipeSearchType.rst_BY_CUISINE,
"Cuisine"));
list.add(
new SelectItem(RecipeSearchType.rst_BY_INGREDIENT,
"Ingredient"));
return list;
}
// ==== // ====
public String formatCategories(Recipe r) { public String formatCategories(Recipe r) {
@ -189,19 +164,6 @@ public class UserSession implements Serializable {
*/ */
private List<Recipe> shoppingList = new ArrayList<Recipe>(); private List<Recipe> shoppingList = new ArrayList<Recipe>();
/**
* @return the sessionTimeoutInterval
*/
public long getSessionTimeoutInterval() {
return sessionTimeoutInterval;
}
public void sessionIdleListener() {
log.warn("Session Idle Listener fired.");
PrimeFaces.current()
.executeScript("sessionExpiredConfirmation.show()");
}
public String logoutAction() { public String logoutAction() {
log.warn("Session Idle listener logout"); log.warn("Session Idle listener logout");
return goHome(); return goHome();

@ -2,7 +2,7 @@ package com.mousetech.gourmetj.persistence.dao;
import java.util.List; import java.util.List;
import javax.transaction.Transactional; import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;

@ -2,7 +2,7 @@ package com.mousetech.gourmetj.persistence.dao;
import java.util.List; import java.util.List;
import javax.transaction.Transactional; import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,8 +1,8 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
/** /**
* The persistent class for the "ingredients" database table. The * The persistent class for the "ingredients" database table. The

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**
* The persistent class for the "pantry" database table. * The persistent class for the "pantry" database table.

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -6,7 +6,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.persistence.*; import jakarta.persistence.*;
/** /**
* The persistent class for the "recipe" database table. * The persistent class for the "recipe" database table.

@ -4,17 +4,17 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.persistence.Column; import jakarta.persistence.Column;
import javax.persistence.Entity; import jakarta.persistence.Entity;
import javax.persistence.FetchType; import jakarta.persistence.FetchType;
import javax.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import javax.persistence.GenerationType; import jakarta.persistence.GenerationType;
import javax.persistence.Id; import jakarta.persistence.Id;
import javax.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import javax.persistence.NamedQueries; import jakarta.persistence.NamedQueries;
import javax.persistence.NamedQuery; import jakarta.persistence.NamedQuery;
import javax.persistence.OneToMany; import jakarta.persistence.OneToMany;
import javax.persistence.Table; import jakarta.persistence.Table;
/** /**
* The persistent class for the "shopcats" database table. * The persistent class for the "shopcats" database table.

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -1,7 +1,7 @@
package com.mousetech.gourmetj.persistence.model; package com.mousetech.gourmetj.persistence.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.*; import jakarta.persistence.*;
/** /**

@ -2,9 +2,9 @@ package com.mousetech.gourmetj.persistence.service;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import jakarta.inject.Inject;
import javax.inject.Named; import jakarta.inject.Named;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

@ -4,9 +4,9 @@ import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import jakarta.inject.Inject;
import javax.inject.Named; import jakarta.inject.Named;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

@ -17,9 +17,9 @@ import java.io.InputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import javax.servlet.http.Part; import jakarta.servlet.http.Part;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -180,7 +180,7 @@ public class PictureController {
/** /**
* Take imageFile input and store it as image for recipe. * Take imageFile input and store it as image for recipe.
* Generate thumnail * Generate thumbnail
* *
* @param recipe Recipe to store into. * @param recipe Recipe to store into.
* @param bs Info about uploaded data. * @param bs Info about uploaded data.

@ -6,13 +6,13 @@
*/ */
package com.mousetech.gourmetj.utils; package com.mousetech.gourmetj.utils;
import javax.faces.application.FacesMessage; import jakarta.faces.application.FacesMessage;
import javax.faces.component.UIComponent; import jakarta.faces.component.UIComponent;
import javax.faces.context.FacesContext; import jakarta.faces.context.FacesContext;
import javax.faces.convert.Converter; import jakarta.faces.convert.Converter;
import javax.faces.convert.ConverterException; import jakarta.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter; import jakarta.faces.convert.FacesConverter;
import javax.faces.validator.ValidatorException; import jakarta.faces.validator.ValidatorException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -48,6 +48,9 @@ public class TimeConverter implements Converter<Integer> {
@Override @Override
public String getAsString(FacesContext context, public String getAsString(FacesContext context,
UIComponent component, Integer value) { UIComponent component, Integer value) {
if ( value == null ) {
return "--";
}
return TimeFormatter.formatTime(Long.valueOf(value)); return TimeFormatter.formatTime(Long.valueOf(value));
} }
} }

@ -1,18 +1,22 @@
package com.mousetech.gourmetj.utils; package com.mousetech.gourmetj.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.primefaces.model.ByteArrayContent; import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent; import org.primefaces.model.StreamedContent;
import org.primefaces.util.SerializableSupplier;
import com.mousetech.gourmetj.ShopIngredient; import com.mousetech.gourmetj.ShopIngredient;
/** /**
* Construct a Primefaces file output content for an ingredient * Construct a Primefaces file output content for an ingredient
* list in YAML format. * list in YAML format.
* @see ShoppingListBean
* *
* @author timh * @author timh
* @since Jan 15, 2022 * @since Jan 15, 2022
@ -20,18 +24,30 @@ import com.mousetech.gourmetj.ShopIngredient;
public class YamlShoppingList { public class YamlShoppingList {
/**
* Create "file" to download in YAML format
* @param ingredientList Ingredient list to format
* @return Http response for file download
*/
public static StreamedContent createDownload( public static StreamedContent createDownload(
List<ShopIngredient> ingredientList) { List<ShopIngredient> ingredientList) {
StreamedContent file;
ByteArrayOutputStream ary = new ByteArrayOutputStream(); ByteArrayOutputStream ary = new ByteArrayOutputStream();
PrintWriter wtr = new PrintWriter(ary); PrintWriter wtr = new PrintWriter(ary);
wtr.println("---"); wtr.println("---");
formatContent(wtr, ingredientList); formatContent(wtr, ingredientList);
wtr.close(); wtr.close();
byte[] bas = ary.toByteArray();
StreamedContent dlList = new ByteArrayContent(bas, InputStream airy = new ByteArrayInputStream(ary.toByteArray());
"text/text", "shopping_list.yml"); file = DefaultStreamedContent.
return dlList; builder().contentEncoding("text/text")
.name("shopping_list.yml")
.stream(() -> airy)
.build();
return file;
} }
/** /**

@ -6,10 +6,9 @@
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui" xmlns:p="http://primefaces.org/ui"
> >
<head></head> <h:head></h:head>
<body> <h:body>
<ui:composition> <ui:composition>
<f:view>
<h:head> <h:head>
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title> <title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
<link rel="icon" type="image/vnd.microsoft.icon" <link rel="icon" type="image/vnd.microsoft.icon"
@ -29,50 +28,14 @@
</ui:insert> </ui:insert>
<!-- --> <!-- -->
<div id="footer"> <div id="footer">
(C) 2021 Tim Holloway, Licensed under the <a (C) 2021, 2024 Tim Holloway, Licensed under the <a
href="http://www.apache.org/licenses/LICENSE-2.0" href="http://www.apache.org/licenses/LICENSE-2.0"
>Apache License, Version 2.0</a>. >Apache License, Version 2.0</a>.
<p>Based on Gourmet Recipe Manager by T. <p>Based on Gourmet Recipe Manager by T.
Hinkle</p> Hinkle</p>
</div> </div>
<!-- --> <!-- -->
<h:form id="frmTimeout">
<p:idleMonitor
timeout="#{userSession.sessionTimeoutInterval}"
onidle="PF('dlgSessionExpired').show()"
>
<p:ajax event="idle"
listener="#{userSession.sessionIdleListener}"
/>
</p:idleMonitor>
<p:confirmDialog closable="false"
id="sessionExpiredDlg"
message="Your session has expired."
header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
severity="alert"
widgetVar="dlgSessionExpired"
style="z-index: 25000"
>
<p:commandButton id="cmdExpiredOK"
value="OK" action="/main.jsf"
oncomplete="PF('dlgSessionExpired').hide()"
/>
</p:confirmDialog>
</h:form>
<!-- -->
<h:form id="frmOpErr">
<p:confirmDialog
message="Session may have expired."
header="Error"
severity="alert" widgetVar="opError"
>
<p:commandButton value="OK"
oncomplete="PF('opError').hide()"
/>
</p:confirmDialog>
</h:form>
</h:body> </h:body>
</f:view>
</ui:composition> </ui:composition>
</body> </h:body>
</html> </html>

@ -8,23 +8,13 @@
> >
<!-- === Edit ingkey/shopcat === --> <!-- === Edit ingkey/shopcat === -->
<h:form id="frmIsk"> <h:form id="frmIsk">
<p:panelGrid> <p:panelGrid columns="2" style="width: 30em;">
<p:row> <f:facet name="header">
<p:column> <h:outputText value="Shopping Categories"/>
<p:outputLabel for="ctlScSel" </f:facet>
value="Shopping Category" <h:outputText value="Shopping Category"/>
/> <h:outputText value="Ingredient key"/>
</p:column>
<p:column>
<p:outputLabel for="ctlIngkeySel"
value="Ingredient Key"
/>
</p:column>
</p:row>
<p:row style="vertical-align: top">
<p:column>
<p:selectOneListbox id="ctlScSel" <p:selectOneListbox id="ctlScSel"
style="width: 240px"
value="#{shoppingListBean.selectedShopcat}" value="#{shoppingListBean.selectedShopcat}"
> >
<f:selectItems <f:selectItems
@ -32,10 +22,8 @@
/> />
<p:ajax update="ctlIngkeySel" event="change" /> <p:ajax update="ctlIngkeySel" event="change" />
</p:selectOneListbox> </p:selectOneListbox>
</p:column>
<p:column>
<h:selectManyListbox id="ctlIngkeySel" <h:selectManyListbox id="ctlIngkeySel"
style="width: 240px" style="width: 12em;"
value="#{shoppingListBean.selectedIngkey}" value="#{shoppingListBean.selectedIngkey}"
label="Ingcat" label="Ingcat"
> >
@ -44,34 +32,21 @@
/> />
<p:ajax event="change" update="ctlChangeCat"/> <p:ajax event="change" update="ctlChangeCat"/>
</h:selectManyListbox> </h:selectManyListbox>
</p:column>
</p:row>
<p:row>
<p:column>
<p:outputLabel <p:outputLabel
value="Change shopping category to:" value="Change selected ingredient key category to:"
style="width: 10em;"
/> />
</p:column>
<p:column>
<p:autoComplete <p:autoComplete
value="#{shoppingListBean.newShopcat}" value="#{shoppingListBean.newShopcat}"
autoSelection="false" forceSelection="false" autoSelection="false" forceSelection="false"
maxResults="12" maxResults="12"
completeMethod="#{shoppingListBean.suggestShopcat}" completeMethod="#{shoppingListBean.suggestShopcat}"
/> />
</p:column>
</p:row>
<p:row>
<p:column>
<p:commandButton id="ctlChangeCat" value="Change..." <p:commandButton id="ctlChangeCat" value="Change..."
disabled="#{empty shoppingListBean.selectedIngkey}" disabled="#{empty shoppingListBean.selectedIngkey}"
onclick="PF('dlgOkRecat').show()" onclick="PF('dlgOkRecat').show()"
/> />
</p:column>
<p:column>
<h:outputText value="" /> <h:outputText value="" />
</p:column>
</p:row>
</p:panelGrid> </p:panelGrid>
</h:form> </h:form>
<!-- --> <!-- -->

@ -14,6 +14,15 @@
<ui:define name="content"> <ui:define name="content">
<h:outputScript name="js/scrolltable.js" /> <h:outputScript name="js/scrolltable.js" />
<style> <style>
.deDescl {
width: 15em;
text-align: left;
}
.deDescr {
text-align: left;
}
.ingSel { .ingSel {
width: 3em; width: 3em;
text-align: center; text-align: center;
@ -46,7 +55,9 @@
<p:tab id="overviewTab" <p:tab id="overviewTab"
title="Description" title="Description"
> >
<p:panelGrid columns="2"> <p:panelGrid columns="2"
columnClasses="deDescl, deDescr"
>
<f:facet name="header">Description</f:facet> <f:facet name="header">Description</f:facet>
<p:outputLabel for="@next" <p:outputLabel for="@next"
value="Title" value="Title"
@ -141,14 +152,18 @@
rows="10" cols="45" rows="10" cols="45"
value="#{recipeDetailBean.recipe.description}" value="#{recipeDetailBean.recipe.description}"
/> />
<p:panel id="picPanel"> </p:panelGrid>
<img id="bigPix" <p:panel id="picPanel">
src="/img/picture/?dt=#{recipeDetailBean.currentTime}" <img id="bigPix"
/> src="/img/picture/?dt=#{recipeDetailBean.currentTime}"
</p:panel> />
</p:panel>
<p:panelGrid id="picButtonPanel"
columns="2"
>
<p:fileUpload id="ctlUpload" <p:fileUpload id="ctlUpload"
label="Upload Image" label="Upload Image"
fileUploadListener="#{recipeDetailBean.ajaxUploadImage}" listener="#{recipeDetailBean.ajaxUploadImage}"
global="true" mode="advanced" global="true" mode="advanced"
multiple="false" multiple="false"
update=":messages picPanel" update=":messages picPanel"
@ -158,13 +173,10 @@
/> />
<p:commandButton id="ctlDelImg" <p:commandButton id="ctlDelImg"
value="Delete Image" value="Delete Image"
> action="#{recipeDetailBean.ajaxDeleteImage}"
<f:ajax update="picPanel"
listener="#{recipeDetailBean.ajaxDeleteImage}" immediate="true"
render="picPanel" />
immediate="true"
/>
</p:commandButton>
</p:panelGrid> </p:panelGrid>
</p:tab> </p:tab>
<p:tab id="ingredientsTab" <p:tab id="ingredientsTab"
@ -379,22 +391,36 @@
</p:panel> </p:panel>
</p:tab> </p:tab>
</p:tabView> </p:tabView>
<p:commandButton id="doSave" value="Save" <p:commandButton id="doSave" value="Save" icon="ui-icon-pencil" ajax="false" disabled="{not recipeDetailBean.dirty}" action="#{recipeDetailBean.doSave}" />
icon="ui-icon-pencil" ajax="false"
disabled="{not recipeDetailBean.dirty}"
action="#{recipeDetailBean.doSave}"
/>
<p:commandButton id="doCancel" value="Cancel" <p:commandButton id="doCancel" value="Cancel"
ajax="false" immediate="true" ajax="false" immediate="true"
action="recipeDetails.jsf" action="recipeDetails.jsf"
/> />
<p:commandButton id="doHome" value="Home" <p:commandButton id="doHome" value="Home"
icon="ui-icon-home" icon="ui-icon-home" ajax="false"
ajax="false" immediate="true" immediate="true" action="main.jsf"
action="main.jsf" />
/>
</h:form> </h:form>
</p:panel> </p:panel>
<!-- Note timeouts must be less than
session timeout in application properties-->
<p:growl id="growl" showDetail="true" sticky="true" />
<h:form id="frmTimeout">
<p:idleMonitor timeout="1500000">
<p:ajax id="ajaxIdle" event="idle"
listener="#{cookieBean.sessionIdleListener}"
update="growl"
/>
</p:idleMonitor>
<p:idleMonitor timeout="1900000">
<p:ajax id="ajaxIdle" event="idle"
listener="#{cookieBean.sessionTimeout}"
update="growl"
oncomplete="window.location='#{request.contextPath}/main.jsf'"
/>
</p:idleMonitor>
</h:form>
<!-- -->
<p:dialog id="addGroupDlg" widgetVar="addGroupDlg"> <p:dialog id="addGroupDlg" widgetVar="addGroupDlg">
<h:form id="frmAddGroup"> <h:form id="frmAddGroup">
<p:panelGrid columns="1"> <p:panelGrid columns="1">
@ -424,7 +450,9 @@
<p:dialog id="editShopcatDlg" <p:dialog id="editShopcatDlg"
widgetVar="editShopcatDlg" widgetVar="editShopcatDlg"
> >
<ui:include src="/WEB-INF/layout/dialog/editShopcat.xhtml" /> <ui:include
src="/WEB-INF/layout/dialog/editShopcat.xhtml"
/>
</p:dialog> </p:dialog>
</ui:define> </ui:define>
</ui:composition> </ui:composition>

@ -28,10 +28,10 @@
/> />
<p:outputLabel for="@next" value="Search for " /> <p:outputLabel for="@next" value="Search for " />
<p:selectOneMenu id="ctlSearchType" <p:selectOneMenu id="ctlSearchType"
value="#{userSession.searchType}" value="#{cookieBean.searchType}"
> >
<f:selectItems <f:selectItems
value="#{userSession.searchTypeList}" value="#{appBean.searchTypeList}"
/> />
<p:ajax <p:ajax
listener="#{adminMainBean.resetSuggestions}" listener="#{adminMainBean.resetSuggestions}"
@ -45,12 +45,12 @@
<p:commandButton value="New Recipe" <p:commandButton value="New Recipe"
action="#{adminMainBean.doNewRecipe}" action="#{adminMainBean.doNewRecipe}"
/> />
<p:commandButton value="More..." <p:commandButton value="Shopping..."
action="#{adminMainBean.doMore}" action="#{adminMainBean.doMore}"
/> />
<h:outputText id="slistSSize" <h:outputText id="slistSSize"
style="margin-left: 2em" style="margin-left: 2em"
value="#{userSession.shoppingList.size()}" value="#{cookieBean.displayListSize}"
/> />
<h:outputLabel for="slistSize" <h:outputLabel for="slistSize"
value=" Recipes in Shopping List" value=" Recipes in Shopping List"

@ -27,179 +27,166 @@
<h:messages /> <h:messages />
<h:form id="form1"> <h:form id="form1">
<p:panelGrid label="#{recipeDetailBean.recipe.title}" <p:panelGrid label="#{recipeDetailBean.recipe.title}"
columns="1" columns="2"
> >
<p:panelGrid> <f:facet name="header">
<f:facet name="header"> <h:outputText styleClass="recipeTitle"
<h:outputText styleClass="recipeTitle" value="#{recipeDetailBean.recipe.title}"
value="#{recipeDetailBean.recipe.title}" />
/> </f:facet>
</f:facet> <p:panel id="leftCol" style="width: auto;">
<p:row> <p:panelGrid columns="2">
<p:column id="leftCol" style="width: 75%"> <p:panelGrid id="picButtons" columns="2">
<p:panelGrid columns="2"> <img id="bigpix"
<img id="bigpix" style="width: 132px;"
src="/img/picture/#{recipeDetailBean.recipe.id}" src="/img/picture/#{recipeDetailBean.recipe.id}"
/>
<p:panelGrid id="pnlDetails"
columns="2"
>
<!-- TODO: ask if we should save -->
<p:commandButton value="Back"
ajax="false"
icon="ui-icon-arrowthick-1-w"
action="home"
immediate="true"
/>
<p:commandButton ajax="false"
value="Print"
icon="ui-icon-print"
action="recipePrint.jsf"
styleClass="ui-button-print"
immediate="true"
/>
<p:commandButton id="ctlShop"
icon="ui-icon-cart"
value="Shop"
immediate="true"
styleClass="#{recipeDetailBean.shop ? 'greenButton' : null}"
action="#{recipeDetailBean.doShop}"
update="ctlShop"
/>
<h:outputText value=""/>
<p:outputLabel for="@next"
value="Categories:"
/>
<h:outputText
label="Category: "
value="#{userSession.formatCategories(recipeDetailBean.recipe)}"
/>
<p:outputLabel for="@next"
value="Cuisine:"
/>
<h:outputText
label="Cuisine: "
value="#{recipeDetailBean.recipe.cuisine}"
/>
<p:outputLabel for="@next"
value="Prep Time:"
/>
<h:outputText
label="Prep Time: "
value="#{recipeDetailBean.recipe.preptime}"
converter="com.mousetech.gourmetj.utils.TimeConverter"
/>
<p:outputLabel for="@next"
value="Cook Time:"
/>
<h:outputText
label="Cook Time: "
value="#{recipeDetailBean.recipe.cooktime}"
converter="com.mousetech.gourmetj.utils.TimeConverter"
/>
</p:panelGrid>
</p:panelGrid>
<h:commandLink value="Edit Details"
action="#{recipeDetailBean.editDescription}"
/> />
<!-- --> <p:panelGrid id="pnlButtons"
<p:panelGrid id="pnlInstr" columns="2" style="width: 220px;"
columns="1" style="width: 100%"
> >
<f:facet name="header"> <!-- TODO: ask if we should save -->
<h:outputText <p:commandButton value="Back"
styleClass="subtitle" ajax="false"
value="Instructions" icon="ui-icon-arrowthick-1-w"
/> action="home"
</f:facet> immediate="true"
<h:outputText id="instructions"
escape="false"
value="#{recipeDetailBean.instructions}"
/> />
<h:commandLink <p:commandButton ajax="false"
value="Edit Instructions" value="Print"
action="#{recipeDetailBean.editInstructions}" icon="ui-icon-print"
action="recipePrint.jsf"
styleClass="ui-button-print"
immediate="true"
/> />
</p:panelGrid> <p:commandButton id="ctlShop"
<p:panelGrid id="pnlNotes" icon="ui-icon-cart"
columns="1" style="width: 100%" value="Shop" immediate="true"
> styleClass="#{recipeDetailBean.shop ? 'greenButton' : null}"
<f:facet name="header"> action="#{recipeDetailBean.doShop}"
<h:outputText update="ctlShop"
styleClass="subtitle" />
value="Notes" <h:outputText value="" />
/> <p:outputLabel for="@next"
</f:facet> value="Categories:"
<h:outputText escape="false"
value="#{recipeDetailBean.modifications}"
/> />
<h:commandLink value="Edit Notes" <h:outputText label="Category: "
action="#{recipeDetailBean.editNotes}" value="#{userSession.formatCategories(recipeDetailBean.recipe)}"
/>
<p:outputLabel for="@next"
value="Cuisine:"
/>
<h:outputText label="Cuisine: "
value="#{recipeDetailBean.recipe.cuisine}"
/>
<p:outputLabel for="@next"
value="Prep Time:"
/>
<h:outputText label="Prep Time: "
value="#{recipeDetailBean.recipe.preptime}"
converter="com.mousetech.gourmetj.utils.TimeConverter"
/>
<p:outputLabel for="@next"
value="Cook Time:"
/>
<h:outputText label="Cook Time: "
value="#{recipeDetailBean.recipe.cooktime}"
converter="com.mousetech.gourmetj.utils.TimeConverter"
/>
<h:outputText value="" />
<p:commandButton
icon="ui-icon-wrench"
value="Edit"
action="#{recipeDetailBean.editDescription}"
/> />
</p:panelGrid> </p:panelGrid>
<p:commandButton id="ctlDelete" </p:panelGrid>
value="Delete Recipe" </p:panelGrid>
onclick="PF('okDeleteDlg').show()" <!-- -->
<p:panel id="pnlInstr">
<f:facet name="header">
<h:outputText styleClass="subtitle"
value="Instructions"
/> />
</p:column> </f:facet>
<!-- ====== Ingredients ============================ --> <h:outputText id="instructions"
<p:column id="ingredientsc" escape="false"
style="width: 25%; vertical-align: top;" value="#{recipeDetailBean.instructions}"
/>
<br />
<h:commandLink value="Edit Instructions"
action="#{recipeDetailBean.editInstructions}"
/>
</p:panel>
<p:panel id="pnlNotes">
<f:facet name="header">
<h:outputText styleClass="subtitle"
value="Notes"
/>
</f:facet>
<h:outputText escape="false"
value="#{recipeDetailBean.modifications}"
/>
<br />
<h:commandLink value="Edit Notes"
action="#{recipeDetailBean.editNotes}"
/>
</p:panel>
<p:commandButton id="ctlDelete"
value="Delete Recipe"
onclick="PF('okDeleteDlg').show()"
/>
</p:panel>
<!-- ====== Ingredients ============================ -->
<p:panel id="ingredientsc"
style="width: 30%; min-width: 40em; vertical-align: top;"
>
<p:dataTable id="ingredients"
value="#{recipeDetailBean.ingredients}"
var="item"
rowStyleClass="#{item.ingGroup ? 'displayIngGroupRow' : null}"
>
<f:facet name="header">
<h:outputText styleClass="subtitle"
value="Ingredients"
/>
</f:facet>
<f:facet name="footer">
<h:commandLink
value="Edit Ingredients"
style="vertical-align: top"
action="#{recipeDetailBean.editIngredients}"
/>
</f:facet>
<p:column label="Amt"
style="width: 3em; text-align: right"
> >
<p:dataTable id="ingredients"
value="#{recipeDetailBean.ingredients}"
var="item"
rowStyleClass="#{item.ingGroup ? 'displayIngGroupRow' : null}"
>
<f:facet name="header">
<h:outputText
styleClass="subtitle"
value="Ingredients"
/>
</f:facet>
<f:facet name="footer">
<h:commandLink
value="Edit Ingredients"
style="vertical-align: top"
action="#{recipeDetailBean.editIngredients}"
/>
</f:facet>
<p:column label="Amt"
style="width: 3em; text-align: right"
>
<h:outputText
value="#{item.displayAmount}"
/>
</p:column>
<p:column label="Units"
style="width: 5em"
>
<h:outputText
value="#{item.unit}"
/>
</p:column>
<p:column label="Item"
style="width: 20em"
>
<h:outputText
value="#{item.item}"
/>
</p:column>
<p:column label="Optional"
style="width: 2em"
>
<p:selectBooleanCheckbox
readonly="true"
value="#{item.optionalCB}"
/>
</p:column>
</p:dataTable>
<h:outputText <h:outputText
value="Recipe ID: #{recipeDetailBean.recipe.id}" value="#{item.displayAmount}"
/>
</p:column>
<p:column label="Units"
style="width: 5em"
>
<h:outputText value="#{item.unit}" />
</p:column>
<p:column label="Item"
style="width: 20em; text-wrap: wrap"
>
<h:outputText value="#{item.item}" />
</p:column>
<p:column label="Optional"
style="width: 2em"
>
<p:selectBooleanCheckbox
disabled="true"
value="#{item.optionalCB}"
/> />
</p:column> </p:column>
</p:row> </p:dataTable>
</p:panelGrid> <h:outputText
value="Recipe ID: #{recipeDetailBean.recipe.id}"
/>
</p:panel>
</p:panelGrid> </p:panelGrid>
</h:form> </h:form>
<!-- --> <!-- -->

@ -6,7 +6,7 @@
xmlns:p="http://primefaces.org/ui" xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jstl" xmlns:c="http://xmlns.jcp.org/jstl"
> >
<!-- Tabbed page for the Mainpage "More..." button --> <!-- Tabbed page for the Mainpage "Shopping..." button -->
<ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define> <ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define>
<ui:define name="content"> <ui:define name="content">
<style> <style>
@ -34,6 +34,7 @@
} }
</style> </style>
This is the list of recipe items you've selected to shop for.
<h:messages /> <h:messages />
<p:tabView id="tabGroupClient" orientation="left" <p:tabView id="tabGroupClient" orientation="left"
dynamic="true" dynamic="true"
@ -41,12 +42,12 @@
<p:tab id="overviewTab" title="Shopping List"> <p:tab id="overviewTab" title="Shopping List">
<h:form id="form1"> <h:form id="form1">
<p:dataTable id="tblRecipes" <p:dataTable id="tblRecipes"
style="width: 600px" style="width: 40em"
value="#{shoppingListBean.recipeList}" value="#{shoppingListBean.recipeList}"
var="item" var="item"
> >
<f:facet name="header"> <f:facet name="header">
<h:outputText value="Recipes" /> <h:outputText value="Recipes " />
<p:commandButton <p:commandButton
update="@parent:tblRecipes" update="@parent:tblRecipes"
value="Clear Recipes" value="Clear Recipes"
@ -82,11 +83,11 @@
</p:commandButton> </p:commandButton>
</p:column> </p:column>
<p:column id="ingredientsc" <p:column id="ingredientsc"
style="width: 25%; vertical-align: top;" style="vertical-align: top; margin-left: 1em; margin-right: 1em;"
> >
<p:dataTable id="tblShopIngredients" <p:dataTable id="tblShopIngredients"
style="width: 600px; margin-top: 10px"
value="#{shoppingListBean.ingredientList}" value="#{shoppingListBean.ingredientList}"
style="width: 40em;"
sortBy="#{item.shopCat}" var="item" sortBy="#{item.shopCat}" var="item"
> >
<f:facet name="header"> <f:facet name="header">
@ -95,13 +96,11 @@
value="Ingredients" value="Ingredients"
/> />
</f:facet> </f:facet>
<p:headerRow>
<p:column colspan="4"> <p:column colspan="4">
<h:outputText <h:outputText
value="#{item.shopCat}" value="#{item.shopCat}"
/> />
</p:column> </p:column>
</p:headerRow>
<p:column label="Amt" <p:column label="Amt"
style="width: 3em; text-align: right" style="width: 3em; text-align: right"
> >
@ -124,7 +123,7 @@
styleClass="#{(item.inPantry) ? 'noRecipe' :'plusRecipe' }" styleClass="#{(item.inPantry) ? 'noRecipe' :'plusRecipe' }"
/> />
</p:column> </p:column>
<p:column style="width: 2em"> <p:column style="width: 3em">
<f:facet name="header"> <f:facet name="header">
<h:outputText value="Pantry" /> <h:outputText value="Pantry" />
</f:facet> </f:facet>
@ -150,6 +149,7 @@
</p:tab> </p:tab>
<!-- --> <!-- -->
<p:tab id="tabPantry" title="Pantry"> <p:tab id="tabPantry" title="Pantry">
<h:outputText value="Stuff already in the pantry." />
<h:outputText value="For future implementation" /> <h:outputText value="For future implementation" />
</p:tab> </p:tab>
<!-- --> <!-- -->
@ -160,7 +160,7 @@
<h:form id="frmHome"> <h:form id="frmHome">
<p:commandButton id="doHome" value="Home" <p:commandButton id="doHome" value="Home"
icon="ui-icon-home" ajax="false" immediate="true" icon="ui-icon-home" ajax="false" immediate="true"
action="main.jsf" action="/main.jsf?redirect=true"
/> />
</h:form> </h:form>
</ui:define> </ui:define>

@ -13,20 +13,28 @@ spring:
#url: jdbc:sqlite:${home}/recipes.db #url: jdbc:sqlite:${home}/recipes.db
url: jdbc:mysql:dbase/recipes url: jdbc:mysql:dbase/recipes
# options: ${env} values # options: ${env} values
driverClassName: org.mysql.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
username: recipes username: recipes
password: yumyumyum password: yumyumyum
jpa: jpa:
hibernate: hibernate:
ddl-auto: none ddl-auto: none
#database-platform: org.sqlite.hibernate.dialect.SQLiteDialect
database-platform: org.hibernate.dialect.MySQLDialect database-platform: org.hibernate.dialect.MySQLDialect
server: server:
servlet: servlet:
session: session:
timeout: '30m' timeout: '30m'
# Theme here overrides joinfaces theme
# context-parameters:
# primefaces:
# THEME: vela
gourmet: gourmet:
password: password:
file: .gourmetpw file: .gourmetpw
joinfaces:
primefaces:
theme: casablanca

Loading…
Cancel
Save