Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

copied bookmarks sample out of project tree

git-svn-id: https://svn.codehaus.org/grails/trunk@5105 1cfb16fd-6d17-0410-8ff1-b7e8e1e2867d
  • Loading branch information...
commit 3e0d92241d94ea8d382aee09d0f766a7064c8c7b 0 parents
graeme authored
Showing with 21,747 additions and 0 deletions.
  1. +66 −0 .classpath
  2. +23 −0 .project
  3. +3 −0  .settings/org.codehaus.groovy.eclipse.preferences.prefs
  4. +64 −0 .settings/org.eclipse.jdt.core.prefs
  5. +21 −0 README.txt
  6. +20 −0 bookmarks.launch
  7. +8 −0 grails-app/conf/ApplicationBootStrap.groovy
  8. +12 −0 grails-app/conf/ApplicationDataSource.groovy
  9. +9 −0 grails-app/conf/BookmarksUrlMappings.groovy
  10. +10 −0 grails-app/conf/DevelopmentDataSource.groovy
  11. +10 −0 grails-app/conf/ProductionDataSource.groovy
  12. +10 −0 grails-app/conf/TestDataSource.groovy
  13. +37 −0 grails-app/conf/log4j.development.properties
  14. +37 −0 grails-app/conf/log4j.production.properties
  15. +37 −0 grails-app/conf/log4j.test.properties
  16. +185 −0 grails-app/controllers/BookmarkController.groovy
  17. +11 −0 grails-app/controllers/SecureController.groovy
  18. +19 −0 grails-app/controllers/SubscriptionController.groovy
  19. +73 −0 grails-app/controllers/TagController.groovy
  20. +36 −0 grails-app/controllers/UserController.groovy
  21. +20 −0 grails-app/i18n/messages.properties
  22. +60 −0 grails-app/jobs/SubscriptionJob.groovy
  23. +34 −0 grails-app/services/BookmarkService.groovy
  24. +53 −0 grails-app/services/DeliciousService.groovy
  25. +30 −0 grails-app/taglib/BookmarkTagLib.groovy
  26. +25 −0 grails-app/views/bookmark/_bookmark.gsp
  27. +16 −0 grails-app/views/bookmark/_editor.gsp
  28. +7 −0 grails-app/views/bookmark/_suggest.gsp
  29. +37 −0 grails-app/views/bookmark/create.gsp
  30. +10 −0 grails-app/views/bookmark/edit.gsp
  31. +36 −0 grails-app/views/bookmark/list.gsp
  32. +1 −0  grails-app/views/bookmark/show.gsp
  33. +46 −0 grails-app/views/error.gsp
  34. +88 −0 grails-app/views/layouts/main.gsp
  35. +3 −0  grails-app/views/subscription/_subscription.gsp
  36. +32 −0 grails-app/views/subscription/list.gsp
  37. +44 −0 grails-app/views/tag/create.gsp
  38. +51 −0 grails-app/views/tag/edit.gsp
  39. +50 −0 grails-app/views/tag/list.gsp
  40. +56 −0 grails-app/views/tag/show.gsp
  41. +35 −0 grails-app/views/user/login.gsp
  42. +57 −0 grails-app/views/user/register.gsp
  43. +180 −0 grails-tests/BookmarkTests.groovy
  44. +20 −0 grails-tests/DeliciousTests.groovy
  45. +59 −0 grails-tests/SubscriptionTests.groovy
  46. +15 −0 hibernate/hibernate.cfg.xml
  47. BIN  lib/acegi-security-1.0.1.jar
  48. BIN  lib/acegi-security-jetty-1.0.1.jar
  49. BIN  lib/acegi-security-tiger-1.0.1.jar
  50. BIN  lib/activation.jar
  51. BIN  lib/commons-codec-1.3.jar
  52. BIN  lib/commons-email-1.0.jar
  53. BIN  lib/commons-httpclient-3.0.1.jar
  54. BIN  lib/jaxen-1.1-beta-9.jar
  55. BIN  lib/jdom-1.0.jar
  56. BIN  lib/mail.jar
  57. BIN  lib/stax-api-1.0.1.jar
  58. BIN  lib/wsdl4j-1.5.2.jar
  59. BIN  lib/wss4j-1.5.0.jar
  60. BIN  lib/wstx-asl-3.0.1.jar
  61. BIN  lib/xfire-all-1.2.2.jar
  62. BIN  lib/xstream-1.2.jar
  63. +150 −0 plugins/core/grails-app/taglib/ApplicationTagLib.groovy
  64. +602 −0 plugins/core/grails-app/taglib/FormTagLib.groovy
  65. +544 −0 plugins/core/grails-app/taglib/JavascriptTagLib.groovy
  66. +377 −0 plugins/core/grails-app/taglib/RenderTagLib.groovy
  67. +41 −0 plugins/core/grails-app/taglib/TagLibUtil.groovy
  68. +214 −0 plugins/core/grails-app/taglib/UITagLib.groovy
  69. +293 −0 plugins/core/grails-app/taglib/ValidationTagLib.groovy
  70. +11 −0 plugins/core/grails-app/utils/HTMLCodec.groovy
  71. +7 −0 plugins/core/grails-app/utils/JavaScriptCodec.groovy
  72. +22 −0 plugins/core/grails-app/utils/URLCodec.groovy
  73. +179 −0 spring/resources.xml
  74. +14 −0 src/java/org/grails/bookmarks/Bookmark.aegis.xml
  75. +90 −0 src/java/org/grails/bookmarks/Bookmark.java
  76. +8 −0 src/java/org/grails/bookmarks/BookmarkConstraints.groovy
  77. +23 −0 src/java/org/grails/bookmarks/IBookmarkService.java
  78. +44 −0 src/java/org/grails/bookmarks/Subscription.java
  79. +31 −0 src/java/org/grails/bookmarks/Tag.java
  80. +48 −0 src/java/org/grails/bookmarks/TagReference.java
  81. +79 −0 src/java/org/grails/bookmarks/User.java
  82. +128 −0 src/java/org/grails/bookmarks/api/BookmarkXmlController.java
  83. +39 −0 web-app/WEB-INF/applicationContext.xml
  84. +37 −0 web-app/WEB-INF/log4j.properties
  85. +14 −0 web-app/WEB-INF/sitemesh.xml
  86. +13 −0 web-app/WEB-INF/test-applicationContext.xml
  87. +416 −0 web-app/WEB-INF/tld/2.3/c.tld
  88. +442 −0 web-app/WEB-INF/tld/2.3/fmt.tld
  89. +311 −0 web-app/WEB-INF/tld/2.3/spring.tld
  90. +563 −0 web-app/WEB-INF/tld/2.4/c.tld
  91. +671 −0 web-app/WEB-INF/tld/2.4/fmt.tld
  92. +311 −0 web-app/WEB-INF/tld/2.4/spring.tld
  93. +563 −0 web-app/WEB-INF/tld/c.tld
  94. +671 −0 web-app/WEB-INF/tld/fmt.tld
  95. +550 −0 web-app/WEB-INF/tld/grails.tld
  96. +311 −0 web-app/WEB-INF/tld/spring.tld
  97. +113 −0 web-app/WEB-INF/web.template.xml
  98. +192 −0 web-app/WEB-INF/web.xml
  99. +127 −0 web-app/css/main.css
  100. +58 −0 web-app/css/tree/check/tree.css
  101. +97 −0 web-app/css/tree/default/tree.css
  102. +55 −0 web-app/css/tree/folders/tree.css
  103. +58 −0 web-app/css/tree/menu/tree.css
  104. BIN  web-app/images/grails_logo.jpg
  105. BIN  web-app/images/spinner.gif
  106. BIN  web-app/images/tree/bullet.gif
  107. BIN  web-app/images/tree/check/check0.gif
  108. BIN  web-app/images/tree/check/check1.gif
  109. BIN  web-app/images/tree/check/check2.gif
  110. BIN  web-app/images/tree/check/lm.gif
  111. BIN  web-app/images/tree/check/lmh.gif
  112. BIN  web-app/images/tree/check/ln.gif
  113. BIN  web-app/images/tree/check/loading.gif
  114. BIN  web-app/images/tree/check/lp.gif
  115. BIN  web-app/images/tree/check/lph.gif
  116. BIN  web-app/images/tree/check/tm.gif
  117. BIN  web-app/images/tree/check/tmh.gif
  118. BIN  web-app/images/tree/check/tn.gif
  119. BIN  web-app/images/tree/check/tp.gif
  120. BIN  web-app/images/tree/check/tph.gif
  121. BIN  web-app/images/tree/check/vline.gif
  122. BIN  web-app/images/tree/default/lm.gif
  123. BIN  web-app/images/tree/default/lmh.gif
  124. BIN  web-app/images/tree/default/ln.gif
  125. BIN  web-app/images/tree/default/loading.gif
  126. BIN  web-app/images/tree/default/lp.gif
  127. BIN  web-app/images/tree/default/lph.gif
  128. BIN  web-app/images/tree/default/tm.gif
  129. BIN  web-app/images/tree/default/tmh.gif
  130. BIN  web-app/images/tree/default/tn.gif
  131. BIN  web-app/images/tree/default/tp.gif
  132. BIN  web-app/images/tree/default/tph.gif
  133. BIN  web-app/images/tree/default/vline.gif
  134. BIN  web-app/images/tree/folders/lm.gif
  135. BIN  web-app/images/tree/folders/lmh.gif
  136. BIN  web-app/images/tree/folders/ln.gif
  137. BIN  web-app/images/tree/folders/loading.gif
  138. BIN  web-app/images/tree/folders/lp.gif
  139. BIN  web-app/images/tree/folders/lph.gif
  140. BIN  web-app/images/tree/folders/tm.gif
  141. BIN  web-app/images/tree/folders/tmh.gif
  142. BIN  web-app/images/tree/folders/tn.gif
  143. BIN  web-app/images/tree/folders/tp.gif
  144. BIN  web-app/images/tree/folders/tph.gif
  145. BIN  web-app/images/tree/folders/vline.gif
  146. BIN  web-app/images/tree/greybg.png
  147. BIN  web-app/images/tree/header.gif
  148. BIN  web-app/images/tree/logo.gif
  149. BIN  web-app/images/tree/menu/collapse.gif
  150. BIN  web-app/images/tree/menu/collapseh.gif
  151. BIN  web-app/images/tree/menu/collapseon.gif
  152. BIN  web-app/images/tree/menu/dash.gif
  153. BIN  web-app/images/tree/menu/expand.gif
  154. BIN  web-app/images/tree/menu/expandh.gif
  155. BIN  web-app/images/tree/menu/expandon.gif
  156. BIN  web-app/images/tree/menu/loading.gif
  157. BIN  web-app/images/tree/navHover2.png
  158. BIN  web-app/images/tree/qbottom.png
  159. BIN  web-app/images/tree/qmiddle.png
  160. BIN  web-app/images/tree/qtop.png
  161. +3 −0  web-app/index.jsp
  162. +13 −0 web-app/js/application.js
  163. +7 −0 web-app/js/prototype/animation.js
  164. +101 −0 web-app/js/prototype/builder.js
  165. +815 −0 web-app/js/prototype/controls.js
  166. +915 −0 web-app/js/prototype/dragdrop.js
  167. +958 −0 web-app/js/prototype/effects.js
  168. +2,006 −0 web-app/js/prototype/prototype.js
  169. +2,691 −0 web-app/js/prototype/rico.js
  170. +47 −0 web-app/js/prototype/scriptaculous.js
  171. +283 −0 web-app/js/prototype/slider.js
  172. +383 −0 web-app/js/prototype/unittest.js
  173. +7 −0 web-app/js/yahoo/animation-min.js
  174. BIN  web-app/js/yahoo/assets/Thumbs.db
  175. BIN  web-app/js/yahoo/assets/alrt16_1.gif
  176. BIN  web-app/js/yahoo/assets/blck16_1.gif
  177. +163 −0 web-app/js/yahoo/assets/calendar.css
  178. BIN  web-app/js/yahoo/assets/callt.gif
  179. BIN  web-app/js/yahoo/assets/calrt.gif
  180. BIN  web-app/js/yahoo/assets/calx.gif
  181. BIN  web-app/js/yahoo/assets/close12_1.gif
  182. +206 −0 web-app/js/yahoo/assets/container.css
  183. BIN  web-app/js/yahoo/assets/hlp16_1.gif
  184. BIN  web-app/js/yahoo/assets/info16_1.gif
  185. BIN  web-app/js/yahoo/assets/lm.gif
  186. BIN  web-app/js/yahoo/assets/lmh.gif
  187. BIN  web-app/js/yahoo/assets/ln.gif
  188. BIN  web-app/js/yahoo/assets/loading.gif
  189. BIN  web-app/js/yahoo/assets/lp.gif
  190. BIN  web-app/js/yahoo/assets/lph.gif
  191. +264 −0 web-app/js/yahoo/assets/menu.css
  192. BIN  web-app/js/yahoo/assets/menuarodwn9_clk_1.gif
  193. BIN  web-app/js/yahoo/assets/menuarodwn9_dim_1.gif
  194. BIN  web-app/js/yahoo/assets/menuarodwn9_nrm_1.gif
  195. BIN  web-app/js/yahoo/assets/menuarorght9_dim_1.gif
  196. BIN  web-app/js/yahoo/assets/menuarorght9_hov_1.gif
  197. BIN  web-app/js/yahoo/assets/menuarorght9_nrm_1.gif
  198. BIN  web-app/js/yahoo/assets/tip16_1.gif
  199. BIN  web-app/js/yahoo/assets/tm.gif
  200. BIN  web-app/js/yahoo/assets/tmh.gif
  201. BIN  web-app/js/yahoo/assets/tn.gif
  202. BIN  web-app/js/yahoo/assets/tp.gif
  203. BIN  web-app/js/yahoo/assets/tph.gif
  204. +98 −0 web-app/js/yahoo/assets/tree.css
  205. BIN  web-app/js/yahoo/assets/vline.gif
  206. BIN  web-app/js/yahoo/assets/warn16_1.gif
  207. +211 −0 web-app/js/yahoo/calendar-min.js
  208. +72 −0 web-app/js/yahoo/connection-min.js
  209. +265 −0 web-app/js/yahoo/container-min.js
  210. +137 −0 web-app/js/yahoo/container_core-min.js
  211. +1 −0  web-app/js/yahoo/dom-min.js
  212. +1 −0  web-app/js/yahoo/dragdrop-min.js
  213. +1 −0  web-app/js/yahoo/event-min.js
  214. +1 −0  web-app/js/yahoo/menu-min.js
  215. +1 −0  web-app/js/yahoo/slider-min.js
  216. +1 −0  web-app/js/yahoo/treeview-min.js
  217. +1 −0  web-app/js/yahoo/yahoo-min.js
  218. +16 −0 webtest/conf/webtest.properties
  219. +629 −0 webtest/reports/WebTestResults.html
  220. +460 −0 webtest/reports/WebTestResults.xml
  221. +5 −0 webtest/reports/readme.txt
  222. +40 −0 webtest/reports/response_1159829438560_invoke.html
  223. +44 −0 webtest/reports/response_1159829441998_clickLink.html
  224. +79 −0 webtest/reports/response_1159829446196_clickButton.html
  225. +55 −0 webtest/reports/response_1159829447832_clickLink.html
  226. +79 −0 webtest/reports/response_1159829449533_clickLink.html
  227. +59 −0 webtest/reports/response_1159829452976_clickButton.html
  228. +79 −0 webtest/reports/response_1159829455395_clickButton.html
  229. +55 −0 webtest/reports/response_1159829457001_clickLink.html
  230. +79 −0 webtest/reports/response_1159829458642_clickLink.html
  231. +44 −0 webtest/reports/response_1159829461056_clickButton.html
  232. +63 −0 webtest/tests/BookmarkWebTest.groovy
  233. +36 −0 webtest/tests/TestSuite.groovy
