{"id":20707,"date":"2014-01-28T13:00:23","date_gmt":"2014-01-28T11:00:23","guid":{"rendered":"http:\/\/www.javacodegeeks.com\/?p=20707"},"modified":"2014-01-27T21:48:36","modified_gmt":"2014-01-27T19:48:36","slug":"project-student-maintenance-webapp-read-only","status":"publish","type":"post","link":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html","title":{"rendered":"Project Student: Maintenance Webapp (read-only)"},"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> and <a href=\"http:\/\/invariantproperties.com\/2013\/12\/29\/project-student-jpa-criteria-queries\/\">JPA Criteria Queries<\/a>.<\/p>\n<p>When I started this project I had four goals. In no particular order they were to:<br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n&nbsp;<\/p>\n<ul>\n<li>learn about jQuery and other AJAX technologies. For that I needed a REST server I understood,<\/li>\n<li>capture recently acquired knowledge about jersey and tapestry,<\/li>\n<li>create a framework I could use to learn about other technologies (e.g., spring MVC, restlet, netty), and<\/li>\n<li>have something to discuss in job interviews<\/li>\n<\/ul>\n<p>If it was useful to others \u2013 great! That\u2019s why it\u2019s available under the Apache license.<\/p>\n<p>(It should go without saying that acceptable uses does not include turning \u201cProject Student\u201d into a student project without proper attribution!)<\/p>\n<p>The problem with learning AJAX is that I\u2019ll initially be uncertain where the problem lies. Is it bad jQuery? A bad REST service? Something else? The extensive unit and integration tests are a good start but there will always be some uncertainty.<\/p>\n<p>The other consideration is that in the real world we\u2019ll often need a basic view into the database. It won\u2019t be for public consumption \u2013 it\u2019s for internal use when we hit a WTF moment. It can also be used to maintain information that we don\u2019t want to manage via a public interface, e.g., values in pulldown menus.<\/p>\n<p>A small twist on this can be used to provide a modest level of scalability. Use hefty servers for your database and REST service, then have N front-end servers that run conventional webapps that act as an intermediary between the user and the REST service. The front-end servers can be fairly lightweight and spun up on an as-needed basis. Bonus points for putting a caching server between the front ends and the REST server since the overwhelming fraction of hits will be reads.<\/p>\n<p>This approach won\u2019t scale to Amazon or Facebook scales but it will be good enough for many sites.<\/p>\n<h2>Maintenance Webapp<\/h2>\n<p>This brings us to the optional layers of the webapp onion \u2013 a conventional webapp that acts as a frontend to the REST service. For various reasons I\u2019m using <a href=\"http:\/\/tapestry.apache.org\/index.html\">Tapestry 5<\/a> for the application but it\u2019s an arbitrary decision and I won\u2019t spend much time delving into Tapestry-specific code.<\/p>\n<p>You can create a new tapestry project with<\/p>\n<pre class=\" brush:java\">$ mvn archetype:generate -DarchetypeCatalog=http:\/\/tapestry.apache.org<\/pre>\n<p>I\u2019ve found the examples at <a href=\"http:\/\/jumpstart.doublenegative.com.au\/jumpstart\/examples\/\">http:\/\/jumpstart.doublenegative.com.au\/jumpstart\/examples\/<\/a> invaluable. I\u2019ve kept in attribution where appropriate.<\/p>\n<p>Later I will also create the second optional layer of the webapp \u2013 functional and regression tests with <a href=\"http:\/\/www.seleniumhq.org\/\">Selenium<\/a> and <a href=\"http:\/\/www.seleniumhq.org\/\">WebDriver<\/a> (that is, Selenium 2.0).<\/p>\n<h2>Limitations<\/h2>\n<p><strong>Read-only<\/strong> \u2013 spinning up the webapp takes a lot of work so the initial version will only provide read-only access of simple tables. No updates, no one-to-many mappings.<\/p>\n<p><strong>User Authentication<\/strong> \u2013 no effort has been made to authenticate users.<\/p>\n<p><strong>Encryption<\/strong> \u2013 no effort has been made to encrypt communications.<\/p>\n<p><strong>Database Locks<\/strong> \u2013 we\u2019re using opportunistic locking with hibernate versions instead of explicit database locks. Security note: under the principle of least disclosure we don\u2019t want to make the version visible unless it\u2019s required. A good rule is that you\u2019ll see if it you request a specific object but not see it in lists.<\/p>\n<p><strong>REST Client<\/strong> \u2013 the default GET handler for each type is very crude \u2013 it just returns a list of objects. We need a more sophisticated response (e.g., the number of records, the start- and end-index, a status code, etc.) and will let the UI drive it. For now the only thing we really need is a count and we can just request the list and count the number of elements.<\/p>\n<h2>Goal<\/h2>\n<p>We want a page that lists all courses in the database. It does not need to worry about pagination, sorting, etc. It should have links (possibly inactive) for editing and deleting a record. It does not need to have a link to add a new course.<div style=\"display:inline-block; margin: 15px 0;\"> <div id=\"adngin-JavaCodeGeeks_incontent_video-0\" style=\"display:inline-block;\"><\/div> <\/div><\/p>\n<p>The page should look something like this:<\/p>\n<p><a href=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/project-maintenance.gif\"><img decoding=\"async\" class=\"aligncenter size-medium wp-image-21084\" alt=\"project-maintenance\" src=\"http:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2014\/01\/project-maintenance-300x230.gif\" width=\"300\" height=\"230\" \/><\/a><\/p>\n<h2>Course template<\/h2>\n<p>The Tapestry page listing courses is straightforward \u2013 it\u2019s basically just a decorated <em>grid<\/em> of values.<\/p>\n<p>(See the tapestry archetype for Layout.tml, etc.)<\/p>\n<pre class=\" brush:java\">&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        &lt;!-- Most of the page content, including &lt;head&gt;, &lt;body&gt;, etc. tags, comes from Layout.tml --&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:grid source=\"courses\" row=\"course\" include=\"uuid,name,creationdate\" add=\"edit,delete\"&gt;\r\n            &lt;p:name&gt;\r\n                &lt;t:pagelink page=\"CourseEditor\" context=\"course.uuid\"&gt;${course.name}&lt;\/t:pagelink&gt;\r\n            &lt;\/p:name&gt;\r\n            &lt;p:editcell&gt;\r\n                &lt;t:actionlink t:id=\"edit\" 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\"&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:pagelink page=\"Course\/Editor\" parameters=\"{ 'mode':'create', 'courseUuid':null }\"&gt;add some&lt;\/t:pagelink&gt;.&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\/List\"&gt;Courses&lt;\/t:pagelink&gt;\r\n            ]\r\n        &lt;\/p&gt;\r\n    &lt;\/p:sidebar&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>with a properties file of<\/p>\n<pre class=\" brush:java\">title=Courses\r\ndelete-course=Delete course?<\/pre>\n<p>I\u2019ve included the actionlinks for edit and delete but they\u2019re nonfunctional.<\/p>\n<h2>GridDataSources<\/h2>\n<p>Our page needs a source for the values to display. This requires two classes. The first defines the <em>courses<\/em> property used above.<\/p>\n<pre class=\" brush:java\">package com.invariantproperties.sandbox.student.maintenance.web.tables;\r\n\r\nimport com.invariantproperties.sandbox.student.business.CourseFinderService;\r\n\r\npublic class GridDataSources {\r\n    \/\/ Screen fields\r\n\r\n    @Property\r\n    private GridDataSource courses;\r\n\r\n    \/\/ Generally useful bits and pieces\r\n\r\n    @Inject\r\n    private CourseFinderService courseFinderService;\r\n\r\n    @InjectComponent\r\n    private Grid grid;\r\n\r\n    \/\/ The code\r\n\r\n    void setupRender() {\r\n        courses = new CoursePagedDataSource(courseFinderService);\r\n    }\r\n}<\/pre>\n<p>and the actual implementation is<\/p>\n<pre class=\" brush:java\">package com.invariantproperties.sandbox.student.maintenance.web.tables;\r\n\r\nimport com.invariantproperties.sandbox.student.business.CourseFinderService;\r\nimport com.invariantproperties.sandbox.student.domain.Course;\r\nimport com.invariantproperties.sandbox.student.maintenance.query.SortCriterion;\r\nimport com.invariantproperties.sandbox.student.maintenance.query.SortDirection;\r\n\r\npublic class CoursePagedDataSource implements GridDataSource {\r\n\r\n    private int startIndex;\r\n    private List&lt;Course&gt; preparedResults;\r\n\r\n    private final CourseFinderService courseFinderService;\r\n\r\n    public CoursePagedDataSource(CourseFinderService courseFinderService) {\r\n        this.courseFinderService = courseFinderService;\r\n    }\r\n\r\n    @Override\r\n    public int getAvailableRows() {\r\n        long count = courseFinderService.count();\r\n        return (int) count;\r\n    }\r\n\r\n    @Override\r\n    public void prepare(final int startIndex, final int endIndex, final List&lt;SortConstraint&gt; sortConstraints) {\r\n\r\n        \/\/ Get a page of courses - ask business service to find them (from the\r\n        \/\/ database)\r\n        \/\/ List&lt;SortCriterion&gt; sortCriteria = toSortCriteria(sortConstraints);\r\n        \/\/ preparedResults = courseFinderService.findCourses(startIndex,\r\n        \/\/ endIndex - startIndex + 1, sortCriteria);\r\n        preparedResults = courseFinderService.findAllCourses();\r\n\r\n        this.startIndex = startIndex;\r\n    }\r\n\r\n    @Override\r\n    public Object getRowValue(final int index) {\r\n        return preparedResults.get(index - startIndex);\r\n    }\r\n\r\n    @Override\r\n    public Class&lt;Course&gt; getRowType() {\r\n        return Course.class;\r\n    }\r\n\r\n    \/**\r\n     * Converts a list of Tapestry's SortConstraint to a list of our business\r\n     * tier's SortCriterion. The business tier does not use SortConstraint\r\n     * because that would create a dependency on Tapestry.\r\n     *\/\r\n    private List&lt;SortCriterion&gt; toSortCriteria(List&lt;SortConstraint&gt; sortConstraints) {\r\n        List&lt;SortCriterion&gt; sortCriteria = new ArrayList&lt;&gt;();\r\n\r\n        for (SortConstraint sortConstraint : sortConstraints) {\r\n\r\n            String propertyName = sortConstraint.getPropertyModel().getPropertyName();\r\n            SortDirection sortDirection = SortDirection.UNSORTED;\r\n\r\n            switch (sortConstraint.getColumnSort()) {\r\n            case ASCENDING:\r\n                sortDirection = SortDirection.ASCENDING;\r\n                break;\r\n            case DESCENDING:\r\n                sortDirection = SortDirection.DESCENDING;\r\n                break;\r\n            default:\r\n            }\r\n\r\n            SortCriterion sortCriterion = new SortCriterion(propertyName, sortDirection);\r\n            sortCriteria.add(sortCriterion);\r\n        }\r\n\r\n        return sortCriteria;\r\n    }\r\n}<\/pre>\n<h2>AppModule<\/h2>\n<p>Now that we have a GridDataSource we can see what it needs \u2013 a CourseFinderService. While there is a Tapestry-Spring integration we want to keep the maintenance webapp as thin as possible so for now we use standard Tapestry injection.<\/p>\n<pre class=\" brush:java\">package com.invariantproperties.sandbox.student.maintenance.web.services;\r\n\r\nimport com.invariantproperties.sandbox.student.business.CourseFinderService;\r\nimport com.invariantproperties.sandbox.student.business.CourseManagerService;\r\nimport com.invariantproperties.sandbox.student.maintenance.service.impl.CourseFinderServiceTapestryImpl;\r\nimport com.invariantproperties.sandbox.student.maintenance.service.impl.CourseManagerServiceTapestryImpl;\r\n\r\n\/**\r\n * This module is automatically included as part of the Tapestry IoC Registry,\r\n * it's a good place to configure and extend Tapestry, or to place your own\r\n * service definitions.\r\n *\/\r\npublic class AppModule {\r\n    public static void bind(ServiceBinder binder) {\r\n        binder.bind(CourseFinderService.class, CourseFinderServiceTapestryImpl.class);\r\n        binder.bind(CourseManagerService.class, CourseManagerServiceTapestryImpl.class);\r\n    }\r\n\r\n    ....\r\n}<\/pre>\n<p>Note that we\u2019re using the standard <em>CourseFinderService<\/em> interface with the tapestry-specific implementation. This means we can use the standard implementation directly with nothing more than a small change to the configuration files!<\/p>\n<h2>CourseFinderServiceTapestryImpl<\/h2>\n<p>The local implementation of the CourseFinderService interface must use the REST client instead of the Spring Data implementation. Using the outside-in approach used earlier the needs of the Tapestry template should drive the needs of the Service implementation and that, in turn, drives the needs of the REST client and server.<\/p>\n<pre class=\" brush:java\">package com.invariantproperties.sandbox.student.maintenance.service.impl;\r\n\r\npublic class CourseFinderServiceTapestryImpl implements CourseFinderService {\r\n    private final CourseFinderRestClient finder;\r\n\r\n    public CourseFinderServiceTapestryImpl() {\r\n        \/\/ resource should be loaded as tapestry resource\r\n        final String resource = \"http:\/\/localhost:8080\/student-ws-webapp\/rest\/course\/\";\r\n        finder = new CourseFinderRestClientImpl(resource);\r\n\r\n        \/\/ load some initial data\r\n        initCache(new CourseManagerRestClientImpl(resource));\r\n    }\r\n\r\n    @Override\r\n    public long count() {\r\n        \/\/ FIXME: grossly inefficient but good enough for now.\r\n        return finder.getAllCourses().length;\r\n    }\r\n\r\n    @Override\r\n    public long countByTestRun(TestRun testRun) {\r\n        \/\/ FIXME: grossly inefficient but good enough for now.\r\n        return finder.getAllCourses().length;\r\n    }\r\n\r\n    @Override\r\n    public Course findCourseById(Integer id) {\r\n        \/\/ unsupported operation!\r\n        throw new ObjectNotFoundException(id);\r\n    }\r\n\r\n    @Override\r\n    public Course findCourseByUuid(String uuid) {\r\n        return finder.getCourse(uuid);\r\n    }\r\n\r\n    @Override\r\n    public List&lt;Course&gt; findAllCourses() {\r\n        return Arrays.asList(finder.getAllCourses());\r\n    }\r\n\r\n    @Override\r\n    public List&lt;Course&gt; findCoursesByTestRun(TestRun testRun) {\r\n        return Collections.emptyList();\r\n    }\r\n\r\n    \/\/ method to load some test data into the database.\r\n    private void initCache(CourseManagerRestClient manager) {\r\n        manager.createCourse(\"physics 101\");\r\n        manager.createCourse(\"physics 201\");\r\n        manager.createCourse(\"physics 202\");\r\n    }\r\n}<\/pre>\n<p>Our JPA Criteria query can give us a quick count but our REST client doesn\u2019t support that yet.<\/p>\n<h2>Wrapping up<\/h2>\n<p>After we finish the grunt work we\u2019ll have a maintenance .war file. We can deploy it with the webservice .war on our appserver \u2013 or not. There\u2019s no reason the two .war files have to be on the same system other than the temporarily hardcoded URL for the webservice.<\/p>\n<p>We should first go to http:\/\/localhost:8080\/student-maintenance-webapp\/course\/list. We should see a short list of courses as shown above. (In that case I had restarted the webapp three times so each entry is duplicated three-fold.)<\/p>\n<p>Now we should go to our webservice webapp at http:\/\/localhost:8080\/student-ws-webapp\/rest\/course and verify that we can get data via the browser there as well. After a bit of cleanup we should see:<\/p>\n<pre class=\" brush:java\">{\"course\":\r\n [\r\n   {\r\n     \"creationDate\":\"2013-12-28T14:40:21.369-07:00\",\r\n     \"uuid\":\"500069e4-444d-49bc-80f0-4894c2d13f6a\",\r\n     \"version\":\"0\",\r\n     \"name\":\"physics 101\"\r\n   },\r\n   {\r\n     \"creationDate\":\"2013-12-28T14:40:21.777-07:00\",\r\n     \"uuid\":\"54001b2a-abbb-4a75-a289-e1f09173fa04\",\r\n     \"version\":\"0\",\r\n     \"name\":\"physics 201\"\r\n   },\r\n   {\r\n     \"creationDate\":\"2013-12-28T14:40:21.938-07:00\",\r\n     \"uuid\":\"cfaf892b-7ead-4d64-8659-8f87756bed62\",\r\n     \"version\":\"0\",\r\n     \"name\":\"physics 202\"\r\n   },\r\n   {\r\n     \"creationDate\":\"2013-12-28T16:17:54.608-07:00\",\r\n     \"uuid\":\"d29735ff-f614-4979-a0de-e1d134e859f4\",\r\n     \"version\":\"0\",\r\n     \"name\":\"physics 101\"\r\n   },\r\n   ....\r\n ]\r\n}<\/pre>\n<h2>Source Code<\/h2>\n<ul>\n<li>The source code is at <a href=\"https:\/\/github.com\/beargiles\/project-student\">https:\/\/github.com\/beargiles\/project-student<\/a> [github] and <a href=\"http:\/\/beargiles.github.io\/project-student\/\">http:\/\/beargiles.github.io\/project-student\/<\/a> [github pages].<\/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\/2013\/12\/30\/project-student-maintenance-webapp-read-only\/\">Project Student: Maintenance Webapp (read-only)<\/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 and JPA Criteria Queries. When I started this project I had four goals. In no particular order they were to: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &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-20707","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 (read-only)<\/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\/01\/project-student-maintenance-webapp-read-only.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 (read-only)\" \/>\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\/01\/project-student-maintenance-webapp-read-only.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-01-28T11:00:23+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=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html\"},\"author\":{\"name\":\"Bear Giles\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/91196fd6369bac9f4ec7217ffbca53f9\"},\"headline\":\"Project Student: Maintenance Webapp (read-only)\",\"datePublished\":\"2014-01-28T11:00:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html\"},\"wordCount\":1065,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.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\\\/01\\\/project-student-maintenance-webapp-read-only.html#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html\",\"name\":\"Project Student: Maintenance Webapp (read-only)\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/enterprise-java-logo.jpg\",\"datePublished\":\"2014-01-28T11:00:23+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\\\/01\\\/project-student-maintenance-webapp-read-only.html#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.html\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2014\\\/01\\\/project-student-maintenance-webapp-read-only.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\\\/01\\\/project-student-maintenance-webapp-read-only.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 (read-only)\"}]},{\"@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 (read-only)","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\/01\/project-student-maintenance-webapp-read-only.html","og_locale":"en_US","og_type":"article","og_title":"Project Student: Maintenance Webapp (read-only)","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\/01\/project-student-maintenance-webapp-read-only.html","og_site_name":"Java Code Geeks","article_publisher":"https:\/\/www.facebook.com\/javacodegeeks","article_published_time":"2014-01-28T11:00:23+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":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html#article","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html"},"author":{"name":"Bear Giles","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/91196fd6369bac9f4ec7217ffbca53f9"},"headline":"Project Student: Maintenance Webapp (read-only)","datePublished":"2014-01-28T11:00:23+00:00","mainEntityOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html"},"wordCount":1065,"commentCount":0,"publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.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\/01\/project-student-maintenance-webapp-read-only.html#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html","url":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html","name":"Project Student: Maintenance Webapp (read-only)","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html#primaryimage"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/enterprise-java-logo.jpg","datePublished":"2014-01-28T11:00:23+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\/01\/project-student-maintenance-webapp-read-only.html#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.html"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/2014\/01\/project-student-maintenance-webapp-read-only.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\/01\/project-student-maintenance-webapp-read-only.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 (read-only)"}]},{"@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\/20707","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=20707"}],"version-history":[{"count":0,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/20707\/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=20707"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/categories?post=20707"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/tags?post=20707"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}