{"id":20788,"date":"2014-02-01T15:00:49","date_gmt":"2014-02-01T13:00:49","guid":{"rendered":"http:\/\/www.javacodegeeks.com\/?p=20788"},"modified":"2014-02-01T10:52:00","modified_gmt":"2014-02-01T08:52:00","slug":"project-student-maintenance-webapp-editable","status":"publish","type":"post","link":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html","title":{"rendered":"Project Student: Maintenance Webapp (editable)"},"content":{"rendered":"<p>This is part of <a href=\"http:\/\/invariantproperties.com\/2013\/12\/10\/project-student\/\">Project Student<\/a>. Other posts are <a href=\"http:\/\/www.javacodegeeks.com\/2013\/12\/project-student-webservice-client-with-jersey.html\">Webservice Client With Jersey<\/a>, <a href=\"http:\/\/www.javacodegeeks.com\/2014\/01\/project-student-webservice-server-with-jersey.html\">Webservice Server with Jersey<\/a>, <a href=\"http:\/\/www.javacodegeeks.com\/2014\/01\/project-student-business-layer.html\">Business Layer<\/a>, <a href=\"http:\/\/www.javacodegeeks.com\/2014\/01\/project-student-persistence-with-spring-data.html\">Persistence with Spring Data<\/a>, <a href=\"http:\/\/www.javacodegeeks.com\/2014\/01\/project-student-sharding-integration-test-data.html\">Sharding Integration Test Data<\/a>, <a href=\"http:\/\/www.javacodegeeks.com\/2014\/01\/project-student-webservice-integration.html\">Webservice Integration<\/a>, <a href=\"http:\/\/invariantproperties.com\/2013\/12\/29\/project-student-jpa-criteria-queries\/\">JPA Criteria Queries<\/a> and <a href=\"http:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html\">Maintenance Webapp (read-only)<\/a>.<\/p>\n<p>Last time we created a simple webapp that allows us to take a quick peek into the database. It had very limited functionality \u2013 the primary goal was to knit together a system that exercised the entire stack from web browser to database. This time we add actual CRUD support.<\/p>\n<p>This post is borrows heavily from the <a href=\"http:\/\/jumpstart.doublenegative.com.au\/\">jumpstart<\/a> site but there are significant differences. There\u2019s a lot of code but it\u2019s boilerplate that can be easily reused.<\/p>\n<h2>Limitations<\/h2>\n<ul>\n<li><strong>User authentication<\/strong> \u2013 no effort has been made to authenticate users.<\/li>\n<li><strong>Encryption<\/strong> \u2013 no effort has been made to encrypt communications.<\/li>\n<li><strong>Pagination<\/strong> \u2013 no effort has been made to support pagination. The Tapestry 5 component will give the appearance of pagination but it will always contain the same first page of data.<\/li>\n<li><strong>Error Messages<\/strong> \u2013 error messages will be shown but server-side errors will be uninformative for now.<\/li>\n<li><strong>Cross-Site Scripting (XSS)<\/strong> \u2013 no effort has been made to prevent XSS attacks.<\/li>\n<li><strong>Internationalization<\/strong> \u2013 no effort has been made to support internationalization.<\/li>\n<\/ul>\n<h2>Goal<\/h2>\n<p>We want the standard CRUD pages.<\/p>\n<p>First, we need to be able to create a new course. Our list of courses should include a link as a default message when we don\u2019t have any data. (The first \u201ccreate\u2026\u201d is a separate element.)<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_008.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21230\" alt=\"Course-List-Chromium_008\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_008-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_008-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_008.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Now a creation page with several fields. A code uniquely identifies a course, e.g., CSCI 101, and name, summary and description should be self-explanatory.<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_006.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21231\" alt=\"Course-Editor-Chromium_006\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_006-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_006-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_006.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>After successful creation we\u2019re taken to a review page.<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_002.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21232\" alt=\"Course-Editor-Chromium_002\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_002-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_002-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_002.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>And then back to an update page if we need to make a change.<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_004.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21233\" alt=\"Course-Editor-Chromium_004\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_004-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_004-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_004.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>At any point we can go back to the list page.<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_001.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21234\" alt=\"Course-List-Chromium_001\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_001-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_001-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_001.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>We\u2019re prompted for confirmation before deleting a record.<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_005.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21235\" alt=\"Course-List-Chromium_005\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_005-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_005-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-List-Chromium_005.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>And finally we\u2019re able to show server-side errors, e.g., for duplicate values in unique fields, even if the messages are pretty useless at the moment.<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_007.png\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21236\" alt=\"Course-Editor-Chromium_007\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_007-300x230.png\" width=\"300\" height=\"230\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_007-300x230.png 300w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/Course-Editor-Chromium_007.png 1010w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>We also have client-side error checking although I don\u2019t show it here.<\/p>\n<h2>Index.tml<\/h2>\n<p>We start with the index page. It\u2019s similar to what we saw in the last post.<\/p>\n<p>Tapestry 5 has three major types of links. A <em>pagelink<\/em> is mapped to a standard HTML link. An <em>actionlink<\/em> is directly handled by the corresponding class, e.g., <em>Index.java<\/em> for the <em>Index.tml<\/em> template. Finally an <em>eventlink<\/em> injects an event into the normal event flow within the tapestry engine. All of my links go to a closely related page so I use an actionlink.<\/p>\n<pre class=\" brush:xml\">&lt;html t:type=\"layout\" title=\"Course List\"\r\n      t:sidebarTitle=\"Framework Version\"\r\n      xmlns:t=\"http:\/\/tapestry.apache.org\/schema\/tapestry_5_3.xsd\"\r\n      xmlns:p=\"tapestry:parameter\"&gt;\r\n\r\n    &lt;t:zone t:id=\"zone\"&gt;   \r\n        &lt;p&gt;\r\n            \"Course\" page\r\n        &lt;\/p&gt;\r\n\r\n        &lt;t:actionlink t:id=\"create\"&gt;Create...&lt;\/t:actionlink&gt;&lt;br\/&gt;\r\n\r\n        &lt;t:grid source=\"courses\" row=\"course\" include=\"code, name,creationdate\" add=\"edit,delete\"&gt;\r\n            &lt;p:codecell&gt;\r\n                &lt;t:actionlink t:id=\"view\" context=\"course.uuid\"&gt;${course.code}&lt;\/t:actionlink&gt;\r\n            &lt;\/p:codecell&gt;\r\n            &lt;p:editcell&gt;\r\n                &lt;t:actionlink t:id=\"update\" context=\"course.uuid\"&gt;Edit&lt;\/t:actionlink&gt;\r\n            &lt;\/p:editcell&gt;\r\n            &lt;p:deletecell&gt;\r\n                &lt;t:actionlink t:id=\"delete\" context=\"course.uuid\" t:mixins=\"Confirm\" t:message=\"Delete ${course.name}?\"&gt;Delete&lt;\/t:actionlink&gt;\r\n            &lt;\/p:deletecell&gt;\r\n            &lt;p:empty&gt;\r\n              &lt;p&gt;There are no courses to display; you can &lt;t:actionlink t:id=\"create1\"&gt;create&lt;\/t:actionlink&gt; one.&lt;\/p&gt;\r\n            &lt;\/p:empty&gt;\r\n        &lt;\/t:grid&gt;\r\n    &lt;\/t:zone&gt;\r\n\r\n    &lt;p:sidebar&gt;\r\n        &lt;p&gt;\r\n            [\r\n            &lt;t:pagelink page=\"Index\"&gt;Index&lt;\/t:pagelink&gt;\r\n            ]&lt;br\/&gt;\r\n            [\r\n            &lt;t:pagelink page=\"Course\/Index\"&gt;Courses&lt;\/t:pagelink&gt;\r\n            ]\r\n        &lt;\/p&gt;\r\n    &lt;\/p:sidebar&gt;\r\n\r\n&lt;\/html&gt;<\/pre>\n<h2>Confirm mixin<\/h2>\n<p>The Index.tml template included a \u2018mixin\u2019 on the delete actionlink. It uses a mixture of javascript and java to display a popup message to ask the user to verify that he wants to delete the course.<\/p>\n<p>This code is straight from the jumpstart and Tapestry sites.<\/p>\n<pre class=\" brush:java\">\/\/ from http:\/\/jumpstart.doublenegative.com.au\/\r\nConfirm = Class.create({\r\n\r\n    initialize: function(elementId, message) {\r\n        this.message = message;\r\n        Event.observe($(elementId), 'click', this.doConfirm.bindAsEventListener(this));\r\n    },\r\n\r\n    doConfirm: function(e) {\r\n\r\n        \/\/ Pop up a javascript Confirm Box (see http:\/\/www.w3schools.com\/js\/js_popup.asp)\r\n\r\n        if (!confirm(this.message)) {\r\n                e.stop();\r\n        }\r\n    }\r\n})\r\n\r\n\/\/ Extend the Tapestry.Initializer with a static method that instantiates a Confirm.\r\n\r\nTapestry.Initializer.confirm = function(spec) {\r\n    new Confirm(spec.elementId, spec.message);\r\n}<\/pre>\n<p>The corresponding java code is<\/p>\n<pre class=\" brush:java\">\/\/ from http:\/\/jumpstart.doublenegative.com.au\/\r\n@Import(library = \"confirm.js\")\r\npublic class Confirm {\r\n\r\n    @Parameter(name = \"message\", value = \"Are you sure?\", defaultPrefix = BindingConstants.LITERAL)\r\n    private String message;\r\n\r\n    @Inject\r\n    private JavaScriptSupport javaScriptSupport;\r\n\r\n    @InjectContainer\r\n    private ClientElement clientElement;\r\n\r\n    @AfterRender\r\n    public void afterRender() {\r\n\r\n        \/\/ Tell the Tapestry.Initializer to do the initializing of a Confirm,\r\n        \/\/ which it will do when the DOM has been\r\n        \/\/ fully loaded.\r\n\r\n        JSONObject spec = new JSONObject();\r\n        spec.put(\"elementId\", clientElement.getClientId());\r\n        spec.put(\"message\", message);\r\n        javaScriptSupport.addInitializerCall(\"confirm\", spec);\r\n    }\r\n}<\/pre>\n<h2>Index.java<\/h2>\n<p>The java that supports the index template is straightforward since we only need to define a data source and provide a few action handlers.<\/p>\n<pre class=\" brush:java\">package com.invariantproperties.sandbox.student.maintenance.web.pages.course;\r\n\r\npublic class Index {\r\n    @Property\r\n    @Inject\r\n    @Symbol(SymbolConstants.TAPESTRY_VERSION)\r\n    private String tapestryVersion;\r\n\r\n    @InjectComponent\r\n    private Zone zone;\r\n\r\n    @Inject\r\n    private CourseFinderService courseFinderService;\r\n\r\n    @Inject\r\n    private CourseManagerService courseManagerService;\r\n\r\n    @Property\r\n    private Course course;\r\n\r\n    \/\/ our sibling page\r\n    @InjectPage\r\n    private com.invariantproperties.sandbox.student.maintenance.web.pages.course.Editor editorPage;\r\n\r\n    \/**\r\n     * Get the datasource containing our data.\r\n     * \r\n     * @return\r\n     *\/\r\n    public GridDataSource getCourses() {\r\n        return new CoursePagedDataSource(courseFinderService);\r\n    }\r\n\r\n    \/**\r\n     * Handle a delete request. This could fail, e.g., if the course has already\r\n     * been deleted.\r\n     * \r\n     * @param courseUuid\r\n     *\/\r\n    void onActionFromDelete(String courseUuid) {\r\n        courseManagerService.deleteCourse(courseUuid, 0);\r\n    }\r\n\r\n    \/**\r\n     * Bring up editor page in create mode.\r\n     * \r\n     * @param courseUuid\r\n     * @return\r\n     *\/\r\n    Object onActionFromCreate() {\r\n        editorPage.setup(Mode.CREATE, null);\r\n        return editorPage;\r\n    }\r\n\r\n    \/**\r\n     * Bring up editor page in create mode.\r\n     * \r\n     * @param courseUuid\r\n     * @return\r\n     *\/\r\n    Object onActionFromCreate1() {\r\n        return onActionFromCreate();\r\n    }\r\n\r\n    \/**\r\n     * Bring up editor page in review mode.\r\n     * \r\n     * @param courseUuid\r\n     * @return\r\n     *\/\r\n    Object onActionFromView(String courseUuid) {\r\n        editorPage.setup(Mode.REVIEW, courseUuid);\r\n        return editorPage;\r\n    }\r\n\r\n    \/**\r\n     * Bring up editor page in update mode.\r\n     * \r\n     * @param courseUuid\r\n     * @return\r\n     *\/\r\n    Object onActionFromUpdate(String courseUuid) {\r\n        editorPage.setup(Mode.UPDATE, courseUuid);\r\n        return editorPage;\r\n    }\r\n}<\/pre>\n<h2>Editor.tml<\/h2>\n<p>The CRUD pages could be three separate pages (for create, review and update) or a single page. I\u2019m following the pattern used by the jumpstart site \u2013 a single page. I\u2019ll be honest \u2013 I\u2019m not sure why he made this decision \u2013 perhaps it is because the pages are closely related and he uses event processing? In any case I\u2019ll discuss the elements separately.<div style=\"display:inline-block; margin: 15px 0;\"> <div id=\"adngin-JavaCodeGeeks_incontent_video-0\" style=\"display:inline-block;\"><\/div> <\/div><\/p>\n<h4>CREATE template<\/h4>\n<p>The \u201ccreate\u201d template is a simple form. You can see that HTML &lt;input&gt; elements are enhanced with some tapestry-specific attributes, plus a few additional tags like &lt;t:errors\/&gt; and &lt;t:submit&gt;.<\/p>\n<p>The <em>CustomForm<\/em> and <em>CustomError<\/em> are local extensions to the standard Tapestry <em>Form<\/em> and <em>Error<\/em> classes. They\u2019re currently empty but allow us to easily add local extensions.<\/p>\n<pre class=\" brush:xml\">&lt;html t:type=\"layout\" title=\"Course Editor\"\r\n      t:sidebarTitle=\"Framework Version\"\r\n      xmlns:t=\"http:\/\/tapestry.apache.org\/schema\/tapestry_5_3.xsd\" xmlns:p=\"tapestry:parameter\"&gt;\r\n\r\n    &lt;t:zone t:id=\"zone\"&gt;   \r\n\r\n    &lt;t:if test=\"modeCreate\"&gt;\r\n        &lt;h1&gt;Create&lt;\/h1&gt;\r\n\r\n        &lt;form t:type=\"form\" t:id=\"createForm\" &gt;\r\n            &lt;t:errors\/&gt;\r\n\r\n            &lt;table&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;&lt;t:label for=\"code\"\/&gt;:&lt;\/th&gt;\r\n                    &lt;td&gt;&lt;input t:type=\"TextField\" t:id=\"code\" value=\"course.code\" t:validate=\"required, maxlength=12\" size=\"12\"\/&gt;&lt;\/td&gt;\r\n                    &lt;td&gt;(required)&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr class=\"err\"&gt;\r\n                    &lt;th&gt;&lt;\/th&gt;\r\n                    &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"code\"\/&gt;&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;&lt;t:label for=\"name\"\/&gt;:&lt;\/th&gt;\r\n                    &lt;td&gt;&lt;input t:type=\"TextField\" t:id=\"name\" value=\"course.name\" t:validate=\"required, maxlength=80\" size=\"45\"\/&gt;&lt;\/td&gt;\r\n                    &lt;td&gt;(required)&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr class=\"err\"&gt;\r\n                    &lt;th&gt;&lt;\/th&gt;\r\n                    &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"name\"\/&gt;&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;&lt;t:label for=\"summary\"\/&gt;:&lt;\/th&gt;\r\n                    &lt;td&gt;&lt;input cols=\"50\" rows=\"4\" t:type=\"TextArea\" t:id=\"summary\" value=\"course.summary\" t:validate=\"maxlength=400\"\/&gt;&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr class=\"err\"&gt;\r\n                    &lt;th&gt;&lt;\/th&gt;\r\n                    &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"summary\"\/&gt;&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;&lt;t:label for=\"description\"\/&gt;:&lt;\/th&gt;\r\n                    &lt;td&gt;&lt;input cols=\"50\" rows=\"12\" t:type=\"TextArea\" t:id=\"description\" value=\"course.description\" t:validate=\"maxlength=2000\"\/&gt;&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr class=\"err\"&gt;\r\n                    &lt;th&gt;&lt;\/th&gt;\r\n                    &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"description\"\/&gt;&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n            &lt;\/table&gt;\r\n\r\n            &lt;div class=\"buttons\"&gt;\r\n                &lt;t:submit t:event=\"cancelCreate\" t:context=\"course.uuid\" value=\"Cancel\"\/&gt;\r\n                &lt;input type=\"submit\" value=\"Save\"\/&gt;\r\n            &lt;\/div&gt;\r\n        &lt;\/form&gt;\r\n    &lt;\/t:if&gt;\r\n\r\n    ...\r\n&lt;\/html&gt;<\/pre>\n<h4>CREATE java<\/h4>\n<ul>\n<li>The corresponding java class is straightforward. We must define a few custom events.<\/li>\n<li>The <em>ActivationRequestParameter<\/em> values are pulled from the URL query string.<\/li>\n<li>The <em>course<\/em> field contains the values to be used when creating a new object.<\/li>\n<li>The <em>courseForm<\/em> field contains the corresponding &lt;form&gt; on the template.<\/li>\n<li>The <em>indexPage<\/em> contains a reference to the index page.<\/li>\n<\/ul>\n<p>There are four event handlers named <em>onEventFromCreateForm<\/em>, where event<br \/>\ncan be prepare, validate, success or failure. Each event handler has very specific roles.<\/p>\n<p>There is one additional event handler, <em>onCancelCreate()<\/em>. You can see the name of that event in the &lt;t:submit&gt; tag in the template.<\/p>\n<pre class=\" brush:java\">\/**\r\n * This component will trigger the following events on its container (which in\r\n * this example is the page):\r\n * {@link Editor.web.components.examples.component.crud.Editor#CANCEL_CREATE} ,\r\n * {@link Editor.web.components.examples.component.crud.Editor#SUCCESSFUL_CREATE}\r\n * (Long courseUuid),\r\n * {@link Editor.web.components.examples.component.crud.Editor#FAILED_CREATE} ,\r\n *\/\r\n\/\/ @Events is applied to a component solely to document what events it may\r\n\/\/ trigger. It is not checked at runtime.\r\n@Events({ Editor.CANCEL_CREATE, Editor.SUCCESSFUL_CREATE, Editor.FAILED_CREATE })\r\npublic class Editor {\r\n    public static final String CANCEL_CREATE = \"cancelCreate\";\r\n    public static final String SUCCESSFUL_CREATE = \"successfulCreate\";\r\n    public static final String FAILED_CREATE = \"failedCreate\";\r\n\r\n    public enum Mode {\r\n        CREATE, REVIEW, UPDATE;\r\n    }\r\n\r\n    \/\/ Parameters\r\n\r\n    @ActivationRequestParameter\r\n    @Property\r\n    private Mode mode;\r\n\r\n    @ActivationRequestParameter\r\n    @Property\r\n    private String courseUuid;\r\n\r\n    \/\/ Screen fields\r\n\r\n    @Property\r\n    private Course course;\r\n\r\n    \/\/ Work fields\r\n\r\n    \/\/ This carries version through the redirect that follows a server-side\r\n    \/\/ validation failure.\r\n    @Persist(PersistenceConstants.FLASH)\r\n    private Integer versionFlash;\r\n\r\n    \/\/ Generally useful bits and pieces\r\n\r\n    @Inject\r\n    private CourseFinderService courseFinderService;\r\n\r\n    @Inject\r\n    private CourseManagerService courseManagerService;\r\n\r\n    @Component\r\n    private CustomForm createForm;\r\n\r\n    @Inject\r\n    private ComponentResources componentResources;\r\n\r\n    @InjectPage\r\n    private com.invariantproperties.sandbox.student.maintenance.web.pages.course.Index indexPage;\r\n\r\n    \/\/ The code\r\n\r\n    public void setup(Mode mode, String courseUuid) {\r\n        this.mode = mode;\r\n        this.courseUuid = courseUuid;\r\n    }\r\n\r\n    \/\/ setupRender() is called by Tapestry right before it starts rendering the\r\n    \/\/ component.\r\n\r\n    void setupRender() {\r\n\r\n        if (mode == Mode.REVIEW) {\r\n            if (courseUuid == null) {\r\n                course = null;\r\n                \/\/ Handle null course in the template.\r\n            } else {\r\n                if (course == null) {\r\n                    try {\r\n                        course = courseFinderService.findCourseByUuid(courseUuid);\r\n                    } catch (ObjectNotFoundException e) {\r\n                        \/\/ Handle null course in the template.\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n    \/\/ CREATE\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n    \/\/ Handle event \"cancelCreate\"\r\n\r\n    Object onCancelCreate() {\r\n        return indexPage;\r\n    }\r\n\r\n    \/\/ Component \"createForm\" bubbles up the PREPARE event when it is rendered\r\n    \/\/ or submitted\r\n\r\n    void onPrepareFromCreateForm() throws Exception {\r\n        \/\/ Instantiate a Course for the form data to overlay.\r\n        course = new Course();\r\n    }\r\n\r\n    \/\/ Component \"createForm\" bubbles up the VALIDATE event when it is submitted\r\n\r\n    void onValidateFromCreateForm() {\r\n\r\n        if (createForm.getHasErrors()) {\r\n            \/\/ We get here only if a server-side validator detected an error.\r\n            return;\r\n        }\r\n\r\n        try {\r\n            course = courseManagerService.createCourse(course.getCode(), course.getName(), course.getSummary(),\r\n                    course.getDescription(), 1);\r\n        } catch (RestClientFailureException e) {\r\n            createForm.recordError(\"Internal error on server.\");\r\n            createForm.recordError(e.getMessage());\r\n        } catch (Exception e) {\r\n            createForm.recordError(ExceptionUtil.getRootCauseMessage(e));\r\n        }\r\n    }\r\n\r\n    \/\/ Component \"createForm\" bubbles up SUCCESS or FAILURE when it is\r\n    \/\/ submitted, depending on whether VALIDATE\r\n    \/\/ records an error\r\n\r\n    boolean onSuccessFromCreateForm() {\r\n        componentResources.triggerEvent(SUCCESSFUL_CREATE, new Object[] { course.getUuid() }, null);\r\n\r\n        \/\/ We don't want \"success\" to bubble up, so we return true to say we've\r\n        \/\/ handled it.\r\n        mode = Mode.REVIEW;\r\n        courseUuid = course.getUuid();\r\n        return true;\r\n    }\r\n\r\n    boolean onFailureFromCreateForm() {\r\n        \/\/ Rather than letting \"failure\" bubble up which doesn't say what you\r\n        \/\/ were trying to do, we trigger new event\r\n        \/\/ \"failedCreate\". It will bubble up because we don't have a handler\r\n        \/\/ method for it.\r\n        componentResources.triggerEvent(FAILED_CREATE, null, null);\r\n\r\n        \/\/ We don't want \"failure\" to bubble up, so we return true to say we've\r\n        \/\/ handled it.\r\n        return true;\r\n    }\r\n\r\n    ....\r\n}<\/pre>\n<h4>REVIEW template<\/h4>\n<p>The \u201creview\u201d template is a simple table. It is wrapped in a form but that\u2019s solely for the navigation buttons at the bottom of the page.<\/p>\n<pre class=\" brush:java\">&lt;t:if test=\"modeReview\"&gt;\r\n        &lt;h1&gt;Review&lt;\/h1&gt;\r\n\r\n        &lt;strong&gt;Warning: no attempt is made to block XSS&lt;\/strong&gt;\r\n\r\n        &lt;form t:type=\"form\" t:id=\"reviewForm\"&gt;\r\n            &lt;t:errors\/&gt;\r\n\r\n        &lt;t:if test=\"course\"&gt;\r\n            &lt;div t:type=\"if\" t:test=\"deleteMessage\" class=\"error\"&gt;\r\n                ${deleteMessage}\r\n            &lt;\/div&gt;\r\n\r\n            &lt;table&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;Uuid:&lt;\/th&gt;\r\n                    &lt;td&gt;${course.uuid}&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;Code:&lt;\/th&gt;\r\n                    &lt;td&gt;${course.code}&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;Name:&lt;\/th&gt;\r\n                    &lt;td&gt;${course.name}&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;Summary:&lt;\/th&gt;\r\n                    &lt;td&gt;${course.summary}&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n                &lt;tr&gt;\r\n                    &lt;th&gt;Description:&lt;\/th&gt;\r\n                    &lt;td&gt;${course.description}&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n            &lt;\/table&gt;\r\n\r\n            &lt;div class=\"buttons\"&gt;\r\n                &lt;t:submit t:event=\"toIndex\" t:context=\"course.uuid\" value=\"List\"\/&gt;\r\n                &lt;t:submit t:event=\"toUpdate\" t:context=\"course.uuid\" value=\"Update\"\/&gt;\r\n                &lt;t:submit t:event=\"delete\" t:context=\"course.uuid\" t:mixins=\"Confirm\" t:message=\"Delete ${course.name}?\" value=\"Delete\"\/&gt;\r\n            &lt;\/div&gt;\r\n\r\n        &lt;\/t:if&gt;\r\n        &lt;t:if negate=\"true\" test=\"course\"&gt;\r\n            Course ${courseUuid} does not exist.&lt;br\/&gt;&lt;br\/&gt;\r\n        &lt;\/t:if&gt;\r\n        &lt;\/form&gt;\r\n    &lt;\/t:if&gt;<\/pre>\n<h4>REVIEW java<\/h4>\n<p>The java required for the review form is trivial \u2013 we just need to load the data. I would have expected the setupRender() to be enough but in practice I needed the <em>onPrepareFromReviewForm()<\/em> method.<\/p>\n<pre class=\" brush:java\">public class Editor {\r\n\r\n    public enum Mode {\r\n        CREATE, REVIEW, UPDATE;\r\n    }\r\n\r\n    \/\/ Parameters\r\n\r\n    @ActivationRequestParameter\r\n    @Property\r\n    private Mode mode;\r\n\r\n    @ActivationRequestParameter\r\n    @Property\r\n    private String courseUuid;\r\n\r\n    \/\/ Screen fields\r\n\r\n    @Property\r\n    private Course course;\r\n\r\n    \/\/ Generally useful bits and pieces\r\n\r\n    @Inject\r\n    private CourseFinderService courseFinderService;\r\n\r\n    @Component\r\n    private CustomForm reviewForm;\r\n\r\n    @Inject\r\n    private ComponentResources componentResources;\r\n\r\n    @InjectPage\r\n    private com.invariantproperties.sandbox.student.maintenance.web.pages.course.Index indexPage;\r\n\r\n    \/\/ The code\r\n\r\n    public void setup(Mode mode, String courseUuid) {\r\n        this.mode = mode;\r\n        this.courseUuid = courseUuid;\r\n    }\r\n\r\n    \/\/ setupRender() is called by Tapestry right before it starts rendering the\r\n    \/\/ component.\r\n\r\n    void setupRender() {\r\n\r\n        if (mode == Mode.REVIEW) {\r\n            if (courseUuid == null) {\r\n                course = null;\r\n                \/\/ Handle null course in the template.\r\n            } else {\r\n                if (course == null) {\r\n                    try {\r\n                        course = courseFinderService.findCourseByUuid(courseUuid);\r\n                    } catch (ObjectNotFoundException e) {\r\n                        \/\/ Handle null course in the template.\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n    \/\/ REVIEW\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n    void onPrepareFromReviewForm() {\r\n        try {\r\n            course = courseFinderService.findCourseByUuid(courseUuid);\r\n        } catch (ObjectNotFoundException e) {\r\n            \/\/ Handle null course in the template.\r\n        }\r\n    }\r\n\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n    \/\/ PAGE NAVIGATION\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n    \/\/ Handle event \"toUpdate\"\r\n\r\n    boolean onToUpdate(String courseUuid) {\r\n        mode = Mode.UPDATE;\r\n        return false;\r\n    }\r\n\r\n    \/\/ Handle event \"toIndex\"\r\n\r\n    Object onToIndex() {\r\n        return indexPage;\r\n    }\r\n\r\n    ....\r\n}<\/pre>\n<h4>UPDATE template<\/h4>\n<p>Finally, the \u201cupdate\u201d template looks similar to the \u201ccreate\u201d template.<\/p>\n<pre class=\" brush:java\">&lt;t:if test=\"modeUpdate\"&gt;\r\n        &lt;h1&gt;Update&lt;\/h1&gt;\r\n\r\n        &lt;strong&gt;Warning: no attempt is made to block XSS&lt;\/strong&gt;\r\n\r\n        &lt;form t:type=\"form\" t:id=\"updateForm\"&gt;\r\n            &lt;t:errors\/&gt;\r\n\r\n            &lt;t:if test=\"course\"&gt;\r\n                &lt;!-- If optimistic locking is not needed then comment out this next line. It works because Hidden fields are part of the submit. --&gt;\r\n                &lt;!-- &lt;t:hidden value=\"course.version\"\/&gt; --&gt;\r\n\r\n                &lt;table&gt;\r\n                    &lt;tr&gt;\r\n                        &lt;th&gt;&lt;t:label for=\"updCode\"\/&gt;:&lt;\/th&gt;\r\n                        &lt;td&gt;&lt;input t:type=\"TextField\" t:id=\"updCode\" value=\"course.code\" t:disabled=\"true\" size=\"12\"\/&gt;&lt;\/td&gt;\r\n                        &lt;td&gt;(read-only)&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr class=\"err\"&gt;\r\n                        &lt;th&gt;&lt;\/th&gt;\r\n                        &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"updName\"\/&gt;&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr&gt;\r\n                        &lt;th&gt;&lt;t:label for=\"updName\"\/&gt;:&lt;\/th&gt;\r\n                        &lt;td&gt;&lt;input t:type=\"TextField\" t:id=\"updName\" value=\"course.name\" t:validate=\"required, maxlength=80\" size=\"45\"\/&gt;&lt;\/td&gt;\r\n                        &lt;td&gt;(required)&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr class=\"err\"&gt;\r\n                        &lt;th&gt;&lt;\/th&gt;\r\n                        &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"updSummary\"\/&gt;&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr&gt;\r\n                        &lt;th&gt;&lt;t:label for=\"updSummary\"\/&gt;:&lt;\/th&gt;\r\n                        &lt;td&gt;&lt;input cols=\"50\" rows=\"4\" t:type=\"TextArea\" t:id=\"updSummary\" value=\"course.summary\" t:validate=\"maxlength=400\"\/&gt;&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr class=\"err\"&gt;\r\n                        &lt;th&gt;&lt;\/th&gt;\r\n                        &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"updSummary\"\/&gt;&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr&gt;\r\n                        &lt;th&gt;&lt;t:label for=\"updDescription\"\/&gt;:&lt;\/th&gt;\r\n                        &lt;td&gt;&lt;input cols=\"50\" rows=\"12\" t:type=\"TextArea\" t:id=\"updDescription\" value=\"course.description\" t:validate=\"maxlength=50\"\/&gt;&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                    &lt;tr class=\"err\"&gt;\r\n                        &lt;th&gt;&lt;\/th&gt;\r\n                        &lt;td colspan=\"2\"&gt;&lt;t:CustomError for=\"updDescription\"\/&gt;&lt;\/td&gt;\r\n                    &lt;\/tr&gt;\r\n                &lt;\/table&gt;\r\n\r\n                &lt;div class=\"buttons\"&gt;\r\n                    &lt;t:submit t:event=\"toIndex\" t:context=\"course.uuid\" value=\"List\"\/&gt;\r\n                    &lt;t:submit t:event=\"cancelUpdate\" t:context=\"course.uuid\" value=\"Cancel\"\/&gt;\r\n                    &lt;input t:type=\"submit\" value=\"Save\"\/&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/t:if&gt;\r\n            &lt;t:if negate=\"true\" test=\"course\"&gt;\r\n                Course ${courseUuid} does not exist.&lt;br\/&gt;&lt;br\/&gt;\r\n            &lt;\/t:if&gt;\r\n        &lt;\/form&gt;    \r\n    &lt;\/t:if&gt;<\/pre>\n<p><strong>UPDATE java<\/strong><\/p>\n<p>Likewise the \u201cupdate\u201d java code looks a lot like the \u201ccreate\u201d java code. The biggest difference is that we have to be able to handle a race condition where a course has been deleted before we attempt to update the database.<\/p>\n<pre class=\" brush:java\">@Events({ Editor.TO_UPDATE, Editor.CANCEL_UPDATE,\r\n        Editor.SUCCESSFUL_UPDATE, Editor.FAILED_UPDATE })\r\npublic class Editor {\r\n    public static final String TO_UPDATE = \"toUpdate\";\r\n    public static final String CANCEL_UPDATE = \"cancelUpdate\";\r\n    public static final String SUCCESSFUL_UPDATE = \"successfulUpdate\";\r\n    public static final String FAILED_UPDATE = \"failedUpdate\";\r\n\r\n    public enum Mode {\r\n        CREATE, REVIEW, UPDATE;\r\n    }\r\n\r\n    \/\/ Parameters\r\n\r\n    @ActivationRequestParameter\r\n    @Property\r\n    private Mode mode;\r\n\r\n    @ActivationRequestParameter\r\n    @Property\r\n    private String courseUuid;\r\n\r\n    \/\/ Screen fields\r\n\r\n    @Property\r\n    private Course course;\r\n\r\n    @Property\r\n    @Persist(PersistenceConstants.FLASH)\r\n    private String deleteMessage;\r\n\r\n    \/\/ Work fields\r\n\r\n    \/\/ This carries version through the redirect that follows a server-side\r\n    \/\/ validation failure.\r\n    @Persist(PersistenceConstants.FLASH)\r\n    private Integer versionFlash;\r\n\r\n    \/\/ Generally useful bits and pieces\r\n\r\n    @Inject\r\n    private CourseFinderService courseFinderService;\r\n\r\n    @Inject\r\n    private CourseManagerService courseManagerService;\r\n\r\n    @Component\r\n    private CustomForm updateForm;\r\n\r\n    @Inject\r\n    private ComponentResources componentResources;\r\n\r\n    @InjectPage\r\n    private com.invariantproperties.sandbox.student.maintenance.web.pages.course.Index indexPage;\r\n\r\n    \/\/ The code\r\n\r\n    public void setup(Mode mode, String courseUuid) {\r\n        this.mode = mode;\r\n        this.courseUuid = courseUuid;\r\n    }\r\n\r\n    \/\/ setupRender() is called by Tapestry right before it starts rendering the\r\n    \/\/ component.\r\n\r\n    void setupRender() {\r\n\r\n        if (mode == Mode.REVIEW) {\r\n            if (courseUuid == null) {\r\n                course = null;\r\n                \/\/ Handle null course in the template.\r\n            } else {\r\n                if (course == null) {\r\n                    try {\r\n                        course = courseFinderService.findCourseByUuid(courseUuid);\r\n                    } catch (ObjectNotFoundException e) {\r\n                        \/\/ Handle null course in the template.\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n    \/\/ UPDATE\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n    \/\/ Handle event \"cancelUpdate\"\r\n\r\n    Object onCancelUpdate(String courseUuid) {\r\n        return indexPage;\r\n    }\r\n\r\n    \/\/ Component \"updateForm\" bubbles up the PREPARE_FOR_RENDER event during\r\n    \/\/ form render\r\n\r\n    void onPrepareForRenderFromUpdateForm() {\r\n        try {\r\n            course = courseFinderService.findCourseByUuid(courseUuid);\r\n        } catch (ObjectNotFoundException e) {\r\n            \/\/ Handle null course in the template.\r\n        }\r\n\r\n        \/\/ If the form has errors then we're redisplaying after a redirect.\r\n        \/\/ Form will restore your input values but it's up to us to restore\r\n        \/\/ Hidden values.\r\n\r\n        if (updateForm.getHasErrors()) {\r\n            if (course != null) {\r\n                course.setVersion(versionFlash);\r\n            }\r\n        }\r\n    }\r\n\r\n    \/\/ Component \"updateForm\" bubbles up the PREPARE_FOR_SUBMIT event during for\r\n    \/\/ submission\r\n\r\n    void onPrepareForSubmitFromUpdateForm() {\r\n        \/\/ Get objects for the form fields to overlay.\r\n        try {\r\n            course = courseFinderService.findCourseByUuid(courseUuid);\r\n        } catch (ObjectNotFoundException e) {\r\n            course = new Course();\r\n            updateForm.recordError(\"Course has been deleted by another process.\");\r\n        }\r\n    }\r\n\r\n    \/\/ Component \"updateForm\" bubbles up the VALIDATE event when it is submitted\r\n\r\n    void onValidateFromUpdateForm() {\r\n\r\n        if (updateForm.getHasErrors()) {\r\n            \/\/ We get here only if a server-side validator detected an error.\r\n            return;\r\n        }\r\n\r\n        try {\r\n            courseManagerService\r\n                    .updateCourse(course, course.getName(), course.getSummary(), course.getDescription(), 1);\r\n        } catch (RestClientFailureException e) {\r\n            updateForm.recordError(\"Internal error on server.\");\r\n            updateForm.recordError(e.getMessage());\r\n        } catch (Exception e) {\r\n            \/\/ Display the cause. In a real system we would try harder to get a\r\n            \/\/ user-friendly message.\r\n            updateForm.recordError(ExceptionUtil.getRootCauseMessage(e));\r\n        }\r\n    }\r\n\r\n    \/\/ Component \"updateForm\" bubbles up SUCCESS or FAILURE when it is\r\n    \/\/ submitted, depending on whether VALIDATE\r\n    \/\/ records an error\r\n\r\n    boolean onSuccessFromUpdateForm() {\r\n        \/\/ We want to tell our containing page explicitly what course we've\r\n        \/\/ updated, so we trigger new event\r\n        \/\/ \"successfulUpdate\" with a parameter. It will bubble up because we\r\n        \/\/ don't have a handler method for it.\r\n        componentResources.triggerEvent(SUCCESSFUL_UPDATE, new Object[] { courseUuid }, null);\r\n\r\n        \/\/ We don't want \"success\" to bubble up, so we return true to say we've\r\n        \/\/ handled it.\r\n        mode = Mode.REVIEW;\r\n        return true;\r\n    }\r\n\r\n    boolean onFailureFromUpdateForm() {\r\n        versionFlash = course.getVersion();\r\n\r\n        \/\/ Rather than letting \"failure\" bubble up which doesn't say what you\r\n        \/\/ were trying to do, we trigger new event\r\n        \/\/ \"failedUpdate\". It will bubble up because we don't have a handler\r\n        \/\/ method for it.\r\n        componentResources.triggerEvent(FAILED_UPDATE, new Object[] { courseUuid }, null);\r\n        \/\/ We don't want \"failure\" to bubble up, so we return true to say we've\r\n        \/\/ handled it.\r\n        return true;\r\n    }\r\n}<\/pre>\n<h4>DELETE template and java<\/h4>\n<p>The editor doesn\u2019t have an explicit \u201cdelete\u201d mode but it does support deleting the current object on the review and update pages.<\/p>\n<pre class=\" brush:java\">\/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n    \/\/ DELETE\r\n    \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n    \/\/ Handle event \"delete\"\r\n\r\n    Object onDelete(String courseUuid) {\r\n        this.courseUuid = courseUuid;\r\n        int courseVersion = 0;\r\n\r\n        try {\r\n            courseManagerService.deleteCourse(courseUuid, courseVersion);\r\n        } catch (ObjectNotFoundException e) {\r\n            \/\/ the object's already deleted\r\n        } catch (RestClientFailureException e) {\r\n            createForm.recordError(\"Internal error on server.\");\r\n            createForm.recordError(e.getMessage());\r\n\r\n            \/\/ Display the cause. In a real system we would try harder to get a\r\n            \/\/ user-friendly message.\r\n            deleteMessage = ExceptionUtil.getRootCauseMessage(e);\r\n\r\n            \/\/ Trigger new event \"failedDelete\" which will bubble up.\r\n            componentResources.triggerEvent(FAILED_DELETE, new Object[] { courseUuid }, null);\r\n            \/\/ We don't want \"delete\" to bubble up, so we return true to say\r\n            \/\/ we've handled it.\r\n            return true;\r\n        } catch (Exception e) {\r\n            \/\/ Display the cause. In a real system we would try harder to get a\r\n            \/\/ user-friendly message.\r\n            deleteMessage = ExceptionUtil.getRootCauseMessage(e);\r\n\r\n            \/\/ Trigger new event \"failedDelete\" which will bubble up.\r\n            componentResources.triggerEvent(FAILED_DELETE, new Object[] { courseUuid }, null);\r\n            \/\/ We don't want \"delete\" to bubble up, so we return true to say\r\n            \/\/ we've handled it.\r\n            return true;\r\n        }\r\n\r\n        \/\/ Trigger new event \"successfulDelete\" which will bubble up.\r\n        componentResources.triggerEvent(SUCCESFUL_DELETE, new Object[] { courseUuid }, null);\r\n        \/\/ We don't want \"delete\" to bubble up, so we return true to say we've\r\n        \/\/ handled it.\r\n        return indexPage;\r\n    }<\/pre>\n<h2>Next Steps<\/h2>\n<p>The obvious next steps are improving the error messages, adding support for pagination, support, and one-to-many and many-to-many relationships. All will require revising the REST payloads. I have a few additional items in the pipeline, e.g., an ExceptionService, to say nothing of the security issues.<\/p>\n<h2>Source Code<\/h2>\n<ul>\n<li>The source code is available at <a href=\"http:\/\/code.google.com\/p\/invariant-properties-blog\/source\/browse\/student\">http:\/\/code.google.com\/p\/invariant-properties-blog\/source\/browse\/student<\/a>.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<div style=\"border: 1px solid #D8D8D8; background: #FAFAFA; width: 100%; padding-left: 5px;\"><b><i>Reference: <\/i><\/b><a href=\"http:\/\/invariantproperties.com\/2014\/01\/04\/project-student-maintenance-webapp-editable\/\">Project Student: Maintenance Webapp (editable)<\/a> from our <a href=\"http:\/\/www.javacodegeeks.com\/jcg\">JCG partner<\/a> Bear Giles at the <a href=\"http:\/\/invariantproperties.com\/\">Invariant Properties<\/a> blog.<\/div>\n","protected":false},"excerpt":{"rendered":"<p>This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring Data, Sharding Integration Test Data, Webservice Integration, JPA Criteria Queries and Maintenance Webapp (read-only). Last time we created a simple webapp that allows us to take a quick peek into the database. It &hellip;<\/p>\n","protected":false},"author":113,"featured_media":112,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[915],"class_list":["post-20788","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-enterprise-java","tag-apache-tapestry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Project Student: Maintenance Webapp (editable)<\/title>\n<meta name=\"description\" content=\"This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Project Student: Maintenance Webapp (editable)\" \/>\n<meta property=\"og:description\" content=\"This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html\" \/>\n<meta property=\"og:site_name\" content=\"Java Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/javacodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2014-02-01T13:00:49+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Bear Giles\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@javacodegeeks\" \/>\n<meta name=\"twitter:site\" content=\"@javacodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Bear Giles\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"20 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html\"},\"author\":{\"name\":\"Bear Giles\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/91196fd6369bac9f4ec7217ffbca53f9\"},\"headline\":\"Project Student: Maintenance Webapp (editable)\",\"datePublished\":\"2014-02-01T13:00:49+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html\"},\"wordCount\":990,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/enterprise-java-logo.jpg\",\"keywords\":[\"Apache Tapestry\"],\"articleSection\":[\"Enterprise Java\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html\",\"name\":\"Project Student: Maintenance Webapp (editable)\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/enterprise-java-logo.jpg\",\"datePublished\":\"2014-02-01T13:00:49+00:00\",\"description\":\"This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#primaryimage\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/enterprise-java-logo.jpg\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/enterprise-java-logo.jpg\",\"width\":150,\"height\":150,\"caption\":\"java-interview-questions-answers\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/02\\\/project-student-maintenance-webapp-editable.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Java\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/category\\\/java\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Enterprise Java\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/category\\\/java\\\/enterprise-java\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Project Student: Maintenance Webapp (editable)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\",\"name\":\"Java Code Geeks\",\"description\":\"Java Developers Resource Center\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"alternateName\":\"JCG\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.javacodegeeks.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2022\\\/06\\\/exelixis-logo.png\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2022\\\/06\\\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/javacodegeeks\",\"https:\\\/\\\/x.com\\\/javacodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/91196fd6369bac9f4ec7217ffbca53f9\",\"name\":\"Bear Giles\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c4e8f47b520b4147cb7f173f9d78cf8862974fdeeff4baea9d6a632cf7b1b54c?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c4e8f47b520b4147cb7f173f9d78cf8862974fdeeff4baea9d6a632cf7b1b54c?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c4e8f47b520b4147cb7f173f9d78cf8862974fdeeff4baea9d6a632cf7b1b54c?s=96&d=mm&r=g\",\"caption\":\"Bear Giles\"},\"sameAs\":[\"http:\\\/\\\/invariantproperties.com\\\/\"],\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/author\\\/Bear-Giles\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Project Student: Maintenance Webapp (editable)","description":"This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html","og_locale":"en_US","og_type":"article","og_title":"Project Student: Maintenance Webapp (editable)","og_description":"This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring","og_url":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html","og_site_name":"Java Code Geeks","article_publisher":"https:\/\/www.facebook.com\/javacodegeeks","article_published_time":"2014-02-01T13:00:49+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg","type":"image\/jpeg"}],"author":"Bear Giles","twitter_card":"summary_large_image","twitter_creator":"@javacodegeeks","twitter_site":"@javacodegeeks","twitter_misc":{"Written by":"Bear Giles","Est. reading time":"20 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#article","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html"},"author":{"name":"Bear Giles","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/91196fd6369bac9f4ec7217ffbca53f9"},"headline":"Project Student: Maintenance Webapp (editable)","datePublished":"2014-02-01T13:00:49+00:00","mainEntityOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html"},"wordCount":990,"commentCount":0,"publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg","keywords":["Apache Tapestry"],"articleSection":["Enterprise Java"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html","url":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html","name":"Project Student: Maintenance Webapp (editable)","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#primaryimage"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg","datePublished":"2014-02-01T13:00:49+00:00","description":"This is part of Project Student. Other posts are Webservice Client With Jersey, Webservice Server with Jersey, Business Layer, Persistence with Spring","breadcrumb":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#primaryimage","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg","width":150,"height":150,"caption":"java-interview-questions-answers"},{"@type":"BreadcrumbList","@id":"https:\/\/www.javacodegeeks.com\/2014\/02\/project-student-maintenance-webapp-editable.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.javacodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Java","item":"https:\/\/www.javacodegeeks.com\/category\/java"},{"@type":"ListItem","position":3,"name":"Enterprise Java","item":"https:\/\/www.javacodegeeks.com\/category\/java\/enterprise-java"},{"@type":"ListItem","position":4,"name":"Project Student: Maintenance Webapp (editable)"}]},{"@type":"WebSite","@id":"https:\/\/www.javacodegeeks.com\/#website","url":"https:\/\/www.javacodegeeks.com\/","name":"Java Code Geeks","description":"Java Developers Resource Center","publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"alternateName":"JCG","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.javacodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.javacodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.javacodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/javacodegeeks","https:\/\/x.com\/javacodegeeks"]},{"@type":"Person","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/91196fd6369bac9f4ec7217ffbca53f9","name":"Bear Giles","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/c4e8f47b520b4147cb7f173f9d78cf8862974fdeeff4baea9d6a632cf7b1b54c?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/c4e8f47b520b4147cb7f173f9d78cf8862974fdeeff4baea9d6a632cf7b1b54c?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c4e8f47b520b4147cb7f173f9d78cf8862974fdeeff4baea9d6a632cf7b1b54c?s=96&d=mm&r=g","caption":"Bear Giles"},"sameAs":["http:\/\/invariantproperties.com\/"],"url":"https:\/\/www.javacodegeeks.com\/author\/Bear-Giles"}]}},"_links":{"self":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/20788","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/users\/113"}],"replies":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/comments?post=20788"}],"version-history":[{"count":0,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/20788\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/media\/112"}],"wp:attachment":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/media?parent=20788"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/categories?post=20788"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/tags?post=20788"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}