66 .classpath
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/java"/>
+ <classpathentry kind="src" path="src/groovy"/>
+ <classpathentry kind="src" path="grails-app/conf"/>
+ <classpathentry kind="src" path="grails-app/controllers"/>
+ <classpathentry kind="src" path="grails-app/domain"/>
+ <classpathentry kind="src" path="grails-app/services"/>
+ <classpathentry kind="src" path="grails-app/taglib"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/antlr-2.7.6rc1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-cli-1.0.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-collections-3.0.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-dbcp-1.2.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-pool-1.2.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/dom4j-1.6.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/ehcache-1.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/ejb3-persistence.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/groovy-all-1.0.1-SNAPSHOT.jar"/>
+ <classpathentry sourcepath="/HIBERNATE_HOME/src" kind="var" path="GRAILS_HOME/lib/hibernate3.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/hibernate-annotations.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jasper-compiler.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jasper-runtime.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jdbc2_0-stdext.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jstl-2.3.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jstl-2.4.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jta.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/log4j-1.2.8.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/ognl-2.7.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/org.mortbay.jetty.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/quartz-1.5.2.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/sitemesh-2.2.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/spring-binding.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/spring-mock.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/springmodules-sandbox.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/spring-webflow.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/standard-2.3.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/standard-2.4.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/xml-apis.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/ant/lib/ant.jar"/>
+ <classpathentry sourcepath="/SPRING_HOME/src" kind="var" path="GRAILS_HOME/lib/spring-1.2.8.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/servletapi-2.4.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/oro-2.0.8.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/junit-3.8.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/hsqldb-1.8.0.5.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-validator-1.3.0.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-logging-1.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-lang-2.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-fileupload-1.1.1.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/commons-el-1.0.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/cglib-nodep-2.1_3.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/bsf-2.3.0.jar"/>
+ <classpathentry sourcepath="/GRAILS_HOME/src/web" kind="var" path="GRAILS_HOME/dist/grails-0.3-SNAPSHOT.jar"/>
+ <classpathentry kind="lib" path="lib/mail.jar"/>
+ <classpathentry kind="lib" path="lib/commons-httpclient-3.0.1.jar"/>
+ <classpathentry kind="lib" path="lib/commons-email-1.0.jar"/>
+ <classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
+ <classpathentry kind="lib" path="lib/activation.jar"/>
+ <classpathentry kind="var" path="GRAILS_HOME/lib/jsp-api-2.0.jar"/>
+ <classpathentry kind="lib" path="lib/acegi-security-tiger-1.0.1.jar"/>
+ <classpathentry kind="lib" path="lib/acegi-security-jetty-1.0.1.jar"/>
+ <classpathentry kind="lib" path="lib/acegi-security-1.0.1.jar"/>
+ <classpathentry kind="lib" path="lib/xstream-1.2.jar"/>
+ <classpathentry kind="lib" path="lib/xfire-all-1.2.2.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
23 .project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>bookmarks</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.codehaus.groovy.eclipse.groovyBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.codehaus.groovy.eclipse.groovyNature</nature>
+ </natures>
+</projectDescription>
3  .settings/org.codehaus.groovy.eclipse.preferences.prefs
@@ -0,0 +1,3 @@
+#Wed Oct 04 07:57:39 BST 2006
+eclipse.preferences.version=1
+groovy.compiler.output.path=bin
64 .settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,64 @@
+#Wed Oct 04 15:15:57 BST 2006
+eclipse.preferences.version=1
+org.eclipse.jdt.core.builder.cleanOutputFolder=clean
+org.eclipse.jdt.core.builder.duplicateResourceTask=warning
+org.eclipse.jdt.core.builder.invalidClasspath=abort
+org.eclipse.jdt.core.builder.recreateModifiedClassFileInOutputFolder=ignore
+org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*.launch,*.DS_Store,tmp
+org.eclipse.jdt.core.circularClasspath=error
+org.eclipse.jdt.core.classpath.exclusionPatterns=enabled
+org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled
+org.eclipse.jdt.core.compiler.maxProblemPerUnit=100
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=ignore
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.incompatibleJDKLevel=ignore
+org.eclipse.jdt.core.incompleteClasspath=error
21 README.txt
@@ -0,0 +1,21 @@
+Bookmarks Sample Application
+----------------------------
+
+This is the sample application from the book "The Defintive Guide to Grails" by
+Graeme Rocher (see http://www.apress.com/book/bookDisplay.html?bID=10205)
+
+It shows integration with Java frameworks and services like Xfire, Acegi and Hibernate.
+It also includes del.icio.us integration.
+
+To get started type
+
+grails run-app
+
+And navigate to:
+
+http://localhost:8080/bookmarks
+
+Register with the same user details that you registered with del.icio.us for (if you have an account).
+Then login and start adding bookmarks, and searching del.icio.us.
+
+Enjoy!
20 bookmarks.launch
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="grails.util.GrailsMain"/>
+<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;bookmarks&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/bookmarks/web-app/WEB-INF&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;bookmarks&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="bookmarks"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dbase.dir=${project_loc} -Dserver.port=8080"/>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/bookmarks"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+</launchConfiguration>
+
8 grails-app/conf/ApplicationBootStrap.groovy
@@ -0,0 +1,8 @@
+class ApplicationBootStrap {
+
+ def init = { servletContext ->
+ System.setProperty("javax.xml.transform.TransformerFactory","org.apache.xalan.processor.TransformerFactoryImpl")
+ }
+ def destroy = {
+ }
+}
12 grails-app/conf/ApplicationDataSource.groovy
@@ -0,0 +1,12 @@
+import org.codehaus.groovy.grails.orm.hibernate.cfg.*
+
+class ApplicationDataSource {
+ def configClass = GrailsAnnotationConfiguration.class
+
+ boolean pooling = true
+ String dbCreate = "create-drop" // one of 'create', 'create-drop','update'
+ String url = "jdbc:hsqldb:mem:testDB"
+ String driverClassName = "org.hsqldb.jdbcDriver"
+ String username = "sa"
+ String password = ""
+}
9 grails-app/conf/BookmarksUrlMappings.groovy
@@ -0,0 +1,9 @@
+class BookmarksUrlMappings {
+ static mappings = {
+ "/$controller/$action?/$id?"{
+ constraints {
+ // apply constraints here
+ }
+ }
+ }
+}
10 grails-app/conf/DevelopmentDataSource.groovy
@@ -0,0 +1,10 @@
+import org.codehaus.groovy.grails.orm.hibernate.cfg.*
+class DevelopmentDataSource {
+ def configClass = GrailsAnnotationConfiguration.class
+ boolean pooling = true
+ String dbCreate = "create-drop" // one of 'create', 'create-drop','update'
+ String url = "jdbc:hsqldb:mem:devDB"
+ String driverClassName = "org.hsqldb.jdbcDriver"
+ String username = "sa"
+ String password = ""
+}
10 grails-app/conf/ProductionDataSource.groovy
@@ -0,0 +1,10 @@
+import org.codehaus.groovy.grails.orm.hibernate.cfg.*
+class ProductionDataSource {
+ def configClass = GrailsAnnotationConfiguration.class
+ boolean pooling = true
+ String dbCreate = "update" // one of 'create', 'create-drop','update'
+ String url = "jdbc:hsqldb:file:prodDb"
+ String driverClassName = "org.hsqldb.jdbcDriver"
+ String username = "sa"
+ String password = ""
+}
10 grails-app/conf/TestDataSource.groovy
@@ -0,0 +1,10 @@
+import org.codehaus.groovy.grails.orm.hibernate.cfg.*
+class TestDataSource {
+ def configClass = GrailsAnnotationConfiguration.class
+ boolean pooling = true
+ String dbCreate = "update" // one of 'create', 'create-drop','update'
+ String url = "jdbc:hsqldb:mem:testDB"
+ String driverClassName = "org.hsqldb.jdbcDriver"
+ String username = "sa"
+ String password = ""
+}
37 grails-app/conf/log4j.development.properties
@@ -0,0 +1,37 @@
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+# Enable logging for everything. Rarely useful
+log4j.rootLogger=error, stdout
+
+
+# Enable this logger to log Hibernate output
+# handy to see its database interaction activity
+#log4j.logger.org.hibernate=debug,stdout
+#log4j.additivity.org.hibernate=false
+
+# Enable this logger to see what Spring does, occasionally useful
+#log4j.logger.org.springframework=info,stdout
+#log4j.additivity.org.springframework=false
+
+# This logger covers all of Grails' internals
+# Enable to see whats going on underneath.
+log4j.logger.org.codehaus.groovy.grails=info,stdout
+log4j.additivity.org.codehaus.groovy.grails=false
+
+# This logger is useful if you just want to see what Grails
+# configures with Spring at runtime. Setting to debug will show
+# each bean that is configured
+log4j.logger.org.codehaus.groovy.grails.commons.spring=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.commons.spring=false
+
+# Interesting Logger to see what some of the Grails factory beans are doing
+log4j.logger.org.codehaus.groovy.grails.beans.factory=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.beans.factory=false
+
+# This logger outputs what Grails is doing dynamically under the covers
+log4j.logger.org.codehaus.groovy.grails.commons.metaclass=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.commons.metaclass=false
+
+# This logger is for Grails' public APIs within the grails. package
+log4j.logger.grails=info,stdout
+log4j.additivity.grails=false
37 grails-app/conf/log4j.production.properties
@@ -0,0 +1,37 @@
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+# Enable logging for everything. Rarely useful
+log4j.rootLogger=error, stdout
+
+
+# Enable this logger to log Hibernate output
+# handy to see its database interaction activity
+#log4j.logger.org.hibernate=debug,stdout
+#log4j.additivity.org.hibernate=false
+
+# Enable this logger to see what Spring does, occasionally useful
+#log4j.logger.org.springframework=info,stdout
+#log4j.additivity.org.springframework=false
+
+# This logger covers all of Grails' internals
+# Enable to see whats going on underneath.
+log4j.logger.org.codehaus.groovy.grails=info,stdout
+log4j.additivity.org.codehaus.groovy.grails=false
+
+# This logger is useful if you just want to see what Grails
+# configures with Spring at runtime. Setting to debug will show
+# each bean that is configured
+log4j.logger.org.codehaus.groovy.grails.commons.spring=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.commons.spring=false
+
+# Interesting Logger to see what some of the Grails factory beans are doing
+log4j.logger.org.codehaus.groovy.grails.beans.factory=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.beans.factory=false
+
+# This logger outputs what Grails is doing dynamically under the covers
+log4j.logger.org.codehaus.groovy.grails.commons.metaclass=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.commons.metaclass=false
+
+# This logger is for Grails' public APIs within the grails. package
+log4j.logger.grails=info,stdout
+log4j.additivity.grails=false
37 grails-app/conf/log4j.test.properties
@@ -0,0 +1,37 @@
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+# Enable logging for everything. Rarely useful
+log4j.rootLogger=error, stdout
+
+
+# Enable this logger to log Hibernate output
+# handy to see its database interaction activity
+#log4j.logger.org.hibernate=debug,stdout
+#log4j.additivity.org.hibernate=false
+
+# Enable this logger to see what Spring does, occasionally useful
+#log4j.logger.org.springframework=info,stdout
+#log4j.additivity.org.springframework=false
+
+# This logger covers all of Grails' internals
+# Enable to see whats going on underneath.
+log4j.logger.org.codehaus.groovy.grails=info,stdout
+log4j.additivity.org.codehaus.groovy.grails=false
+
+# This logger is useful if you just want to see what Grails
+# configures with Spring at runtime. Setting to debug will show
+# each bean that is configured
+log4j.logger.org.codehaus.groovy.grails.commons.spring=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.commons.spring=false
+
+# Interesting Logger to see what some of the Grails factory beans are doing
+log4j.logger.org.codehaus.groovy.grails.beans.factory=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.beans.factory=false
+
+# This logger outputs what Grails is doing dynamically under the covers
+log4j.logger.org.codehaus.groovy.grails.commons.metaclass=info,stdout
+log4j.additivity.org.codehaus.groovy.grails.commons.metaclass=false
+
+# This logger is for Grails' public APIs within the grails. package
+log4j.logger.grails=info,stdout
+log4j.additivity.grails=false
185 grails-app/controllers/BookmarkController.groovy
@@ -0,0 +1,185 @@
+import org.grails.bookmarks.*
+
+
+class BookmarkController extends SecureController {
+
+ def scaffold = Bookmark
+
+ DeliciousService deliciousService
+
+ //Map params = [:]
+ //Map flash = [:]
+ //Map session = [:]
+
+ def create = {
+ def b = new Bookmark()
+ b.properties = params
+ def suggestions = b.url ? getSuggestions(b.url, b) : []
+ return [bookmark:b, suggestions:suggestions]
+ }
+ def preview = {
+ def b = Bookmark.get(params.id)
+ render {
+ iframe(src:b.url,width:640,height:480,border:0, 'iframes not supported by browse')
+ }
+ }
+ def edit = {
+ def b = Bookmark.get(params.id)
+ if(b) {
+ return [bookmark:b, suggestions: getSuggestions(b.url,b) ]
+ }
+ else {
+ render "bookmark not found for id ${params.id}"
+ }
+ }
+
+ def delete = {
+ def b = Bookmark.get(params.id)
+ if(b) {
+ b.delete()
+ flash.message = "Bookmark ${b.id} deleted."
+ redirect(action:list)
+ }
+ else {
+ render "Bookmark not found"
+ }
+ }
+
+ def update = {
+ def b = Bookmark.get(params.id)
+ if(b) {
+ b.properties = params
+ addTags(b)
+ if(b.save()) {
+ render(template:'bookmark', model:[bookmark:b])
+ }
+ else {
+ render(view:'edit', model:[bookmark:b])
+ }
+ }
+ else {
+ render "Bookmark not found"
+ }
+ }
+ def save = {
+ def b = new Bookmark()
+ b.properties = params
+ b.user = User.get(session.user.id)
+
+ if(b.save()) {
+ if(params.tagTokens) {
+ addTags(b)
+ b.save()
+ }
+ redirect(action:list,id:b.id)
+ }
+ else {
+ render(view:'create',model:[bookmark:b])
+ }
+ }
+
+ private addTags(Bookmark b) {
+ def tags = params.tagTokens?.trim()
+ if(tags?.indexOf(' ')) {
+ tags = tags.split(' ').toList()
+ tags = tags.unique()
+ tags.each {
+ Tag t = Tag.findByName(it)
+ if(!t)t = new Tag(name:it).save()
+
+ b.addTagReference(new TagReference(bookmark:b, tag:t, user:b.user))
+ }
+ }
+ else {
+ Tag t = Tag.findByName(tags)
+ if(!t)t = new Tag(name:tags).save()
+
+ b.addTagReference( new TagReference(bookmark:b, tag:t, user:b.user) )
+ }
+ }
+
+ def list = {
+ if(!params.max)params.max=10
+ def fromDelicious
+ try {
+ fromDelicious = deliciousService?.findRecent(session.user)
+ }
+ catch(Exception e) {
+ println e.message
+ e.printStackTrace(System.out)
+ //log.error("Error communication with del.icio.us service: ${e.message}",e)
+ }
+
+ [bookmarkList: Bookmark.findAllByUser(User.get(session['user'].id), params ), deliciousList: fromDelicious ]
+ }
+
+ def search = {
+ def criteria = Bookmark.createCriteria()
+ def t = Tag.findByName(params.q)
+ def bookmarks = criteria.list {
+ or {
+ ilike('title',"%${params.q}%".toString())
+ ilike('notes',"%${params.q}%".toString())
+ if(t) {
+ tags {
+ eq('tag', t)
+ }
+ }
+ }
+ }
+ def fromDelicious
+ try {
+ fromDelicious = deliciousService?.findAllForTag(params.q,session.user)
+ }
+ catch(Exception e) {
+ println e.message
+ e.printStackTrace(System.out)
+ //log.error("Error communication with del.icio.us service: ${e.message}",e)
+ }
+
+ render(view:'list',model:[ bookmarkList:bookmarks.adaptee.unique(),
+ deliciousResults:fromDelicious] )
+ }
+
+ def updateNotes = {
+ def b = Bookmark.get(params.id)
+ if(b) {
+ b.notes = params.notes
+ b.save()
+ }
+ render( params.notes )
+ }
+
+ def suggestTag = {
+ if(params.value?.trim()) {
+ if(!params.value?.startsWith("http://")) params.value = "http://${params.value}"
+
+ def bookmark = params.id ? Bookmark.get(params.id) : null
+ try {
+ def url = bookmark ? bookmark.url : new URI(params.value)
+ def tags = getSuggestions(url,bookmark)
+
+ render(template:'suggest',model:[tags:tags,bookmark:bookmark])
+ }
+ catch(Exception e) {
+ log.debug(e.message , e)
+ render ""
+ }
+ }
+ else {
+ render ""
+ }
+ }
+ private getSuggestions(url,bookmark) {
+ def tagRefs = TagReference.findAll('from TagReference where bookmark.url = ?', [url])
+ def tags
+ if(bookmark) {
+ tags = tagRefs.findAll { bookmark.tags?.contains(it.tag) }.tag.unique()
+ }
+ else {
+ tags = tagRefs.tag.unique()
+ }
+ return tags
+ }
+}
+
11 grails-app/controllers/SecureController.groovy
@@ -0,0 +1,11 @@
+import org.grails.bookmarks.*
+
+abstract class SecureController {
+ def beforeInterceptor = {
+ if(!session.user) {
+ session.user = User.findByLogin(request.remoteUser)
+ }
+ }
+
+}
+
19 grails-app/controllers/SubscriptionController.groovy
@@ -0,0 +1,19 @@
+import org.grails.bookmarks.*
+
+class SubscriptionController extends SecureController {
+ def list = {
+ [ subscriptionList: Subscription.findAllByUser( session.user,params ) ]
+ }
+ def delete = {
+ def subscription = Subscription.get( params.id )
+ subscription?.delete()
+ render(template:"subscription", var:"subscription",collection:Subscription.findAllByUser( session.user ))
+ }
+ def save = {
+ def t = Tag.findByName(params.tagName)
+ if(!t)t= new Tag(name:params.tagName).save()
+ if(!Subscription.findByUserAndTag(session.user,t))
+ new Subscription(user:session.user, tag:t).save()
+ render(template:"subscription", var:"subscription",collection:Subscription.findAllByUser( session.user ))
+ }
+}
73 grails-app/controllers/TagController.groovy
@@ -0,0 +1,73 @@
+import org.grails.bookmarks.*
+
+class TagController extends SecureController {
+ def index = { redirect(action:list,params:params) }
+
+ def list = {
+ [ tagList: Tag.list( params ) ]
+ }
+
+ def show = {
+ [ tag : Tag.get( params.id ) ]
+ }
+
+ def delete = {
+ def tag = Tag.get( params.id )
+ if(tag) {
+ tag.delete()
+ flash.message = "Tag ${params.id} deleted."
+ redirect(action:list)
+ }
+ else {
+ flash.message = "Tag not found with id ${params.id}"
+ redirect(action:list)
+ }
+ }
+
+ def edit = {
+ def tag = Tag.get( params.id )
+
+ if(!tag) {
+ flash.message = "Tag not found with id ${params.id}"
+ redirect(action:list)
+ }
+ else {
+ return [ tag : tag ]
+ }
+ }
+
+ def update = {
+ def tag = Tag.get( params.id )
+ if(tag) {
+ tag.properties = params
+ if(tag.save()) {
+ redirect(action:show,id:tag.id)
+ }
+ else {
+ render(view:'edit',model:[tag:tag])
+ }
+ }
+ else {
+ flash.message = "Tag not found with id ${params.id}"
+ redirect(action:edit,id:params.id)
+ }
+ }
+
+ def create = {
+ def tag = new Tag()
+ tag.properties = params
+ return ['tag':tag]
+ }
+
+ def save = {
+ def tag = new Tag()
+ tag.properties = params
+ if(tag.save()) {
+ redirect(action:show,id:tag.id)
+ }
+ else {
+ render(view:'create',model:[tag:tag])
+ }
+ }
+
+}
36 grails-app/controllers/UserController.groovy
@@ -0,0 +1,36 @@
+import org.grails.bookmarks.*
+
+class UserController {
+
+ def accessDenied = {
+ flash.message = "Your account does not have permission to view this page"
+ redirect(controller:"bookmark")
+ }
+
+ def login = {
+ if(session.user) {
+ redirect(controller:'bookmark')
+ }
+ }
+
+ def register = {}
+
+ def handleRegistration = {
+ def user = new User()
+ if(params.password != params.confirm) {
+ flash.message = "The two passwords you entered dont match!"
+ redirect(action:register)
+ }
+ else {
+ user.properties = params
+ if(user.save()) {
+ redirect(controller:'bookmark')
+ }
+ else {
+ flash.user = user
+ redirect(action:register)
+ }
+ }
+ }
+}
+
20 grails-app/i18n/messages.properties
@@ -0,0 +1,20 @@
+default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
+default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
+default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
+default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address.
+default.invalid.message=Property [{0}] of class [{1}] is not a valid [{2}].
+default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
+default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
+default.invalid.length.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid length range from [{3}] to [{4}]
+default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
+default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maxim value [{3}]
+default.invalid.max.length.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maxim length of [{3}]
+default.invalid.min.length.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum length of [{3}]
+default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maxim size of [{3}]
+default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
+default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
+default.blank.message=Property [{0}] of class [{1}] cannot be blank
+default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
+default.null.message=Property [{0}] of class [{1}] cannot be null
+default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
+default.not.editable.message=Property [{0}] of class [{1}] with value [{2}] cannot be changed from [{3}]
60 grails-app/jobs/SubscriptionJob.groovy
@@ -0,0 +1,60 @@
+import org.grails.bookmarks.*
+
+class SubscriptionJob {
+ static final EMAIL_SERVER = "localhost"
+
+ def cronExpression = "0 0 6 ? * FRI"
+
+ def execute() {
+ def criteria = Subscription.createCriteria()
+ def subscribedUsers = criteria.list {
+ projections {
+ distinct("user")
+ }
+ }
+ subscribedUsers.each { user ->
+ def subscribedTags = Subscription.findAllByUser(user).tag
+
+ criteria = Bookmark.createCriteria()
+ def now = new Date()
+ def bookmarks = criteria.list {
+ notEqual("user", user)
+ between("dateCreated",now-7,now)
+ tags {
+ inList("tag", subscribedTags)
+ }
+ }
+ if(bookmarks) {
+ sendMail(user,"Latest Bookmarks") {
+ p("Hi $user!")
+ p("Below are the most recent bookmarks for the tags you're interested in!")
+ for(b in bookmarks) {
+ p {
+ a(href:b.url, b.title)
+ }
+ }
+ p("Enjoy the bookmarks!")
+ p("The Bookmarks Team")
+ }
+ }
+ }
+ }
+
+ def sendMail(User to,String subject,Closure bodyContent) {
+ def sw = new StringWriter()
+ def mkp = new groovy.xml.MarkupBuilder(new PrintWriter(sw))
+ mkp.html {
+ body(bodyContent)
+ }
+
+ def email = new org.apache.commons.mail.HtmlEmail(
+ hostName: EMAIL_SERVER,
+ subject : subject,
+ htmlMsg : sw.toString()
+ )
+ email.setFrom("noreply@bookmarks.grail.org" , "Grails Bookmarks Application")
+ email.addTo(to.email, "$to")
+ println("Sending email: $sw")
+ email.send()
+ }
+}
34 grails-app/services/BookmarkService.groovy
@@ -0,0 +1,34 @@
+import org.grails.bookmarks.Bookmark
+import org.grails.bookmarks.User
+import org.grails.bookmarks.Tag
+
+class BookmarkService implements org.grails.bookmarks.IBookmarkService {
+
+ boolean transactional = false
+
+ List findAllForTag(String tag,User user) {
+ def t = tag ? Tag.findByName(tag) : null
+ if(user) user = User.findByLogin(user.login)
+ List bookmarks = Collections.EMPTY_LIST
+ if(t && user) {
+ def criteria = Bookmark.createCriteria()
+ bookmarks = criteria.list {
+ eq('user',user)
+ tags {
+ eq('tag', t)
+ }
+ }
+ }
+ return bookmarks
+ }
+
+ List findRecent(User user) {
+ def now = new Date()
+ if(user) user = User.findByLogin(user.login)
+ if(user) {
+ return Bookmark
+ .findAllByUserAndDateCreatedBetween(user,now-7,now)
+ }
+ }
+}
+
53 grails-app/services/DeliciousService.groovy
@@ -0,0 +1,53 @@
+import org.grails.bookmarks.*
+import org.apache.commons.httpclient.*
+import org.apache.commons.httpclient.auth.AuthScope
+import org.apache.commons.httpclient.methods.GetMethod
+
+class DeliciousService implements IBookmarkService {
+
+ static final API = "https://api.del.icio.us/v1"
+
+ boolean transactional = false
+
+ List findAllForTag(String tag,User user) {
+ return withDelicious(name:"posts/all",params:[tag:tag],user:user) { xml ->
+ def bookmarks = []
+ xml.post.each {
+ bookmarks << new Bookmark(title:"${it.@description}",url:"${it.@href}")
+ }
+ return bookmarks
+ }
+ }
+
+ List findRecent(User user) {
+ return withDelicious(name:"posts/recent",params:[count:5],user:user) { xml ->
+ def bookmarks = []
+ xml.post.each {
+ bookmarks << new Bookmark(title:"${it.@description}",url:"${it.@href}")
+ }
+ return bookmarks
+ }
+ }
+
+ private withDelicious(args,callable) {
+ if(!args.user) throw new IllegalArgumentException("Property [user] is required and cannot be null")
+
+
+ def url = "${API}/${args.name}?"
+ args.params?.each {k,v->
+ url+="&$k=$v"
+ }
+
+ def client = new HttpClient()
+ client
+ .state
+ .setCredentials(AuthScope.ANY,new UsernamePasswordCredentials(args.user.login, args.user.password))
+
+ def get = new GetMethod(url)
+ get.doAuthentication = true
+ client.executeMethod(get)
+
+ callable( new XmlSlurper().parse(get.responseBodyAsStream) )
+ }
+}
+
30 grails-app/taglib/BookmarkTagLib.groovy
@@ -0,0 +1,30 @@
+class BookmarkTagLib {
+ Writer out
+
+ def repeat = { attrs,body ->
+ attrs.times?.toInteger().times {
+ out << body(it)
+ }
+ }
+ def editInPlace = { attrs, body ->
+ def rows = attrs.rows ? attrs.rows : 0;
+ def cols = attrs.cols ? attrs.cols : 0;
+ def id = attrs.remove('id')
+ out << "<span id='${id}'>"
+ out << body()
+ out << "</span>"
+ out << "<script type='text/javascript'>"
+ out << "new Ajax.InPlaceEditor('${id}', '"
+ out << createLink(attrs)
+ out << "',{"
+ if(rows)
+ out << "rows:${rows},"
+ if(cols)
+ out << "cols:${cols},"
+ if(attrs.paramName) {
+ out << "callback: function(form, value) { return '${attrs.paramName}=' + escape(value) }"
+ }
+ out << "});"
+ out << "</script>"
+ }
+}
25 grails-app/views/bookmark/_bookmark.gsp
@@ -0,0 +1,25 @@
+<div id="bookmark${bookmark.id}">
+ <div id="link${bookmark.id}">
+ <a href="${bookmark.url}">${bookmark.title}</a>
+ <g:if test="${bookmark.id}"> -
+ <g:remoteLink onComplete="Effect.BlindDown('bookmark${bookmark.id}');" action="edit" id="${bookmark.id}" update="bookmark${bookmark.id}">edit</g:remoteLink> |
+ <g:link action="delete" id="${bookmark.id}"
+ onclick="return confirm('Are you sure you want to delete this bookmark?')">delete</g:link> |
+ <a href="#" onmouseover="${remoteFunction(action:'preview',
+ id:bookmark.id,
+ update:'preview' + bookmark.id,
+ onComplete:'Effect.Appear(preview'+bookmark.id+')')};"
+ onmouseout="Effect.Fade('preview${bookmark.id}');">preview</a>
+ </g:if>
+ </div>
+ <div id="notes">
+ <g:editInPlace id="notes${bookmark.id}"
+ url="[action:'updateNotes',id:bookmark.id]"
+ rows="5"
+ cols= "10"
+ paramName="notes">${bookmark.notes}</g:editInPlace>
+
+ </div>
+ <div id="preview${bookmark.id}">
+ </div>
+</div>
16 grails-app/views/bookmark/_editor.gsp
@@ -0,0 +1,16 @@
+<div id="editor">
+ <p>Title: <input type="text" name="title" value="${bookmark?.title}"/></p>
+ <p>URL: <g:remoteField action="suggestTag" update="suggestions" name="url" value="${bookmark?.url}"/></p>
+ <p>Notes:</p>
+ <p><textarea name="notes" col="1" row="1">${bookmark?.notes}</textarea></p>
+ <p>Tags:</p>
+ <p><input type="text" name="tagTokens" id="tags${bookmark?.id}" /></p>
+
+ <div id="suggestions${bookmark?.id}">
+ <g:each in="${suggestions?}">
+ <span class="suggestion">
+ <a href="#" onclick="$('tags${bookmark.id}.value += this.value+" "');this.style.display='none';">${it}</a>
+ </span>
+ </g:each>
+ </div>
+</div>
7 grails-app/views/bookmark/_suggest.gsp
@@ -0,0 +1,7 @@
+<g:if test="${tags}"><h3>Suggestions:</h3></g:if>
+<g:each var="t" in="${tags?}">
+ <span id="${t.id}" class="suggestion">
+ <a href="javascript:void(0);"
+ onclick="$('tags${bookmark?.id ? bookmark.id : ''}').value+=' '+this.innerHTML;this.style.display='none';">${t}</a>
+ </span>
+</g:each>
37 grails-app/views/bookmark/create.gsp
@@ -0,0 +1,37 @@
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Create Bookmark</title>
+ <g:javascript library="prototype" />
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link action="list">Bookmark List</g:link></span>
+ </div>
+ <div class="body">
+ <h1>Create Bookmark</h1>
+ <g:if test="${flash.message}">
+ <div class="message">${flash.message}</div>
+ </g:if>
+ <g:hasErrors bean="${bookmark}">
+ <div class="errors">
+ <g:renderErrors bean="${bookmark}" as="list" />
+ </div>
+ </g:hasErrors>
+ <g:form action="save" method="post" >
+ <div class="dialog">
+ <g:render template="editor" model="[bookmark:bookmark]" />
+ </div>
+ <div class="buttons">
+ <span class="formButton">
+ <input type="submit" value="Create"></input>
+ </span>
+ </div>
+ </g:form>
+ </div>
+ </body>
+</html>
+
10 grails-app/views/bookmark/edit.gsp
@@ -0,0 +1,10 @@
+<div id="bookmark${bookmark.id}" style="display:none;">
+<g:renderErrors as="list" bean="${bookmark}" />
+<g:formRemote name="editorForm" url="[action:'update',id:bookmark.id]" update="bookmark${bookmark.id}">
+<g:render template="editor" model="[bookmark:bookmark,suggestions:suggestions]" />
+<div id="editButtons">
+ <input type="submit" name="save" value="Save" />
+ <g:submitToRemote url="[action:'show',id:bookmark.id]" update="bookmark${bookmark.id}" name="cancel" value="Cancel" />
+</div>
+</g:formRemote>
+</div>
36 grails-app/views/bookmark/list.gsp
@@ -0,0 +1,36 @@
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Bookmark List</title>
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link action="create">New Bookmark</g:link></span>
+ <span class="menuButton"><g:link controller="subscription" action="list">Subscriptions</g:link></span>
+ </div>
+ <div class="body">
+ <h1 id="title">Bookmark List</h1>
+ <g:if test="${flash.message}">
+ <div class="message">
+ ${flash.message}
+ </div>
+ </g:if>
+
+ <g:render template="bookmark" var="bookmark" collection="${bookmarkList}" />
+
+ <g:if test="${deliciousList}">
+ <h2>Latest from <a href="http://del.icio.us/${session.user.login}" target="_blank">del.icio.us</a></h2>
+ <g:set var="edit" value="${false}" />
+ <g:render template="bookmark" var="bookmark" collection="${deliciousList}" />
+ </g:if>
+ <g:if test="${deliciousResults}">
+ <h2>Results from <a href="http://del.icio.us/${session.user.login}" target="_blank">del.icio.us</a></h2>
+ <g:render template="bookmark" var="bookmark" collection="${deliciousResults}" />
+ </g:if>
+ </div>
+ </body>
+</html>
+
1  grails-app/views/bookmark/show.gsp
@@ -0,0 +1 @@
+<g:render template="bookmark" model="[bookmark:bookmark]" />
46 grails-app/views/error.gsp
@@ -0,0 +1,46 @@
+<html>
+ <head>
+ <title>Grails Runtime Exception</title>
+ <style type="text/css">
+ .message {
+ border: 1px solid black;
+ padding: 5px;
+ background-color:#E9E9E9;
+ }
+ .stack {
+ border: 1px solid black;
+ padding: 5px;
+ overflow:auto;
+ height: 300px;
+ }
+ .snippet {
+ padding: 5px;
+ background-color:white;
+ border:1px solid black;
+ margin:3px;
+ font-family:courier;
+ }
+ </style>
+ </head>
+
+ <body>
+ <h1>Grails Runtime Exception</h1>
+ <h2>Error Details</h2>
+ <div class="message">
+ <strong>Message:</strong> ${exception.message} <br />
+ <strong>Caused by:</strong> ${exception.cause?.message} <br />
+ <strong>Class:</strong> ${exception.className} <br />
+ <strong>At Line:</strong> [${exception.lineNumber}] <br />
+ <strong>Code Snippet:</strong><br />
+ <div class="snippet">
+ <g:each var="cs" in="${exception.codeSnippet}">
+ ${cs}<br />
+ </g:each>
+ </div>
+ </div>
+ <h2>Stack Trace</h2>
+ <div class="stack">
+ ${exception.stackTraceText}
+ </div>
+ </body>
+</html>
88 grails-app/views/layouts/main.gsp
@@ -0,0 +1,88 @@
+<%@ page import="org.grails.bookmarks.*" %>
+<html>
+ <head>
+ <title><g:layoutTitle default="Grails" /></title>
+ <link rel="stylesheet" href="${createLinkTo(dir:'css',file:'main.css')}"></link>
+ <g:layoutHead />
+ <g:javascript library="scriptaculous" />
+ <g:javascript library="application" />
+ <style type="text/css">
+ .searchbar {
+ margin-top:10px;
+ background-color: lightgrey;
+ border:1px solid darkgrey;
+ width:97%;
+ height:30px;
+ padding:5px;
+ }
+ .total {
+ padding-top:5px;
+ float:left;
+ color:white;
+ }
+ .search {
+ float:right;
+ color:white;
+ }
+ .userDetails {
+ position:absolute;
+ right:10px;
+ top: 180px;
+ border:1px solid darkgrey;
+ background-color:lightgrey;
+ padding:10px;
+ width:150px;
+ }
+ .pageContent {
+ width:80%;
+ }
+ .logo h1 {
+ float:right;
+ margin:30px;
+ }
+ .spinner {
+ position: absolute;
+ right:0px;
+ padding:5px;
+ }
+ </style>
+ </head>
+ <body onload="<g:pageProperty name='body.onload'/>">
+ <div id="spinner" class="spinner" style="display:none;">
+ <img src="${createLinkTo(dir:'images',file:'spinner.gif')}" alt="Spinner" />
+ </div>
+ <div class="logo">
+ <g:if test="${session.user}">
+ <h1 id="header">Bookmarks by ${session.user.firstName} ${session.user.lastName}</h1>
+ </g:if>
+ <img src="${createLinkTo(dir:'images',file:'grails_logo.jpg')}" alt="Grails" />
+ </div>
+ <g:if test="${session.user}">
+ <div class="searchbar">
+ <div id="total" class="total">You have (${Bookmark.countByUser(session.user)}) bookmarks</div>
+ <div class="search">
+ <g:form id="searchForm" url="[action:'search', controller:'bookmark']">
+ <input type="text" name="q" /> <input type="submit" value="search" />
+ </g:form>
+ </div>
+ </div>
+ </g:if>
+ <div id="pageContent" class="pageContent">
+ <g:layoutBody />
+ </div>
+ <g:if test="${session.user}">
+ <div class="userDetails">
+ <h3>Your Profile</h3>
+ <p><strong>Login</strong>: ${session.user.login}</p>
+ <p><strong>First Name</strong>: ${session.user.firstName}</p>
+ <p><strong>Last Name</strong>: ${session.user.lastName}</p>
+ <p><strong>Email</strong>: ${session.user.email}</p>
+ </div>
+ <div class="tags">
+ <g:each in="${tags}">
+
+ </g:each>
+ </div>
+ </g:if>
+ </body>
+</html>
3  grails-app/views/subscription/_subscription.gsp
@@ -0,0 +1,3 @@
+<li>${subscription} - <g:remoteLink action="delete"
+ id="${subscription.id}"
+ update="subscriptions">delete</g:remoteLink></li>
32 grails-app/views/subscription/list.gsp
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Subscription List</title>
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link controller="bookmark" action="list">View Bookmarks</g:link></span>
+ </div>
+ <div class="body">
+ <h1 id="title">Subscription List</h1>
+ <g:if test="${flash.message}">
+ <div class="message">
+ ${flash.message}
+ </div>
+ </g:if>
+ <p>Below is a list of tags you are subscribed to. You will receive weekly notifications of
+ new bookmarks that have been added by other users that have been tagged with the below:</p>
+ <ul id="subscriptions">
+ <g:render template="subscription" var="subscription" collection="${subscriptionList}" />
+ </ul>
+ <p>
+ <g:formRemote name="addTag" url="[action:'save']" update="subscriptions">
+ Add Tag: <input type="text" name="tagName" /> <input type="submit" value="Add" />
+ </g:formRemote>
+ </p>
+ </div>
+ </body>
+</html>
+
44 grails-app/views/tag/create.gsp
@@ -0,0 +1,44 @@
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Create Tag</title>
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link action="list">Tag List</g:link></span>
+ </div>
+ <div class="body">
+ <h1>Create Tag</h1>
+ <g:if test="${flash.message}">
+ <div class="message">${flash.message}</div>
+ </g:if>
+ <g:hasErrors bean="${tag}">
+ <div class="errors">
+ <g:renderErrors bean="${tag}" as="list" />
+ </div>
+ </g:hasErrors>
+ <g:form action="save" method="post" >
+ <div class="dialog">
+ <table>
+
+
+
+ <tr class='prop'><td valign='top' class='name'><label for='bookmark'>Bookmark:</label></td><td valign='top' class='value ${hasErrors(bean:tag,field:'bookmark','errors')}'><g:select optionKey="id" from="${Bookmark.list()}" name='bookmark.id' value='${tag?.bookmark?.id}'></g:select></td></tr>
+
+ <tr class='prop'><td valign='top' class='name'><label for='name'>Name:</label></td><td valign='top' class='value ${hasErrors(bean:tag,field:'name','errors')}'><input type='text' name='name' value='${tag?.name}' /></td></tr>
+
+ </table>
+ </div>
+ <div class="buttons">
+ <span class="formButton">
+ <input type="submit" value="Create"></input>
+ </span>
+ </div>
+ </g:form>
+ </div>
+ </body>
+</html>
+
51 grails-app/views/tag/edit.gsp
@@ -0,0 +1,51 @@
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Edit Tag</title>
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link action="list">Tag List</g:link></span>
+ <span class="menuButton"><g:link action="create">New Tag</g:link></span>
+ </div>
+ <div class="body">
+ <h1>Edit Tag</h1>
+ <g:if test="${flash.message}">
+ <div class="message">${flash.message}</div>
+ </g:if>
+ <g:hasErrors bean="${tag}">
+ <div class="errors">
+ <g:renderErrors bean="${tag}" as="list" />
+ </div>
+ </g:hasErrors>
+ <div class="prop">
+ <span class="name">Id:</span>
+ <span class="value">${tag?.id}</span>
+ <input type="hidden" name="tag.id" value="${tag?.id}" />
+ </div>
+ <g:form controller="tag" method="post" >
+ <input type="hidden" name="id" value="${tag?.id}" />
+ <div class="dialog">
+ <table>
+
+
+
+ <tr class='prop'><td valign='top' class='name'><label for='bookmark'>Bookmark:</label></td><td valign='top' class='value ${hasErrors(bean:tag,field:'bookmark','errors')}'><g:select optionKey="id" from="${Bookmark.list()}" name='bookmark.id' value='${tag?.bookmark?.id}'></g:select></td></tr>
+
+ <tr class='prop'><td valign='top' class='name'><label for='name'>Name:</label></td><td valign='top' class='value ${hasErrors(bean:tag,field:'name','errors')}'><input type='text' name='name' value='${tag?.name}' /></td></tr>
+
+ </table>
+ </div>
+
+ <div class="buttons">
+ <span class="button"><g:actionSubmit value="Update" /></span>
+ <span class="button"><g:actionSubmit value="Delete" /></span>
+ </div>
+ </g:form>
+ </div>
+ </body>
+</html>
+
50 grails-app/views/tag/list.gsp
@@ -0,0 +1,50 @@
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Tag List</title>
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link action="create">New Tag</g:link></span>
+ </div>
+ <div class="body">
+ <h1>Tag List</h1>
+ <g:if test="${flash.message}">
+ <div class="message">
+ ${flash.message}
+ </div>
+ </g:if>
+ <table>
+ <tr>
+
+
+ <th>Id</th>
+
+ <th>Bookmark</th>
+
+ <th>Name</th>
+
+ <th></th>
+ </tr>
+ <g:each in="${tagList}">
+ <tr>
+
+ <td>${it.id}</td>
+
+ <td>${it.bookmark}</td>
+
+ <td>${it.name}</td>
+
+ <td class="actionButtons">
+ <span class="actionButton"><g:link action="show" id="${it.id}">Show</g:link></span>
+ </td>
+ </tr>
+ </g:each>
+ </table>
+ </div>
+ </body>
+</html>
+
56 grails-app/views/tag/show.gsp
@@ -0,0 +1,56 @@
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="layout" content="main" />
+ <title>Show Tag</title>
+ </head>
+ <body>
+ <div class="nav">
+ <span class="menuButton"><a href="${createLinkTo(dir:'')}">Home</a></span>
+ <span class="menuButton"><g:link action="list">Tag List</g:link></span>
+ <span class="menuButton"><g:link action="create">New Tag</g:link></span>
+ </div>
+ <div class="body">
+ <h1>Show Tag</h1>
+ <g:if test="${flash.message}">
+ <div class="message">${flash.message}</div>
+ </g:if>
+ <div class="dialog">
+ <table>
+
+
+ <tr class="prop">
+ <td valign="top" class="name">Id:</td>
+
+ <td valign="top" class="value">${tag.id}</td>
+
+ </tr>
+
+ <tr class="prop">
+ <td valign="top" class="name">Bookmark:</td>
+
+ <td valign="top" class="value"><g:link controller="bookmark" action="show" id="${tag?.bookmark?.id}">${tag?.bookmark}</g:link></td>
+
+ </tr>
+
+ <tr class="prop">
+ <td valign="top" class="name">Name:</td>
+
+ <td valign="top" class="value">${tag.name}</td>
+
+ </tr>
+
+ </table>
+ </div>
+ <div class="buttons">
+ <g:form controller="tag">
+ <input type="hidden" name="id" value="${tag?.id}" />
+ <span class="button"><g:actionSubmit value="Edit" /></span>
+ <span class="button"><g:actionSubmit value="Delete" /></span>
+ </g:form>
+ </div>
+ </div>
+ </body>
+</html>
+
35 grails-app/views/user/login.gsp
@@ -0,0 +1,35 @@
+<html>
+ <head>
+ <title>Login Page</title>
+ <meta name="layout" content="main" />
+ <style type="text/css" media="screen">
+ form { width: 300px; }
+ input {
+ position: absolute;
+ left: 130px;
+ }
+ p { margin-left: 30px; }
+ .button { margin-top: 30px; }
+ </style>
+ </head>
+ <body>
+ <g:if test="${flash.message}">
+ <div class="message">${flash.message}</div>
+ </g:if>
+ <p>
+ Welcome to Your Bookmarks. Login below or
+ <g:link action="register">register here</g:link>.
+ </p>
+ <form action="${request.contextPath}/j_acegi_security_check">
+ <p>
+ <label for="login">Login:</label>
+ <input type="text" name="j_username" />
+ </p>
+ <p>
+ <label for="password">Password:</label>
+ <input type="password" name="j_password" />
+ </p>
+ <input class="button" type="submit" value="Login" />
+ </form>
+ </body>
+</html>
57 grails-app/views/user/register.gsp
@@ -0,0 +1,57 @@
+<html>
+ <head>
+ <title>Registration Page</title>
+ <meta name="layout" content="main" />
+ </head>
+ <style type="text/css" media="screen">
+ form {
+ width: 300px;
+ }
+ input {
+ position: absolute;
+ left: 200px;
+ }
+ p {
+ margin-left: 30px;
+ }
+ .button {
+ margin-top: 30px;
+ }
+ </style>
+
+ <body>
+ <g:if test="${user}">
+ <div class="errors">
+ <g:renderErrors bean="${flash.user}" />
+ </div>
+ </g:if>
+ <p>Enter your details below to register for Your Bookmarks. If you have a del.icio.us account register with the same login details for del.icio.us integration!</p>
+ <form action="handleRegistration">
+ <p>
+ <label class="label" for="login">Login:</label>
+ <input type="text" name="login" />
+ </p>
+ <p>
+ <label for="password">Password:</label>
+ <input type="password" name="password" />
+ </p>
+ <p>
+ <label for="confirm">Confirm Password:</label>
+ <input type="password" name="confirm" />
+ </p>
+ <p>
+ <label for="firstName">First Name:</label>
+ <input type="text" name="firstName" />
+ </p>
+ <p>
+ <label for="lastName">Last Name:</label>
+ <input type="text" name="lastName" />
+ </p>
+ <p>
+ <label for="email">Email:</label>
+ <input type="text" name="email" />
+ </p>
+ <input class="button" type="submit" value="Register" />
+ </form>
+ </body>
+</html>
180 grails-tests/BookmarkTests.groovy
@@ -0,0 +1,180 @@
+import org.grails.bookmarks.*
+import groovy.mock.interceptor.*
+
+class BookmarkTests extends GroovyTestCase {
+
+ User user
+ Bookmark b1
+ Bookmark b2
+ Bookmark b3
+ Bookmark b4
+
+ void setUp() {
+ user = User.findByLogin("Graeme")
+ if(!user)user = new User(login:"Graeme",
+ password:"test",
+ firstName:"Graeme",
+ lastName:"Rocher",
+ email:"graeme@grails.org").save()
+
+ b1 = new Bookmark(title:"SkillsMatter",url:"http://www.skillsmatter.com",user:user)
+ b1.save()
+ b2 = new Bookmark(title:"Canoo",url:"http://www.canoo.com",user:user).save()
+ b3 = new Bookmark(title:"Grails",url:"http://grails.org",user:user).save()
+ b4 = new Bookmark(title:"Groovy",url:"http://groovy.codehaus.org",user:user).save()
+
+ b1.errors.allErrors.each { println it }
+ assert b1 != null
+ assert b2 != null
+ assert b3 != null
+ assert b4 != null
+ }
+
+ void testEditInPlace() {
+ def tagLibStub = new StubFor(BookmarkTagLib)
+
+ def sw = new StringWriter()
+ def link = "http://grails.org"
+ def paramName = "testParam"
+ def editorId = "testEditor"
+ def rowCount = 10
+ def colCount = 5
+ def attrs = [rows:rowCount,cols:colCount,paramName:paramName,id:editorId]
+
+ tagLibStub.demand.setOut { new PrintWriter(sw) }
+ tagLibStub.demand.createLink { arg -> sw << link }
+
+ tagLibStub.use {
+ def tl = new BookmarkTagLib()
+ tl.out = new PrintWriter(sw)
+ def eip = tl.editInPlace
+ eip.call(attrs, { tl.out << "test"})
+
+ def mkp = sw.toString()
+ println(mkp)
+ assert mkp.startsWith("<span id='${editorId}'>test</span>")
+ assert mkp.endsWith("<script type='text/javascript'>new Ajax.InPlaceEditor('${editorId}',"+
+ " '${link}'," +
+ "{rows:${rowCount},cols:${colCount},"+
+ "callback: function(form, value) { return '${paramName}=' + escape(value) }"+
+ "});</script>")
+ }
+ }
+
+ void testBadUrl() {
+ def badUrl = "aninvalidurl"
+ def b = new Bookmark()
+ shouldFail(MalformedURLException) {
+ b.url = new URI(badUrl)
+ }
+ }
+
+ void testListAction() {
+ def bc = new BookmarkController()
+ bc.session.user = user
+ def bookmarks = bc.list()?.bookmarkList
+ assert bookmarks != null
+ assertLength(4, bookmarks as Object[])
+ assert bookmarks[0].url?.toString() == "http://www.skillsmatter.com"
+ assert bookmarks[1].url?.toString() == "http://www.canoo.com"
+ assert bookmarks[2].url?.toString() == "http://grails.org"
+ assert bookmarks[3].url?.toString() == "http://groovy.codehaus.org"
+ }
+
+ void testListActionPaginate() {
+ def bc = new BookmarkController()
+ bc.session.user = user
+ bc.params.offset = 2
+ bc.params.max = 2
+ def bookmarks = bc.list()?.bookmarkList
+ assert bookmarks != null
+ assertLength(2, bookmarks as Object[])
+ assert bookmarks[0].url?.toString() == "http://grails.org"
+ assert bookmarks[1].url?.toString() == "http://groovy.codehaus.org"
+ }
+
+ BookmarkController bc
+ final Closure UPDATE_TEST_CODE = { id ->
+ bc = new BookmarkController()
+ bc.params.id = id
+ bc.update.call()
+ }
+
+ void testUpdateNotFound() {
+ def mock1 = new MockFor(BookmarkController)
+ def mock2 = new MockFor(Bookmark)
+
+ mock2.demand.get { return null }
+ mock1.demand.render { String text ->
+ assert text == "Bookmark not found"
+ }
+ mock1.use {
+ mock2.use(UPDATE_TEST_CODE.curry(5))
+ }
+ }
+
+ void testUpdateSuccess() {
+ def mock1 = new MockFor(BookmarkController)
+ def mock2 = new MockFor(Bookmark)
+ def b = Bookmark.findByTitle('Grails')
+ mock2.demand.get {
+ return b
+ }
+ mock2.demand.setProperties {}
+ mock1.demand.addTags {}
+ mock2.demand.save {
+ return true
+ }
+ mock1.demand.render { Map params ->
+ assert params.template == 'bookmark'
+ assert params.model?.bookmark == b
+ }
+ mock1.use {
+ mock2.use(UPDATE_TEST_CODE.curry(b.id))
+ }
+ }
+
+ void testUpdateFailure() {
+ def mock1 = new MockFor(BookmarkController)
+ def mock2 = new MockFor(Bookmark)
+ def b = Bookmark.findByTitle('Grails')
+ mock2.demand.get {
+ b.title = ""
+ return b
+ }
+ mock2.demand.setProperties {}
+ mock1.demand.addTags {}
+ mock2.demand.save {
+ return false
+ }
+ mock1.demand.render { Map params ->
+ assert params.view == 'edit'
+ assert params.model?.bookmark == b
+ }
+ mock1.use {
+ mock2.use(UPDATE_TEST_CODE.curry(b.id))
+ }
+ }
+
+ void testSortBookmarks() {
+
+ def sortUrlsBy = { property ->
+ Bookmark.findAll().sort { it."$property" }.url
+ }
+ def urls = sortUrlsBy("title")
+ assert urls != null
+ assert urls.size() == 4
+ assert urls[0].toString() == "http://www.canoo.com"
+ assert urls[1].toString() == "http://grails.org"
+ }
+
+ void tearDown() {
+ b1?.delete()
+ b2?.delete()
+ b3?.delete()
+ b4?.delete()
+ user?.delete()
+
+ }
+
+}
20 grails-tests/DeliciousTests.groovy
@@ -0,0 +1,20 @@
+import org.grails.bookmarks.*
+
+class DeliciousTests extends GroovyTestCase {
+
+ DeliciousService deliciousService
+
+ void testFindAllForTag() {
+ assert deliciousService != null
+ try {
+ def user = new User(login:'graeme.rocher',password:'...')
+ def bookmarks = deliciousService.findAllForTag(new Tag(name:'grails'),user)
+ assert bookmarks.size() > 0
+ def b = bookmarks.find { it.url == new URI("http://grails.org/") }
+ assert b != null
+ }
+ catch(Exception e) {
+ println "Warning: Calling del.icio.us produced error: ${e.message}"
+ }
+ }
+}
59 grails-tests/SubscriptionTests.groovy
@@ -0,0 +1,59 @@
+import org.grails.bookmarks.*
+import groovy.mock.interceptor.StubFor
+
+class SubscriptionTests extends GroovyTestCase {
+