tag:blogger.com,1999:blog-72031814918086832662024-03-14T07:20:06.789-07:00Enterprise 2.0 Application TechnologiesDiscussions of building enterprise applications leveraging Web 2.0+ concepts with technologies related to Java including: HTML5, Ajax, Spring Roo, GWT and other JavaScript and rich client libraries, while leveraging SOA, REST, ORM Persistence and the Semantic Net.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.comBlogger39125tag:blogger.com,1999:blog-7203181491808683266.post-39758476305837167912017-01-27T06:52:00.002-08:002017-01-27T06:52:58.647-08:00Working with Mobile Technologies<br />
<br />
<br />
Here is a tutorial I created to introduce myself to <a href="https://ionicframework.com/">Ionic Framework</a>: <a href="http://edlovesjava.github.io/">http://edlovesjava.github.io/</a>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-74577288030398939892012-10-14T06:52:00.002-07:002012-10-14T07:07:07.249-07:00Looking at new front-end tech stack: bootstrap + backboneRecently, I have been developing using a new front-end technology stack that includes <a href="http://twitter.github.com/bootstrap/" target="_blank">bootstrap</a> from Twitter and <a href="http://backbonejs.org/" target="_blank">backbone.js </a>(a subcomponent of <a href="http://documentcloud.org/" target="_blank">DocumentCloud.org</a>).<br />
<br />
Here is a partial list<br />
<br />
<ul>
<li>b<a href="http://twitter.github.com/bootstrap/" target="_blank">ootstrap </a></li>
<li><a href="http://backbonejs.org/" style="background-color: white;" target="_blank">backbone.js </a></li>
<li><a href="http://jquery.com/" target="_blank">jquery </a></li>
<li><a href="http://underscorejs.org/" target="_blank">underscore js</a></li>
<li><a href="http://requirejs.org/" target="_blank">require.js</a></li>
</ul>
<br />
<br />
<a href="http://twitter.github.com/bootstrap/" target="_blank">Bootstrap </a>has had a lot of adoption in the last few months. It offers a great out of the box set of styles and behaviors that make sense, allowing the quick development of '<a href="http://en.wikipedia.org/wiki/Responsive_Web_Design" target="_blank">responsive web design</a>' web designs that know the limits of the device at hand and respond by tailoring the layout appropriately. It requires <a href="http://jquery.com/" target="_blank">jquery </a>(of course) and builds on <a href="http://jquery.com/" target="_blank">jquery.com</a> by providing layout and default styles and a few basic components with great default behavior. Its CSS is managed nicely using <a href="http://lesscss.org/">less.js</a> to provide your CSS with variables, mixins, functions etc.<br />
<br />
<a href="http://backbonejs.org/" style="background-color: white;" target="_blank">Backbone </a><span style="background-color: white;">is a javascript library supporting an <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller" target="_blank">MVC </a>like framework to develop </span><span style="background-color: white;">single page web apps (rendering a complete UI in one html page)</span><span style="background-color: white;"> where </span><span style="background-color: white;">the only time to go to the server is to interact with REST resources for data. It organizes code into models, views and collections. It's 'controller' is implemented as a router that allows linking to behavior and routing as bookmark-able URLs for browser history. Views typically use client side template resolution implemented by <a href="http://underscorejs.org/" target="_blank">underscore js</a> and not implemented on the server. Models structure information passed to and from the server. Collections are collections of model objects that are associated with a REST resource on the server and support CRUD operations. </span><br />
<span style="background-color: white;"><br /></span>
<span style="background-color: white;">What is obvious by analyzing technology innovation on the front end is how much we rely on good javascript skills to make it all happen. However javascript allows as much clumsy programming as it offers in elegance (read <a href="http://javascript.crockford.com/" target="_blank">JavaScript The Good Parts by the Great Crockford</a>). Larger scale javascript code can be quite strange to read when trying to leverage the good parts and organizing js into classes and modules to scale. So pre-compilers like <a href="http://coffeescript.org/" target="_blank">CoffeeScript</a> and <a href="https://developers.google.com/closure/" target="_blank">Closure </a>offer amazing lexical simplification and cleaner code organization and optimization. Neither of which we are using currently, but we possibly may. The elegance and readability of coffee-script for instance or the compression and performance enhancements with Closure are very impressive.</span><br />
<span style="background-color: white;"><br /></span>
<span style="background-color: white;">Further, writing large scale javascript application requires more than just MVC, it requires modular development. Wrapping the javascript into good modular design is accomplished following the AMD pattern (as explained well in <a href="http://tagneto.blogspot.com/2012/01/simplicity-and-javascript-modules.html" target="_blank">this blog</a>). Using <a href="http://requirejs.org/" target="_blank">require.js</a> helps solve the problem of dependencies when javascript itself has no 'import' or 'module' commands, as well as allowing asynchronous loading and plugin architecture for the loading of different kinds of resources (like text)..</span><br />
<span style="background-color: white;"><br /></span>
<span style="background-color: white;">And what about testing? Our team is big on developer testing before tossing our project off to QA, and we like practicing <a href="http://en.wikipedia.org/wiki/Test-driven_development" target="_blank">TDD </a>(or now <a href="http://en.wikipedia.org/wiki/Behavior-driven_development" rel="" target="_blank">BDD</a>). There is <a href="http://qunitjs.com/" target="_blank">QUnit </a>from JQuery. But we are beginning to use <a href="http://pivotal.github.com/jasmine/" target="_blank">jasmine</a>. Jasmine, like QUnit runs tests and renders results on a nifty html page showing passes and fails. Integrating this will a build system for continuous integration (CI) is more of a challenge. That brings us to automated building, packaging, deploying, and continuous integration testing and analysis.</span><br />
<span style="background-color: white;"><br /></span>
Our back end is traditional J2EE. We expose REST resources via Jersey and interact with business services and information via EJB3 and JPA (and also the great<a href="http://www.oracle.com/us/technologies/soa/soa-suite-066466.html"> SOA Suite</a> layer from Oracle with BPEL, a Service Bus and traditional SOAP based service integration. We build using maven, continuously integrate with Hudson, run JUnit tests and generate reports on code coverage via <a href="http://www.eclemma.org/jacoco/" target="_blank">JaCoCo </a>or Coberatura. On the back end, we are used to automating this build and deploy cycle and assuring no breakage after each check in. But these tools and techniques are new to us on the front end side..<br />
<br />
We need solutions for automated and scripted builds, running and analyzing tests, code coverage and other static analysis using continuous integration techniques. At least that is what we are used to and expect. This process must/should integrate with our existing continuous integration and code analysis tools if possible. Tools offered as part of Node.js are possible solutions. Also maven has a javascript plugin. Many others exist as well. Further analysis is required on this. I will update progress on this blog.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-83310851255222520512012-10-05T09:34:00.000-07:002012-10-05T09:34:52.463-07:00The front-end Moves to the Front SeatSometimes the new is old. Back in the 'client-server' days (yes, I'm that old) the main development cost was on the client side. Using the likes of Powerbuilder, Visual Basic or other tools, developers would spend fantastic amounts of time on the front end connecting directly to a database, embedding sql (or proprietary data access api) and creating interactive screens and windows to present and manage data. A three tiered architecture would enhance the database operations with procedures or tirggers to enable server side processing. A brave few created purpose built server API's in C to support a true middle tier between the client and the database. And transaction managers and message oriented middleware were engaged to connect everything together. But even then, the main application logic would fall on the front end.<br />
<br />
The web moved us back to a more 'time share' IBM 360/CICS/3270 kind of model (no, I'm not that old) where the client took a back seat and the application logic was all rendered on the server. The browser was the new 'terminal' displaying html pages with form fields and submitting requests to the almighty server to dice up a screen for the response. Though the back end divided into multiple tiers separating application front-end logic (typically a markup language) from business logic (e.g. EJB) from the database and other resources (n-tier), the client (browser) was still basically a dumb terminal. There were some spikes to make the client smarter: applets, flash, etc. and some javascript to enhance the experience with more sophisticated controls. But the browser was still a speed bump to drive processing to the front end.<br />
<br />
Today the client is taking the front seat again. JavaScript, HTML5 and powerful and fast browsers have allowed much more sophisticated processing running on the client. New frameworks such as backbone js are allowing developers to build entire applications with a single page html dynamically rendered by javascript where the only interactions to the back end are to manipulate the data not render the screen. The client development is reminiscent of small talk, relying on Model View Controller (MVC) architecture to separate the display from the data and the control. The back end remains basically stateless providing a thin logical access to CRUD operations on data, and implementing business logic to shape, compute or aggregate data and coordinate functionality with other resources and tiers.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-66153395399270655862012-03-18T07:45:00.000-07:002012-03-25T06:27:18.984-07:00Getting started with Spring Roo, STS and GWT - Part 2In <a href="http://edlovesjava.blogspot.com/2011/07/getting-started-with-spring-roo-gwt-and.html">part one</a> of this multipart blog/article, we followed along with the<a href="http://static.springsource.org/spring-roo/reference/html/beginning.html"> Spring Roo Tutorial</a> creating a Pizza shop. But we stopped at the back end. Now its time to create front end. For this blog we will complete the tutorial and show the front end using Spring MVC and JSP pages. Next article we will do this with GWT.<br />
<br />
<br />
<ul>
<li><a href="http://edlovesjava.blogspot.com/2011/07/getting-started-with-spring-roo-gwt-and.html">Part 1 - Install and Create Back end</a></li>
<li><a href="http://edlovesjava.blogspot.com/2012/03/getting-started-with-spring-roo-sts-and.html">Part 2 - Web MVC</a> (this blog)</li>
<li>Part 3 - GWT </li>
<li>Part 4 - Custom app from scratch</li>
<li><br /></li>
</ul>
<br />
<br />
<br />
<span style="font-size: large;">Part 2 - Adding a front end with Spring MVC and JSP</span><br />
<br />
To summarize the previous blog,<br />
<br />
<ol>
<li>installed Spring StS</li>
<li>created a spring project named 'pizza' to package 'com.springsource.pizzashop'</li>
<li>executed the following in spring roo's shell:</li>
</ol>
<br />
<div>
<pre class="brush: shell">// Spring Roo 1.1.4.RELEASE [rev f787ce7] log opened at 2012-03-17 10:38:04
project --topLevelPackage com.springsource.roo.pizzashop --projectName pizza --java 6
// Spring Roo 1.1.4.RELEASE [rev f787ce7] log closed at 2012-03-17 10:38:15
// Spring Roo 1.1.4.RELEASE [rev f787ce7] log opened at 2012-03-17 10:38:19
hint
hint
persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
hint
entity --class ~.domain.Topping --testAutomatically
field string --fieldName name --notnull --sizeMin 2
entity --class ~.domain.Base --testAutomatically
field string --fieldName name --notNull --sizeMin 2
entity --class ~.domain.Pizza --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field number --fieldName price --type java.lang.Float
field set --fieldName toppings --type ~.domain.Topping
field reference --fieldName base --type ~.domain.Base
entity --class ~.domain.PizzaOrder --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field string --fieldName address --sizeMax 30
field number --fieldName total --type java.lang.Float
field date --fieldName deliveryDate --type java.util.Date
field set --fieldName pizzas --type ~.domain.Pizza
perform tests
</pre>
</div>
<br />
No we will add a user interface.<br />
<br />
<b>Step 1 - setup Spring MVC</b><br />
<br />
In Roo shell type:<br />
<ol>
<li>web mvc setup</li>
</ol>
<div>
The web mvc setup does a lot. We really need to drill down here if we hope to understand the science behind the magic. But you can skip to Step 2 if you are impatient. After the code is generated it may work in a basic way, but you always have to muck with the details to make it work the way you want it to.<br />
<br />
Here is the output from Spring Roo shell:</div>
<div>
<br /></div>
<div>
<pre class="brush: shell">Created SRC_MAIN_WEBAPP\WEB-INF\spring
Created SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml
Created SRC_MAIN_WEBAPP\WEB-INF\web.xml
Created SRC_MAIN_WEBAPP\images
Created SRC_MAIN_WEBAPP\images\create.png
Created SRC_MAIN_WEBAPP\images\list.png
Created SRC_MAIN_WEBAPP\images\resultset_previous.png
Created SRC_MAIN_WEBAPP\images\resultset_next.png
Created SRC_MAIN_WEBAPP\images\show.png
Created SRC_MAIN_WEBAPP\images\favicon.ico
Created SRC_MAIN_WEBAPP\images\delete.png
Created SRC_MAIN_WEBAPP\images\resultset_first.png
Created SRC_MAIN_WEBAPP\images\springsource-logo.png
Created SRC_MAIN_WEBAPP\images\resultset_last.png
Created SRC_MAIN_WEBAPP\images\add.png
Created SRC_MAIN_WEBAPP\images\banner-graphic.png
Created SRC_MAIN_WEBAPP\images\update.png
Created SRC_MAIN_WEBAPP\styles
Created SRC_MAIN_WEBAPP\styles\alt.css
Created SRC_MAIN_WEBAPP\styles\standard.css
Created SRC_MAIN_WEBAPP\WEB-INF\classes
Created SRC_MAIN_WEBAPP\WEB-INF\classes\standard.properties
Created SRC_MAIN_WEBAPP\WEB-INF\classes\alt.properties
Created SRC_MAIN_WEBAPP\WEB-INF\layouts
Created SRC_MAIN_WEBAPP\WEB-INF\layouts\default.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\layouts\layouts.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views
Created SRC_MAIN_WEBAPP\WEB-INF\views\header.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\footer.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\views.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views\index.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\index-template.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\uncaughtException.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\resourceNotFound.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\dataAccessFailure.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\update.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\create.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\dependency.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\show.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\list.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\find.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\select.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\display.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\column.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\editor.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\checkbox.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\simple.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\input.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\textarea.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\datetime.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\table.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\form\fields\reference.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\menu
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util\pagination.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util\theme.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util\placeholder.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util\panel.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util\language.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\util\load-scripts.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\i18n
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages.properties
Created SRC_MAIN_WEBAPP\images\en.png
Updated SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Updated SRC_MAIN_WEBAPP\WEB-INF\web.xml
Updated ROOT\pom.xml [added dependencies org.springframework:spring-web:${spring.version}, org.springframework:spring-webmvc:${spring.version}, org.springframework.webflow:spring-js-resources:2.2.1.RELEASE, commons-digester:commons-digester:2.0, commons-fileupload:commons-fileupload:1.2.1, javax.servlet:jstl:1.2, javax.el:el-api:1.0, joda-time:joda-time:1.6, javax.servlet.jsp:jsp-api:2.1, commons-codec:commons-codec:1.4; updated project type to war; added dependencies org.apache.tiles:tiles-core:2.2.1, org.apache.tiles:tiles-jsp:2.2.1]
Updated SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\footer.jspx
</pre>
<br /></div>
<div>
Let's look at a couple of things here to understand what happened. This is just the 'framework' of supporting configurations and code of a fully functional web application based on Spring MVC with JSP pages and Apache Tiles with layout. In spring MVC, one place to look in Spring MVC is the spring config file so see how your application is configured, here it is found in SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml<br />
<br /></div>
<div>
<pre class="brush: xml"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- The controllers are autodetected POJOs labeled with the @Controller annotation. -->
<context:component-scan base-package="com.springsource.roo.pizzashop" use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<!-- Turns on support for mapping requests to Spring MVC @Controller methods
Also registers default Formatters and Validators for use across all @Controllers -->
<mvc:annotation-driven/>
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources -->
<mvc:resources location="/, classpath:/META-INF/web-resources/" mapping="/resources/**"/>
<!-- Allows for mapping the DispatcherServlet to "/" by forwarding static resource requests to the container's default Servlet -->
<mvc:default-servlet-handler/>
<!-- register "global" interceptor beans to apply to all registered HandlerMappings -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="lang"/>
</mvc:interceptors>
<!-- selects a static view for rendering without the need for an explicit controller -->
<mvc:view-controller path="/" view-name="index"/>
<mvc:view-controller path="/uncaughtException"/>
<mvc:view-controller path="/resourceNotFound"/>
<mvc:view-controller path="/dataAccessFailure"/>
<!-- Resolves localized messages*.properties and application.properties files in the application to allow for internationalization.
The messages*.properties files translate Roo generated messages which are part of the admin interface, the application.properties
resource bundle localizes all application specific messages such as entity names and menu items. -->
<bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="messageSource" p:basenames="WEB-INF/i18n/messages,WEB-INF/i18n/application" p:fallbackToSystemLocale="false"/>
<!-- store preferred language configuration in a cookie -->
<bean class="org.springframework.web.servlet.i18n.CookieLocaleResolver" id="localeResolver" p:cookieName="locale"/>
<!-- resolves localized <theme_name>.properties files in the classpath to allow for theme support -->
<bean class="org.springframework.ui.context.support.ResourceBundleThemeSource" id="themeSource"/>
<!-- store preferred theme configuration in a cookie -->
<bean class="org.springframework.web.servlet.theme.CookieThemeResolver" id="themeResolver" p:cookieName="theme" p:defaultThemeName="standard"/>
<!-- This bean resolves specific types of exceptions to corresponding logical - view names for error views.
The default behaviour of DispatcherServlet - is to propagate all exceptions to the servlet container:
this will happen - here with all other types of exceptions. -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" p:defaultErrorView="uncaughtException">
<property name="exceptionMappings">
<props>
<prop key=".DataAccessException">dataAccessFailure</prop>
<prop key=".NoSuchRequestHandlingMethodException">resourceNotFound</prop>
<prop key=".TypeMismatchException">resourceNotFound</prop>
<prop key=".MissingServletRequestParameterException">resourceNotFound</prop>
</props>
</property>
</bean>
<!-- allows for integration of file upload functionality -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>
<bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer" id="tilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layouts/layouts.xml</value>
<!-- Scan views directory for Tiles configurations -->
<value>/WEB-INF/views/**/views.xml</value>
</list>
</property>
</bean>
</beans>
</pre>
</div>
<br />
Most of this is well documented and standard. But I would point out a couple of things.
The top of this configuration defines how controllers are found. In spring MVC, rather than a monolythic servlet, the url requests are dispatched to controllers. Controllers are java classes annotated with @Controller that handle the requests and generate responses (typically by delegating to a view resolver that can render a jspx page or a tiles definition (or other technology)).
<br />
The below snippet shows that we are using spring MVCs component scan ccapability to find controllers by looking for the Controller stereotype (@Controller marker) in the com.springsource.roo.pizzashop base package. The next command will generate these contollers for each domain class we have created.
<br />
<br />
<div>
<pre class="brush: xml"><!-- The controllers are autodetected POJOs labeled with the @Controller annotation. -->
<context:component-scan base-package="com.springsource.roo.pizzashop" use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<!-- Turns on support for mapping requests to Spring MVC @Controller methods
Also registers default Formatters and Validators for use across all @Controllers -->
<mvc:annotation-driven/>
</pre>
</div>
<br />
<br />
The last part, usage of Titles. this bean configuration shows where the layouts.xml and views.xml files are. You will need to understand Tiles<br />
<br />
<div>
<pre class="brush: shell"><bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>
<bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer" id="tilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layouts/layouts.xml</value>
<!-- Scan views directory for Tiles configurations -->
<value>/WEB-INF/views/**/views.xml</value>
</list>
</property>
</bean>
</pre>
</div>
<br />
The layout.xml class defines some basic arrangements of the pages that we can reuse. Tiles 'composes' pages from smaller parts based on these definition files.
<br />
<div>
<pre class="brush: shell"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name="default" template="/WEB-INF/layouts/default.jspx">
<put-attribute name="header" value="/WEB-INF/views/header.jspx" />
<put-attribute name="menu" value="/WEB-INF/views/menu.jspx" />
<put-attribute name="footer" value="/WEB-INF/views/footer.jspx" />
</definition>
<definition name="public" template="/WEB-INF/layouts/default.jspx">
<put-attribute name="header" value="/WEB-INF/views/header.jspx" />
<put-attribute name="footer" value="/WEB-INF/views/footer.jspx" />
</definition>
</tiles-definitions>
</pre>
</div>
<br />
<br />
And the views.xml file contains configurations of specific pages. when spring navigates to a page, the view resolver translates the view names found here into tiles configurations, which then allow tiles to render the final page by assembling the pieces together. Notice the view definitions 'extend' the definitions in layouts.xml. Layouts.xml reference the exact layout jspx pages and standard parts (header, menu, footer) that are reusable. The view definition supplies the body that is unique to the view.
Initially only standard 'error' pages are configured. The next command will add specific pages generated based on analyzing the domain classes we have created in Roo.
<br />
<br />
<div>
<pre class="brush: xml"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name="index" extends="default">
<put-attribute name="body" value="/WEB-INF/views/index.jspx" />
</definition>
<definition name="dataAccessFailure" extends="public">
<put-attribute name="body" value="/WEB-INF/views/dataAccessFailure.jspx" />
</definition>
<definition name="resourceNotFound" extends="public">
<put-attribute name="body" value="/WEB-INF/views/resourceNotFound.jspx" />
</definition>
<definition name="uncaughtException" extends="public">
<put-attribute name="body" value="/WEB-INF/views/uncaughtException.jspx" />
</definition>
</tiles-definitions>
</pre>
</div>
<br />
<br />
<div>
<b>Step 2 - create the scaffold pages for our classes</b><br />
<ol>
<li>web mvc all --package ~.web </li>
</ol>
<div>
This also does a lot of code generation.</div>
<div>
<br /></div>
<div>
Here is what Roo responded with:</div>
<div>
<br /></div>
<div>
<pre class="brush: shell">Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\BaseController.java
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\PizzaOrderController.java
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\ToppingController.java
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\PizzaController.java
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\ApplicationConversionServiceFactoryBean.java
Created SRC_MAIN_WEBAPP\WEB-INF\views\toppings
Created SRC_MAIN_WEBAPP\WEB-INF\views\toppings\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\toppings\views.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\menu\menu.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\menu\item.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\menu\category.tagx
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\views\toppings\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Created SRC_MAIN_WEBAPP\WEB-INF\views\bases
Created SRC_MAIN_WEBAPP\WEB-INF\views\bases\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\bases\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\views\bases\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzas
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\views.xml
Updated SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Updated SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Updated SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\ApplicationConversionServiceFactoryBean_Roo_ConversionService.aj
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\ToppingController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP\WEB-INF\views\toppings\list.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\toppings\show.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\toppings\create.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\toppings\update.jspx
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\BaseController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP\WEB-INF\views\bases\list.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\bases\show.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\bases\create.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\bases\update.jspx
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\PizzaController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\list.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\show.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\create.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzas\update.jspx
Created SRC_MAIN_JAVA\com\springsource\roo\pizzashop\web\PizzaOrderController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\list.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\show.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\create.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\pizzaorders\update.jspx
</pre>
<br /></div>
<div>
Looking at this output we can see that there is some repition. For each main domain class (Toppping, Bse, Pizza and PizzaOrder) we generate a controller and four jspx pages (views) list.jspx, show.jspx, create.jspx and update.jspx tied together in a menu system (menu.jspx). For tiles, each has its own views.xml file to show what layout is used.<br />
<br />
Lets look at a couple of parts. Here is the controllers it created for Pizza:<br />
<br />
File: ~.web.PizzaController.java<br />
<br />
<div>
<pre class="brush: java">package com.springsource.roo.pizzashop.web;
import com.springsource.roo.pizzashop.domain.Pizza;
import org.springframework.roo.addon.web.mvc.controller.RooWebScaffold;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RooWebScaffold(path = "pizzas", formBackingObject = Pizza.class)
@RequestMapping("/pizzas")
@Controller
public class PizzaController {
}
</pre>
<br />
<br />
This looks pretty simple. From spring mvc the @Controller annoation marks this as a controller for the dispatcher to find. The @RequestMapping annotation indicates how to find this controller based on the request uri.
The @RooWebScaffold annotation is the secret sauce that supports the Roo generated scaffold application, allowing for CRUD based functionality with List and Form views.
<br />
<br />
In fact, the annotations leverage several aspects that inject a lot of behavior here. STS can show you these using the Cross references panel to the right of the code (typically)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7g5afS2b32CJvY9B1_AmLJ8hyphenhyphen8uTyQ_6pIhMhol_RD9gzJRSj1nzBsv-4YoZbjSYE0ga11vYmXZ-kc3Hbrt5c1_wtZyfh3UIyAzaiXnHph40YsUVNvbMrYNincyWaPydlfESt2bdULINL/s1600/Java+-+pizzasrcmainjavacomspringsourceroopizzashopwebPizzaController.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="297" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7g5afS2b32CJvY9B1_AmLJ8hyphenhyphen8uTyQ_6pIhMhol_RD9gzJRSj1nzBsv-4YoZbjSYE0ga11vYmXZ-kc3Hbrt5c1_wtZyfh3UIyAzaiXnHph40YsUVNvbMrYNincyWaPydlfESt2bdULINL/s320/Java+-+pizzasrcmainjavacomspringsourceroopizzashopwebPizzaController.png" width="320" /></a></div>
<br />
We can see here, that the aspects inject methods like:<br />
<br />
<ul>
<li>create</li>
<li>createForm</li>
<li>delete</li>
<li>encode</li>
<li>list</li>
<li>populateBases (many-one relationship)</li>
<li>populatePizzas (for lists)</li>
<li>populateToppings (many-many relationship)</li>
</ul>
<br />
Each one of these methods is very useful and relatively generic. Because roo generates this aspect j inter type generation ITD file (see <a href="http://static.springsource.org/spring-roo/reference/html/architecture.html">spring roo architecture</a> page) which will add these neumerous methods to your controller separating the maintenance of your customization of you particular controller class from this standardized template like methods.<br />
<br />
For real applications, to customize behavior, we need to understand what is injected in the ITD.<br />
<br />
Generated PizzaController_Roo_Controller.aj<br />
<div>
<pre class="brush: java">// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package com.springsource.roo.pizzashop.web;
import com.springsource.roo.pizzashop.domain.Base;
import com.springsource.roo.pizzashop.domain.Pizza;
import com.springsource.roo.pizzashop.domain.Topping;
import java.io.UnsupportedEncodingException;
import java.lang.Integer;
import java.lang.Long;
import java.lang.String;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;
privileged aspect PizzaController_Roo_Controller {
@RequestMapping(method = RequestMethod.POST)
public String PizzaController.create(@Valid Pizza pizza, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
uiModel.addAttribute("pizza", pizza);
return "pizzas/create";
}
uiModel.asMap().clear();
pizza.persist();
return "redirect:/pizzas/" + encodeUrlPathSegment(pizza.getId().toString(), httpServletRequest);
}
@RequestMapping(params = "form", method = RequestMethod.GET)
public String PizzaController.createForm(Model uiModel) {
uiModel.addAttribute("pizza", new Pizza());
return "pizzas/create";
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String PizzaController.show(@PathVariable("id") Long id, Model uiModel) {
uiModel.addAttribute("pizza", Pizza.findPizza(id));
uiModel.addAttribute("itemId", id);
return "pizzas/show";
}
@RequestMapping(method = RequestMethod.GET)
public String PizzaController.list(@RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, Model uiModel) {
if (page != null || size != null) {
int sizeNo = size == null ? 10 : size.intValue();
uiModel.addAttribute("pizzas", Pizza.findPizzaEntries(page == null ? 0 : (page.intValue() - 1) * sizeNo, sizeNo));
float nrOfPages = (float) Pizza.countPizzas() / sizeNo;
uiModel.addAttribute("maxPages", (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages));
} else {
uiModel.addAttribute("pizzas", Pizza.findAllPizzas());
}
return "pizzas/list";
}
@RequestMapping(method = RequestMethod.PUT)
public String PizzaController.update(@Valid Pizza pizza, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
uiModel.addAttribute("pizza", pizza);
return "pizzas/update";
}
uiModel.asMap().clear();
pizza.merge();
return "redirect:/pizzas/" + encodeUrlPathSegment(pizza.getId().toString(), httpServletRequest);
}
@RequestMapping(value = "/{id}", params = "form", method = RequestMethod.GET)
public String PizzaController.updateForm(@PathVariable("id") Long id, Model uiModel) {
uiModel.addAttribute("pizza", Pizza.findPizza(id));
return "pizzas/update";
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String PizzaController.delete(@PathVariable("id") Long id, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, Model uiModel) {
Pizza.findPizza(id).remove();
uiModel.asMap().clear();
uiModel.addAttribute("page", (page == null) ? "1" : page.toString());
uiModel.addAttribute("size", (size == null) ? "10" : size.toString());
return "redirect:/pizzas";
}
@ModelAttribute("bases")
public Collection<base></base> PizzaController.populateBases() {
return Base.findAllBases();
}
@ModelAttribute("pizzas")
public Collection<pizza> PizzaController.populatePizzas() {
return Pizza.findAllPizzas();
}
@ModelAttribute("toppings")
public Collection<topping> PizzaController.populateToppings() {
return Topping.findAllToppings();
}
String PizzaController.encodeUrlPathSegment(String pathSegment, HttpServletRequest httpServletRequest) {
String enc = httpServletRequest.getCharacterEncoding();
if (enc == null) {
enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
}
try {
pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
}
catch (UnsupportedEncodingException uee) {}
return pathSegment;
}
}
</topping></pizza></pre>
</div>
<br />
<br />
<br />
<b>Step 3 - Run the Application on the Web Server</b><br />
<br />
Right click the application 'pizza' and choose Run... > Run on server. This will open the run on server dialog.<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT6WPMtErOwKHca3FA3OcCOAHPl_p7miF-FsB8e3BK9YBYWuBfln9roM71frOFTqtq2Vc1403wk1vGSJ6RunS-Cn7dO8V7ro499UStRGnWOc6O-l3p2fUIMMb_h9x5bjdmQoIlYU7w11tY/s1600/Run+On+Server_2012-03-18_10-23-51.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT6WPMtErOwKHca3FA3OcCOAHPl_p7miF-FsB8e3BK9YBYWuBfln9roM71frOFTqtq2Vc1403wk1vGSJ6RunS-Cn7dO8V7ro499UStRGnWOc6O-l3p2fUIMMb_h9x5bjdmQoIlYU7w11tY/s320/Run+On+Server_2012-03-18_10-23-51.png" width="245" /></a></div>
<br /></div>
Select the default app server. STS already configures the VMWare vFabric server which will work for us for the spring mvc. For GWT eventually we will choose other options (see next articles).<br />
<br />
Press Next and make sure the pizza application is deployed (moved from Available to Configured)<br />
And press 'Finish'<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSslFSyxyYs5YiI8_h2uo_loFqrU3yzPnxz-SID8GQT_W0LT11dCtfHDAa4S8SlWcF_dO5kQ5zqB47-ComJ2vlNM6AeX6lKG7IDdCqw8PLAQ1hrtJFWQ0h4rHiaRDc1b5xhAPPdwpEXTqG/s1600/Run+On+Server_add-remove-2012-03-18_10-26-43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSslFSyxyYs5YiI8_h2uo_loFqrU3yzPnxz-SID8GQT_W0LT11dCtfHDAa4S8SlWcF_dO5kQ5zqB47-ComJ2vlNM6AeX6lKG7IDdCqw8PLAQ1hrtJFWQ0h4rHiaRDc1b5xhAPPdwpEXTqG/s320/Run+On+Server_add-remove-2012-03-18_10-26-43.png" width="245" /></a></div>
<br />
Now the application is deployed and stared. STS will try and start this in its own default browser, which wont really run it well (unless configured otherwise). So mke sure to open your favorite browser (aka Chrome) and see the results from the url<br />
<br />
http://localhost:8080/pizza/<br />
<br /></div>
<div>
When started you should see:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhctt5VhIAtvRUERlGGDYl7qvfGLYWNPKeK5yISe-RL1eWSmH9MZtRaF1DxwgPj1q6ecL9xxVkagUls8ychtMMSq9TGpG-04rU71PRX2IXQ-VOOXOZszHh6zK8J98j16x_N5SWeEO0KUCtd/s1600/Welcome+to+Pizza+-+Google+Chrome_2012-03-18_10-34-26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhctt5VhIAtvRUERlGGDYl7qvfGLYWNPKeK5yISe-RL1eWSmH9MZtRaF1DxwgPj1q6ecL9xxVkagUls8ychtMMSq9TGpG-04rU71PRX2IXQ-VOOXOZszHh6zK8J98j16x_N5SWeEO0KUCtd/s320/Welcome+to+Pizza+-+Google+Chrome_2012-03-18_10-34-26.png" width="320" /></a></div>
<br /></div>
<div>
Notice that tiles has assembled this page by bringing together a standard header, and a menu. The content area changes depending on what is selected.<br />
<br />
The menu shows each domain class with two actions: Create new <x> and List all <x>. You can experiment with this scaffold application, create stuff etc. Notice how it handles the relationships from Pizza to Topping, or Pizza to Base. Or how PizzaOrders works.</x></x><br />
<br />
This is not a deliverable application by far, but it will demonstrate your model. The technology configuration that it uses has a lot of assumptions on the tag @RooWebScaffold annotation to make it all work for actions like:<br />
<br />
<ul>
<li>List</li>
<li>Create</li>
<li>View</li>
<li>Edit</li>
<li>Remove</li>
</ul>
<br />
<br />
The scaffold also uses custom tags to handle these forms and expose this functionality.<br />
<br />
You can customize what it generates incrementally to move closer to your application.<br />
<br />
<br />
<ul>
<li>You can modify messages</li>
<li>you can modify csss</li>
<li>you can play with layout (remember tiles)</li>
<li>And parts of the JSP</li>
</ul>
<br />
<br />
<span style="font-size: large;">Opinion</span><br />
<br />
But real customization to the desired application design is hard if you start with a web site design in mind and try to map it to the scaffold UI. For the controller, the ITD (intertype files) created add very useful standard form based behavior to your applicaitons which can be leveraged by a variety of UI approaches. But for the JSP generated, if you don't care as much and are doing an administrative application, this approach may suit you. You can compromise the UI design to best fit what has been generated and make customization's where needed. My experience is that this is rare. You can look at what is generated, but day 2 you are on your own. The basic configuration of spring mvc with tiles is good. The scaffold tags and jsps may not be what you need. You will need to really understand the annotated controllers and be able to write your own front end.<br />
<br />
<span style="font-size: large;">Next Steps</span><br />
<br />
Next we will configure to use with GWT and see how this looks<br />
<br />
<br />
<br />
<br />
<br /></div>
</div>
<br />
<br />
<br />edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com8tag:blogger.com,1999:blog-7203181491808683266.post-30569338461719862282012-03-10T03:07:00.000-08:002012-03-10T03:08:32.897-08:00Another MVC Framework - PlayRecently, I have found a little down time and thought I would investigate some other frameworks. I have been working mostly with Spring MVC this last year. I stumbled upon
<a href="http://www.playframework.org/">Play Framework</a> recently. It reminded me of a promotion of Ruby on Rails a few years ago comparing a several feet high stack of books on J2EE with the one simple single Rails book. Play framework is taking the same approach. Simplicity and immediacy.<br />
<br />
It uses a command line similar to Rails (and <a href="http://www.springsource.org/spring-roo">Spring Roo</a> by the way) to create initial constructs based on archetypes/patterns. It incorporates the web server so every change is instantly view-able on the web page and it will immediately display errors right in the web being developed. Further, it includes a simple template engine integrating the framework for creating pages to deliver to the client.<br />
<br />
<a href="http://youtu.be/kXImTUlHwAo">Watch a a nice tutorial</a> posted on YouTube.<br />
<br />
One premise play is based on is of more interest than its Rails like simplicity, that is its assumption of a stateless "share nothing'' REST first approach. Keeping state synchronized on the client, in the session and on persistent store can be annoying and difficult. It makes clustering more difficult and tends to produce complexity and as a result bug potential.<br />
<br />
<br />edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-70553938427012506342012-03-09T17:47:00.000-08:002012-03-10T04:03:07.545-08:00Looking at Google Plus APII'm jumping around technologies right now but I am playing around with the new google plus restful APIs.<br />
<br />
After watching an interesting <a href="http://www.youtube.com/watch?v=9MAsMybAUNU">youtube video of the API</a> by jenny murphy, I explored the API interactively using the <a href="https://developers.google.com/+/api/">google plus api developers site</a>.<br />
<br />
Clicking on REST API, you can go to each api resource (people, activities, comments, oauth) and explore the API including running an example request and seeing the results. I have a <a href="https://plus.google.com/u/1/115865272953642625051">google plus identity (+Ed)</a> so I posted a couple of public messages to play with.<br />
<br />
I was able to download the<a href="http://code.google.com/p/google-plus-java-api/"> google plus java client api</a> and <a href="http://code.google.com/p/google-plus-java-starter/">google plus java starter project</a>.<br />
<br />
I followed these steps to get started<br />
<br />
Copy the google java starter project web app example (./google-plus-java-starter/web-app) to a new directory to begin playing. The instructions are in the readme.txt to get started.<br />
<br />
In order to access the API, you first have to register your application using the <a href="https://code.google.com/apis/console/?api=plus">google api console</a> and get a token for oauth and access to the specific api. The steps<br />
<br />
<ol>
<li>go to
<a href="https://code.google.com/apis/console/b/0/?api=plus">https://code.google.com/apis/console/b/0/?api=plus</a> </li>
<li>click on 'create project'</li>
<li>agree to the terms of service (and read :)) (two of them)</li>
<li>Select the google+API (if not already selected (note the url I supplied should select this)</li>
<li>CLick on API access (left side menu)</li>
<li>Click on Create an OAuth 2.0 client ID</li>
<li>describe your project (and upload logo if you have it) and press next</li>
<li>select application type=web app, and site host name to run locally, select http: and enter localhost</li>
<li>click 'create client ID'</li>
<li>there are 4 pieces of info that you'll need to copy to your project: client id, client secret, redirect URI and API key (from the Simple API access)</li>
</ol>
<div>
in your new project (copied from starter) open src/main/resources/config.properties</div>
<div>
<br /></div>
<div>
paste in the 4 bits of information from the registration process (the redirect URI may already be correct)</div>
<div>
<br /></div>
<div>
now from the project root, use maven to launch the project using the built in jetty:</div>
<div>
<br /></div>
<blockquote class="tr_bq">
<span style="font-family: 'Courier New', Courier, monospace;">mvn compile jetty:run</span></blockquote>
<div>
<br /></div>
<div>
go to your browser (chrome naturally) to check out </div>
<div>
<br /></div>
<blockquote class="tr_bq">
<span style="font-family: 'Courier New', Courier, monospace;">http://localhost:8080/</span></blockquote>
<div>
<br /></div>
<div>
This may fail (the version I had downloaded _v5 did fail) </div>
<div>
<br /></div>
<div>
If you get an exception thrown when first accessing with error: </div>
<div>
<br /></div>
<blockquote class="tr_bq">
<span style="font-family: 'Courier New', Courier, monospace;">Exception initializing page context java.lang.VerifyError: (class: org/apache/jasper/runtime/PageContextImpl, method: getELResolver signature: ()Ljavax/el/ELResolver;) Incompatible argument to function</span></blockquote>
<div>
Then you may need to fix the pom.xml. Open pom.xml and add scope provided element for the jsp 2.1 dependency:</div>
<div>
<br /></div>
<div>
<pre class="brush: xml"><dependency>
<groupid>org.mortbay.jetty</groupid>
<artifactid>jsp-2.1</artifactid>
<version>6.1.14</version>
<scope>provided</scope>
</dependency>
</pre>
</div>
<div>
<br /></div>
<div>
Then restart and access again. Then it works!</div>
<div>
<br /></div>
<div>
You should see 'connect me' button.</div>
<div>
<br /></div>
<div>
This will require you to authorize access to your account from this program. Once authorized, it presents your profile information and a list of public activities.</div>
<div>
<br /></div>
<div>
Now you can play with the API</div>
<div>
<br /></div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-9344307726706089982012-01-17T11:17:00.000-08:002012-01-26T03:58:56.704-08:00Sencha TouchThis blog is outlining my experiences developing a mobile web based application developed with Sencha Touch and packaged with PhoneGap.<br />
<br />
I have been developing a quick app for my iphone using <a href="http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CEYQFjAA&url=http%3A%2F%2Fwww.sencha.com%2Fproducts%2Ftouch&ei=a8kVT-yJNOuCsgKkzqCIBA&usg=AFQjCNFIc52OJr69O-7WkHimgmsFDgM1Aw&sig2=i0_l5I6xuSZjvzu835v3OA">Sencha Touch API</a> (currenty 1.1). I have found it quite intuitive and the outcome is that I get a web based app that can be run nicely on Android as well as <a href="http://www.apple.com/ios/">iOS</a> devices. And I get to develop on my Win based machine in Java. Easy to test without emulator using normal browser too.<br />
<br />
The idea is to write code via HTML5 that can be useful on touch based devices such as iPhone, iPad and Android based phones and tabs. Rather than learning languages and frameworks specific to these technologies it is possible to stick to common HTML5 skills that still support touch device like gestures and form factors.<br />
<br />
In addition to deploying as a device capable web site, I intend to wrap it up with <a href="http://phonegap.com/">PhoneGap</a> to deploy it as native binaries onto multiple devices (Android, iOS, BB) with access to native APIs.<br />
<br />
The technology I am starting with<br />
<br />
I'm not cool enough to develop on a Mac Book, so I'm building on Win 7 Laptop. For my editor, I've been trying out <a href="http://www.sublimetext.com/">Sublime Text 2</a>. Nice that it opens a folder tree and has smart syntax highlighting for JavaScript, and knows about all the HTML5 stuff (CSS, JSON, HTML, JavaScript) but a full fledged IDE would have things like code complete and syntax checking right in the IDE, so I am also old schooling it and using Eclipse IDE. <br />
<br />
I have setup<a href="http://eclipse.org/downloads/packages/eclipse-classic-371/indigosr1"> Eclipse IDE (Classic edition)</a> augmented with <a href="http://developer.android.com/sdk/index.html">Android SDK</a> and the emulators, with the <a href="http://developer.android.com/sdk/eclipse-adt.html">ADT Android Developer Tools eclipse plugin</a>. However, for just Sencha touch the android stuff is not needed. But soon i will be looking at <a href="http://phonegap.com/">PhoneGap</a> to create binaries ready to deploy to devices. And I'm also developing with Android SDK specifically.<br />
<br />
I am also building the server side. I have been looking at <a href="http://node.js/">node.js</a> as a possibility. But I am also looking at Java based solutions as well (I'm showing my age I guess). For the Java based solutions, I am looking at deploying a J2EE (war) app and using <a href="http://www.springsource.org/">Spring MVC </a>as a core framework on the server side. <br />
<br />
To deploy as a server based web app (and I also intend to have server side components as well), I have constructed a maven driven web app from a maven archetype (see <a href="http://www.mkyong.com/maven/how-to-create-a-web-application-project-with-maven/">this great blog article</a>), and assuring it has the web app nature for using eclipse with help and great gratitude from <a href="http://www.mkyong.com/maven/how-do-use-maven-to-create-a-dynamic-web-project-in-eclipse/">this blog</a>. This process of getting Eclipse to work with Maven for a web app based project was more complex and error prone than I'd like. My desire was to use the IDE to code and build, and see the results immediately synchronized on the integrated web server. But Eclipse doesn't build natively with Maven goals like NetBeans does, and the integrated web server plugins aren't as smart as they should on when to sync, deploy and run. I may rethink this choice as I go along.<br />
<br />
To help provide the server stuff I need (remember Im on a Windows platform) I downloaded <a href="http://www.apachefriends.org/en/xampp.html">XAMPP </a>and am running separate a Tomcat instance as an independent deployment target different from the Eclipse integrated tomcat server where I can test changes during development. <br />
<br />
For the hosted solution and since I'm deploying to a java based app server (tomcat), I am deploying to <a href="http://jelastic.com/">jelastic</a> I can test it on my real devices. I also have Android emulators integrated into Eclipse and also <a href="http://ipadian.net/">iPadian </a>emulating an ipad but I haven't tried that avenue yet.<br />
<br />
The real devices I have to test on incude:<br />
<ul>
<li>galaxy 10.1 tab</li>
<li>iphone 3GS (old I know)</li>
</ul>
For basic application architecture I have two MVC frameworks!<br />
<br />
<ul>
<li>One MVC on the client (Sencha Touch with MVC pattern) and </li>
<li>One MVC on the server (Spring MVC). </li>
</ul>
Overkill? we shall see. We will get into the MVC model client and server side in another blog.<br />
<br />
For Sencha Touch I am following the MVC pattern as described by the <a href="http://www.sencha.com/learn/working-with-forms/">sencha forms tutorial</a>. Also, I have followed a great talk and tutorial from this German developer <a href="http://www.nils-dehl.de/">Nils Dehl</a> where he creates a shopping list app.<br />
<br />
For persistence, I have two choices: old school main stream relational (MySQL) or<a href="http://en.wikipedia.org/wiki/NoSQL"> NoSQL</a> approach with <a href="http://www.mongodb.org/">MondoDB</a>. I will start out with mondo db and see what happens. The idea is that the data model is going to be json based and object oriented. Structured, hierarchic data can be managed using objects and indexes. For the app I want to build, it may work well. But it seems heretical (not necessarily a bad thing)..<br />
<br />
Next article I will get into the app design...<br />
<br />edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com4tag:blogger.com,1999:blog-7203181491808683266.post-59353318762718084132011-07-29T17:31:00.000-07:002012-03-25T05:30:09.103-07:00Getting started with Spring Roo, GWT and STS - Part 1<div>
<br />
This is a multi-part article. This article walks through my experiences getting started with spring's Roo framework using the Spring Tools Suite (STS). The next articles will walk through different aspects of building an application from scratch.<br />
Also see<br />
<br />
<ul>
<li>Part 1 - Install and create - back end (this blog)</li>
<li><a href="http://edlovesjava.blogspot.com/2012/03/getting-started-with-spring-roo-sts-and.html">Part 2 - Web MVC</a></li>
<li>Part 3 - GWT</li>
<li>Part 4 - Custom app from scratch</li>
<li><br /></li>
</ul>
<br />
<br />
<span style="font-size: large;">Part One - Getting started with Roo using STS - The Roo tutorial </span><br />
<br />
Time to get started. I'm going to run through the introductory materials on building a <a href="http://www.springsource.org/roo">Spring Roo</a> application using the Roo tutorial. In the next articles I will build a custom example using Spring Roo with the <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a> front end. I will be using the Spring Tools Suite as the IDE (based on Eclipse).</div>
<div>
<br /></div>
<div>
I will start by following some initial advice:</div>
<div>
<ul>
<li>on the Google side: here is a <a href="http://code.google.com/webtoolkit/doc/latest/tutorial/roo-sts.html4">google code blog</a></li>
<li>on the Spring side: here is <a href="http://www.springsource.org/spring-roo">springsource roo page</a> and the<a href="http://www.springsource.org/roo/start"> springroo start</a> and the<a href="http://static.springsource.org/spring-roo/reference/html/beginning.html"> Beginning with Roo: The tutorial from Spring Framework</a> reference documentation</li>
<li>another blog (<a href="http://gwtsts.blogspot.com/">gwtsts.blogspot.com</a>) by <span class="Apple-style-span" style="color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 12px; font-weight: bold; line-height: 16px;"><a class="profile-name-link" href="http://www.blogger.com/profile/10471886955497473463" rel="author" style="color: #2288bb; text-decoration: none;">Cengiz Öner</a> </span>describing this effort has<a href="http://gwtsts.blogspot.com/2011/03/part-i-installation-and-setup-of-sts.html"> multi-part instructions</a></li>
</ul>
<div>
<span class="Apple-style-span" style="font-size: large;"><b>Installation</b></span></div>
<div>
<br /></div>
<div>
What I have</div>
<div>
<ul>
<li>Java 6 SDK r26 from <a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u26-download-400750.html">her</a>e</li>
<li>Running on a Windows 7 Laptop (64 bit)</li>
</ul>
</div>
<div>
<b>Step 1 - Install STS </b><br />
Download from <a href="http://www.springsource.org/springsource-tool-suite-download">here</a> and follow the simple instructions.<br />
I installed SpringSource Tool Suite: Version: 2.9.0.RELEASE Build Id: 201203011000<br />
<br />
<b>Step 2 - Configure to use Google Web Toolkit and Google App Engine</b><br />
After running STS, go to the dashboard and select the 'extensions' tab (bottom). Find and Check Google Plugin for Eclipse and press 'Install' (bottom left)</div>
<div>
<br /></div>
</div>
<div>
Restart STS and thats it (for now)</div>
<br />
<span style="font-size: large;">Running through the Spring Roo Tutorial</span><br />
<br />
<div>
<span class="Apple-style-span"><b>Overview of Spring Pizza Shop Demo</b></span></div>
<div>
Lets use the <a href="http://static.springsource.org/spring-roo/reference/html/beginning.html">Spring Framework Roo tutorial</a> to get started.This tutorial walks us through creating a Roo project including entities controllers and the UI/scaffold. I will try to emphasise use of the STS tool as well as focusinging on using GWT as the front end. To begin with, we will follow the tutorial fairly closely.</div>
<br />
<div>
Here is the project UML from the spring tutorial that we will create</div>
<br />
<br />
<div>
<br /></div>
<div>
This is the Roo Pizza Shop demo</div>
<div>
<br /></div>
<a href="http://static.springsource.org/spring-roo/reference/html/images/pizza.png"><img alt="" border="0" src="http://static.springsource.org/spring-roo/reference/html/images/pizza.png" style="cursor: hand; cursor: pointer; float: left; height: 123px; margin: 0 10px 10px 0; width: 450px;" /></a><br />
<div>
<br />
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<br />
<div>
<br /></div>
<div>
<span class="Apple-style-span"><b>First Step Basic Roo Configuration</b></span></div>
<div>
<br /></div>
<div>
Spring Roo builds upon Spring framework 3.0 with JPA 2.0. It uses a command line tool to create basic elements of a project, from persistence setup, to creating entities, to creating Spring MVC controllers, to generating Scaffold ui (later we will be using GWT).</div>
<div>
<br /></div>
<br />
<div>
We can use spring STS to create a new spring Roo project project</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEyZLa_xDtU65vsRvwW6ok3j62lpjMIMGEi9HkWQzOs5SEXyma1tN6UlMzPnTKT8NjVUIL2JId9BH74wLaGSAZdb37FSP8LJcFUK8Goo_iYtwKI5q8razm9cDqwJmPllgVZc2wznZJT6Mu/s1600/sts-createrooproject.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5635561123750076194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEyZLa_xDtU65vsRvwW6ok3j62lpjMIMGEi9HkWQzOs5SEXyma1tN6UlMzPnTKT8NjVUIL2JId9BH74wLaGSAZdb37FSP8LJcFUK8Goo_iYtwKI5q8razm9cDqwJmPllgVZc2wznZJT6Mu/s320/sts-createrooproject.png" style="display: block; height: 320px; margin-bottom: 10px; margin-left: auto; margin-right: auto; margin-top: 0px; text-align: center; width: 218px;" /></a></div>
<br />
<div>
<ol>
<li>From the Dashboard, select Create a Spring Roo Project</li>
<li>Fill out dialog: Project Name: pizza, top level package: com.springsource.roo.pizzashop</li>
<li>Accept all other defaults and press Next (and then Finish). This will create the Roo project and open the roo shell. Actually, STS is executing the roo shell to create the project directory and the roo command: <blockquote>
roo> project --topLevelPackage com.springsource.roo.pizzashop</blockquote>
</li>
</ol>
We see the ROO command shell at the bottom with a command promt inviting us to execute roo commands. <br />
<br />
<b>Note: Using the Roo shell in StS </b><br />
<br />
Roo uses a shell similar to Rails to invoke commands that create essential structures and configurations around the Roo project. It saves a lot of time writing repetitive code or digging around to configure stuff. the shell is a bit smart in that it knows what you have done in your project and what steps can be next.</div>
<div>
<br /></div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj79vtwn_GlkbKQBpUnEEmJrFB8cBWZC3CB_haAviava1yP9kGrsPW52iMO6xm0ID31pMz-UcpWO37FcumvJmq4Eud6kKf5Tyl35ooh7lxVe2ynGH85wvYyRP1P-ga7dU-AcsrEU2tHdfnE/s1600/sts-roo-shell-typehere.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5635503853341574994" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj79vtwn_GlkbKQBpUnEEmJrFB8cBWZC3CB_haAviava1yP9kGrsPW52iMO6xm0ID31pMz-UcpWO37FcumvJmq4Eud6kKf5Tyl35ooh7lxVe2ynGH85wvYyRP1P-ga7dU-AcsrEU2tHdfnE/s320/sts-roo-shell-typehere.png" style="cursor: hand; cursor: pointer; height: 112px; width: 320px;" /></a><br />
<br />
<br /></div>
<div>
The window has a top part showing a console with roo commands as well as a prompt, but don't be fooled. The roo prompt we use in StS is below (a single line edit).<br />
The spring ROO project leverages Maven2 and its unique project structure.</div>
<div>
<br />
One cool roo command you will use right away is hint. This will give you possible options to do from the point in your project.<br />
<br />
After creating a roo project, you can type HINT in the Roo shell. It will respond with:<br />
<br />
<pre class="brush: shell">Roo requires the installation of a JPA provider and associated database.
Type 'persistence setup' and then hit CTRL+SPACE three times.
We suggest you type 'H' then CTRL+SPACE to complete "HIBERNATE".
After the --provider, press CTRL+SPACE twice for database choices.
For testing purposes, type (or CTRL+SPACE) HYPERSONIC_IN_MEMORY.
If you press CTRL+SPACE again, you'll see there are no more options.
As such, you're ready to press ENTER to execute the command.
Once JPA is installed, type 'hint' and ENTER for the next suggestion.
</pre>
It knows what you have done before so it can provide hints for the next steps. Very smart!<br />
<br />
<b>Next: Setup project with persistence library</b><br />
<br />
As mentioned before, Spring Roo requires the configuration of a JPA provider such as Eclipselink,, OpenJPA or Hibernate.<br />
<br />
configure persistence for this project: in Roo shell,</div>
<div>
<ol>
<li>As explained above, type hint and roo will prompt you to setup persistence for your project</li>
<li>type' persitence setup' and press ctrl+space. In StS, this will use code assist to allow you to select possible next options for the command (the command line, you press the TAB key). In this case we will select --provider HIBERNATE and --database HYPERSONIC_IN_MEMORY</li>
<li>The complete roo command is:
<pre class="brush: shell">persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY
</pre>
</li>
<li>Roo will generate the JPA configuration files and initial spring context</li>
</ol>
<br />
<b>Note: the roo log file</b><br />
<br />
Spring keeps a log of your commands in the root project: log.roo. This may be handy to track what you have done, and in fact can be re-executed line by line to repeat your steps.</div>
<br />
<div>
<span class="Apple-style-span"><b>Next: Create your first Entity</b></span></div>
<br />
In JPA, classes annotated as Entity are persistable, and generally map one-to-one to a table. Class properties generally map to columns in that table. The first class we will create will be Topping (see above UML model).<br />
<br />
Using Roo shell:<br />
<div>
<ol>
<li>Type Hint again, and Roo will advise you to type ent and press TAB to get the next option (from STS, ctrl+space). This will allow us to create an entity with a specific class name: Topping</li>
<li>Type entity then press (ctrl+space) . This will produce --class</li>
<li>Type ~.domain.Topping for the class name (the tilda will indicate the project's package root (remember we specified com.springsource.roo.pizzashop)</li>
<li>Now ctrl+space doesn't give us more hints since all required options are satisfied, but type double dash (--) and then ctrl+space will give us a bunch more options.</li>
<li>type or select --testAutomatically to generate test classes for the new entity and press enter</li>
</ol>
(the complete roo command: entity --class ~.domain.Topping --testAutomatically)<br />
<br />
Roo creates a whole bunch of files and aspects. Notice the actual entity "SRC_MAIN_JAVA\com\springsource\roo\pizzashop\domain\Topping.java" is created. You can see it in the packag tree. However, STS starts with an inital filter in the package tree to 'Focus on Active Task'.</div>
<div>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzZ2vUVu-IkP2J96WkgpE18HJ0jXVZMimH9rU06So0rdHPdQGjncWk7Q-5jtdSPQH4eZGJd1ijfZfpDET3rWy0OhcmkYzvcwngbFBqRigGlDitpZYayXvnnAPD4hHlA6ydM1LpZEZh6RNO/s1600/sts-project-removefilter.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5635504932462355858" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzZ2vUVu-IkP2J96WkgpE18HJ0jXVZMimH9rU06So0rdHPdQGjncWk7Q-5jtdSPQH4eZGJd1ijfZfpDET3rWy0OhcmkYzvcwngbFBqRigGlDitpZYayXvnnAPD4hHlA6ydM1LpZEZh6RNO/s320/sts-project-removefilter.png" style="float: left; height: 320px; margin-bottom: 10px; margin-left: 0px; margin-right: 10px; margin-top: 0px; width: 117px;" /></a></div>
<div>
Click the filter icon in the top toolbar of the Package view to reveal the entire project. Now navigate to src/main/java. See the com.springsource.roo.pizzashop.domain package and the created Topping.java class just created.<br />
<br />
<b>Next: Create Fields for Our Entity</b></div>
<div>
<br />
Now lets add a field.<br />
Use the roo command to type:<br />
<br />
<br />
<ol>
<li> field string --fieldName name --notnull --sizeMin 2</li>
</ol>
<br />
This will add a field, and update/add to the generated aspects based on the annotaitions.</div>
<br />
<div>
Opening this class, all you see is<br />
<br />
File: Topping.java<br />
<pre class="brush: java" type="syntaxhighlighter">package com.springsource.roo.pizzashop.domain;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@RooJavaBean
@RooToString
@RooEntity
public class Topping {
@NotNull
@Size(min = 2)
private String name;
}
</pre>
</div>
<br />
Notice the classs annotations that roo adds @RooJavaBean, @RooToString and @RooEntity. These annotations generate aspects with extensions .aj for inter type definitions. Spring also adds JSR 303 bean validation annotations on the field based on the options we specified: @NotNull and @Size(min = 2).<br />
<br />
The principle that Roo introduces here is the heavy leveraging of AspectJ's Inter-Type Declaration (ITD). The ITD code generates as a separately complied file, but is encorporated into the class byte code of the advised class. Generally we don't need to bother about what is generated here. But lets look anyway. For instance, the @RooJavaBean annotaion creates the class Topping_Roo_JavaBean.aj. Peeking at it, we see:<br />
<div>
File: Topping_Roo_Entity.aj<br />
<pre class="brush: java" type="syntaxhighlighter">// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package com.springsource.roo.pizzashop.domain;
import java.lang.String;
privileged aspect Topping_Roo_JavaBean {
public String Topping.getName() {
return this.name;
}
public void Topping.setName(String name) {
this.name = name;
}
}
</pre>
</div>
<br />
<div>
<br />
We see that the name field produced a getter and a setter for the field. As we add new fields, this aspect will be updated. Using aspects this way allows the domain class to remain particularly pure.</div>
<br />
<div>
As another example, lets look at what was generated for the @RooToString annotation:</div>
<br />
<div>
File: Topping_Roo_ToString.aj<br />
<br />
<pre class="brush: java" type="syntaxhighlighter">package com.springsource.roo.pizzashop.domain;
import java.lang.String;
privileged aspect Topping_Roo_ToString {
public String Topping.toString() {
StringBuilder sb = new StringBuilder();
sb.append("Id: ").append(getId()).append(", ");
sb.append("Name: ").append(getName()).append(", ");
sb.append("Version: ").append(getVersion());
return sb.toString();
}
}
</pre>
</div>
<br />
<br />
<div>
<br />
Pretty nifty, huh! this is the Secret Sauce of Roo as described in <a href="http://blog.springsource.com/2009/06/18/roo-part-3/">Ben Alex' Blog on Roo's Architecture</a></div>
<br />
<div>
Finally, lets' look at what Roo does with the @RooEntity declairation<br />
<div>
File: Topping_Roo_Entity.aj<br />
<br />
<pre class="brush: java" type="syntaxhighlighter">// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package com.springsource.roo.pizzashop.domain;
import com.springsource.roo.pizzashop.domain.Topping;
import java.lang.Integer;
import java.lang.Long;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Version;
import org.springframework.transaction.annotation.Transactional;
privileged aspect Topping_Roo_Entity {
declare @type: Topping: @Entity;
@PersistenceContext
transient EntityManager Topping.entityManager;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long Topping.id;
@Version
@Column(name = "version")
private Integer Topping.version;
public Long Topping.getId() {
return this.id;
}
public void Topping.setId(Long id) {
this.id = id;
}
public Integer Topping.getVersion() {
return this.version;
}
public void Topping.setVersion(Integer version) {
this.version = version;
}
@Transactional
public void Topping.persist() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.persist(this);
}
@Transactional
public void Topping.remove() {
if (this.entityManager == null) this.entityManager = entityManager();
if (this.entityManager.contains(this)) {
this.entityManager.remove(this);
} else {
Topping attached = Topping.findTopping(this.id);
this.entityManager.remove(attached);
}
}
@Transactional
public void Topping.flush() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.flush();
}
@Transactional
public void Topping.clear() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.clear();
}
@Transactional
public Topping Topping.merge() {
if (this.entityManager == null) this.entityManager = entityManager();
Topping merged = this.entityManager.merge(this);
this.entityManager.flush();
return merged;
}
public static final EntityManager Topping.entityManager() {
EntityManager em = new Topping().entityManager;
if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
return em;
}
public static long Topping.countToppings() {
return entityManager().createQuery("SELECT COUNT(o) FROM Topping o", Long.class).getSingleResult();
}
public static List<topping> Topping.findAllToppings() {
return entityManager().createQuery("SELECT o FROM Topping o", Topping.class).getResultList();
}
public static Topping Topping.findTopping(Long id) {
if (id == null) return null;
return entityManager().find(Topping.class, id);
}
public static List<topping> Topping.findToppingEntries(int firstResult, int maxResults) {
return entityManager().createQuery("SELECT o FROM Topping o", Topping.class).setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
}
}
}
</topping></topping></pre>
</div>
<br />
<br />
Rather than creating a data access object (DAO) or service, Roo assumes that each entity created has its own implementation of persistence. The @RooEntity annotation generates a ITD (Inter-type Declairation) for the entity advising it to add many methods to support persistence, inclding id generation, versioning, access to the entity manager, various transactional methods: <br />
<ul><br />
<li>persist, </li>
<li>remove,</li>
<li>flush,</li>
<li>merge, </li>
<li>clear </li>
</ul>
and various finder methods<br />
<ul><br />
<li>count</li>
<li>find by id</li>
<li>findAll</li>
<li>and pagable findAll</li>
</ul>
<br />
<br /></div>
<br />
<div>
<span class="Apple-style-span"><b>Next: Some More entitles and fields</b></span></div>
<div>
<br /></div>
As Roo creates objects, it keeps track of the last object you created. The next command defaults to this target, so there is no need to specify it explicitly. For example, when we created the field, we didn't need to specify what class to create the field for. It assumed we meant the last entity we worked with.. The spring Roo console shows what entity we last worked with in its prompt, much like a command shell might with a prompt showing the current directory path.<br />
<br />
Using roo shell:<br />
<div>
<br /></div>
<div>
Now lets create another entity and some fields similar to the ones we created before. Here are the Roo commands:</div>
<br />
<pre class="brush: shell">entity --class ~.domain.Base --testAutomatically
field string --fieldName name --notNull --sizeMin 2
entity --class ~.domain.Pizza --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field number --fieldName price --type java.lang.Float</pre>
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsYI7Q2HkR11jEsRpA1qVEold5v37slbzYi0ZVumnvTHTdKvhylLDwPAHVdQNV70-Ou3PSKNP-uEJfqdam7CuJ0_Q5Q1teW8DGJpg-cWY4I91lNyrz4mNJqzJAUrFPodv_2nLkOC4nXOqX/s1600/sts-project-expandedentities1.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5635510631389233666" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsYI7Q2HkR11jEsRpA1qVEold5v37slbzYi0ZVumnvTHTdKvhylLDwPAHVdQNV70-Ou3PSKNP-uEJfqdam7CuJ0_Q5Q1teW8DGJpg-cWY4I91lNyrz4mNJqzJAUrFPodv_2nLkOC4nXOqX/s320/sts-project-expandedentities1.png" style="cursor: hand; cursor: pointer; float: left; height: 320px; margin: 0 10px 10px 0; width: 118px;" /></a><br />
<div>
<br />
That's it. As long as we follow the defaults, things are easy. This is the approach taken by Ruby on Rails: convention over configuration. The command line invokes templated commands to create objects quickly. </div>
<br />
<div>
Our project should look basically like this (as seen from the project view)</div>
<br />
<br />
<div>
<span class="Apple-style-span"><b>Next: Some Relationships for our Entities</b></span></div>
<br />
<div>
<br />
We are still in JPA land. JPA allows us to create relationships as well as if POJO java classes using well known collections. Roo supports a one-to-one or many-to-one reference, a one-to-many 'set' (and also ways to create many-many). In the above, we have a one-many relationship from Pizza to Topping (a pizza has many toppings) and a one-one relationship (reference) to a base. We'll use the Roo command shell to create these too. The tutorial shows there are two relationships from Pizza, a 'reference' to a base (m:1) where a Pizza has one base, and a set of toppings (m:m relationship to toppings), where a pizza has a set of toppings.<br />
<br />
In roo shell, type the following:</div>
<br />
<div>
<pre class="brush: java" type="syntaxhighlighter">field set --fieldName toppings --type ~.domain.Topping
field reference --fieldName base --type ~.domain.Base
</pre>
</div>
<br />
<div>
<br />
The above commands assume you are creating the relationship fields to the last entity you created: Pizza as it would show you in a command prompt. If not, make sure to append --class ~.domain.Pizza to the above commands (at least the first one) to assure the relationships are created for the Pizza class.<br />
<br />
<br />
And we can finish up with PizzaOrder, in roo shell:</div>
<br />
<br />
<div>
<pre class="brush: shell" type="syntaxhighlighter">entity --class ~.domain.PizzaOrder --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field string --fieldName address --sizeMax 30
field number --fieldName total --type java.lang.Float
field date --fieldName deliveryDate --type java.util.Date
field set --fieldName pizzas --type ~.domain.Pizza
</pre>
</div>
<br />
Note: if you make a mistake, you cn press the up arrow from the Roo shell prompt in STS and previous commands can be edited and re executed.<br />
<br />
<div>
Notice the last command creates another relationship, this time an PizzaOrder has a set of Pizzas ordered, which Roo will create a m:m relationship for of type Set pizzas. As you can see, creating entities, fields and relationships with Roo comes down to running a set of commands to generate the appropriate Java code and Aspects according to Roo convention. In fact, you can save these commands as a 'seed' text file that will create a project for you, generating the project structure, configruation, classes and aspects required.</div>
<br />
<br />
<div>
<span class="Apple-style-span"><b>Next: Lets Test</b></span></div>
<br />
<div>
<br />
We can now run the integration tests using the roo command perform tests. </div>
<div>
<blockquote>
perform tests</blockquote>
</div>
<br />
<br />
<div>
<span class="Apple-style-span"><b>Here is a summary of the roo commands executed </b></span><br />
<pre class="brush: shell">// Spring Roo 1.1.4.RELEASE [rev f787ce7] log opened at 2012-03-17 10:38:04
project --topLevelPackage com.springsource.roo.pizzashop --projectName pizza --java 6
// Spring Roo 1.1.4.RELEASE [rev f787ce7] log closed at 2012-03-17 10:38:15
// Spring Roo 1.1.4.RELEASE [rev f787ce7] log opened at 2012-03-17 10:38:19
hint
hint
persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
hint
entity --class ~.domain.Topping --testAutomatically
field string --fieldName name --notnull --sizeMin 2
entity --class ~.domain.Base --testAutomatically
field string --fieldName name --notNull --sizeMin 2
entity --class ~.domain.Pizza --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field number --fieldName price --type java.lang.Float
field set --fieldName toppings --type ~.domain.Topping
field reference --fieldName base --type ~.domain.Base
entity --class ~.domain.PizzaOrder --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field string --fieldName address --sizeMax 30
field number --fieldName total --type java.lang.Float
field date --fieldName deliveryDate --type java.util.Date
field set --fieldName pizzas --type ~.domain.Pizza
perform tests
</pre>
</div>
<br />
<br />
<div>
<span class="Apple-style-span"><b>Finally: Look at the test classes generated</b></span><br />
<span class="Apple-style-span"><b><br /></b></span><br />
The main class 'Pizza' has a name (required min size 2) and a price, but also two relationships: one a many-many relationship to Toppings class and a reference (many-one) relationship to Base class.<br />
<br />
<br /></div>
<div>
File: Pizza.java
<br />
<pre class="brush: java"><topping><topping>package com.springsource.roo.pizzashop.domain;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Set;
import java.util.HashSet;
import javax.persistence.ManyToMany;
import javax.persistence.CascadeType;
import com.springsource.roo.pizzashop.domain.Topping;
import com.springsource.roo.pizzashop.domain.Base;
import javax.persistence.ManyToOne;
@RooJavaBean
@RooToString
@RooEntity
public class Pizza {
@NotNull
@Size(min = 2)
private String name;
private Float price;
@ManyToMany(cascade = CascadeType.ALL)
private Set<topping> toppings = new HashSet<topping>();
@ManyToOne
private Base base;
}
</topping></topping></topping></topping></pre>
</div>
The class 'Base' has a single field 'name' but annotated with @NotNull and @Size min 2.<br />
<br />
<div>
File: Base.java
<br />
<pre class="brush: java">package com.springsource.roo.pizzashop.domain;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@RooJavaBean
@RooToString
@RooEntity
public class Base {
@NotNull
@Size(min = 2)
private String name;
}
</pre>
</div>
Topping is also a simple class, with one field 'name' declaired as Min 2 (but not NotNull ?) probably should be NotNull, dont you think?<br />
<br />
<div>
File: Topping.java
<br />
<pre class="brush: java">package com.springsource.roo.pizzashop.domain;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import javax.validation.constraints.Size;
@RooJavaBean
@RooToString
@RooEntity
public class Topping {
@Size(min = 2)
private String name;
}
</pre>
</div>
Finally, PizzaOrder is the workhorse of the project. It relates the person ordering (this could be another class 'Customer' but isn't for simplicity sake (we may not need to track people or order pizza but it may help down the road)<br />
<br />
<div>
File: PizzaOrder.java
<br />
<pre class="brush: java">package com.springsource.roo.pizzashop.domain;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Set;
import com.springsource.roo.pizzashop.domain.Pizza;
import java.util.HashSet;
import javax.persistence.ManyToMany;
import javax.persistence.CascadeType;
@RooJavaBean
@RooToString
@RooEntity
public class PizzaOrder {
@NotNull
@Size(min = 2)
private String name;
@Size(max = 30)
private String address;
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(style = "S-")
private Date deliveryDate;
private Float total;
@ManyToMany(cascade = CascadeType.ALL)
private Set<pizza> pizzas = new HashSet<pizza>();
}
</pizza></pizza></pre>
</div>
<br />
<b>Fixing a problem</b><br />
In the above example, i noted that Toppings 'name' field doesn't have a @NotNull annotation and it probably should. How do you fix it? Using the Roo shell is good for adding stuff, but not changing or removing stuff. To do this, just go to the java code itself.<br />
<br />
<br />
<ol>
<li>Open the Toppings class</li>
<li>Add the @NotNull annotation</li>
<li>Add the import for the annotation (I like to use CTRL+SHIFT+O for 'Organize Imports' in the Source menu)</li>
</ol>
<div>
And that's it!</div>
<br />
<br />
<b><span style="font-size: large;">Next steps</span></b><br />
<br />
On <a href="http://edlovesjava.blogspot.com/2012/03/getting-started-with-spring-roo-sts-and.html">next blog article</a> we will add a user interface to this to complete the tutorial, still no GWT yet. That will be following.<br />
<div>
</div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com2tag:blogger.com,1999:blog-7203181491808683266.post-75840990545842855932011-07-08T05:17:00.000-07:002013-10-19T04:57:38.696-07:00Testing my RESTful controllers with Spring RESTtemplateI have been playing with Spring's RESTtemplate as an approach to integration testing on my project.<br />
<br />
<h2>
Overview of Testing a RIA with Spring and Spring MVC</h2>
<br />
<br />
I feel I should create a set of blog posts related to how I am doing testing with this technology stack. Sometimes this aggregation is helpful. In my team, we are testing at the following levels:<br />
<br />
<ul>
<li>unit testing of everyting - pure tests, typically single class only, with mock (using <a href="http://mockito.org/">Mockito</a>)</li>
<li>integration testing of our services - out of container (web server) testing, but with db</li>
<li>integration testing of our controllers - in container testing, with db</li>
<li>functional testing of our ui (TBD) - full in container testing</li>
</ul>
<br />
<br />
This blog will focus on integration testing.<br />
<br />
Integration testing, different from unit testing, tests the code as it is wired up to other components, the server and the data base as well. <br />
<br />
<h2>
Testing the controller layer in Spring MVC</h2>
<br />
<br />
To set the stage, I am using spring 3 with Spring MVC for my application. In particular, I am leveraging a lot of Ajax, accessing the controller from the browser and transporting JSON. I am using the <a href="http://jackson.codehaus.org/">Jackson JSON mapping implementation</a> to marshal to from beans to JSON.<br />
<br />
I need to create good integration tests of my controller classes so I can run JUnit tests that connect with the deployed web app and interact RESTfully like the client UI, passing JSON objects back and forth.<br />
<br />
A helpful blog post I have used for guidance: is <a href="http://ralf.schaeftlein.de/2010/03/05/junit-tests-for-spring-3-rest-services/">ralf.schaeftlein s blog</a> detailing an approach to integration testing of restful MVC controllers with JSON. Exactly what I was interested in.<br />
The basic steps seem to be:<br />
<ol>
<li>create a unit test with java4 leveraging <a href="http://static.springsource.org/spring/docs/3.0.x/reference/testing.html#integration-testing">Spring 3 Integration Test Annotations</a></li>
<li>configure your application context XML to define your mappers that will convert between JSON and beans</li>
<li>write your tests pojo style</li>
</ol>
Integration Testing with Spring uses annotations to supply a spring context to your test code. Further, the annotations specify what transaction manager to use and if a rollback should be performed after the test is complete. The tests itself are written with JUnit4. Here is a snippet showing the annotations required placed on your test class.<br />
<br />
<script class="brush: java" type="syntaxhighlighter"><![CDATA[ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"file:src/test/resources/applicationContext-doc-test.xml"}) @TransactionConfiguration(transactionManager = "myTransactionManager", defaultRollback = true)<br />@Transactional<br />public class TestMyRepository {<br />]]></script><br />
In this case, I am testing my Data Repository, which is essentially my Data Access Object (DAO) for you old schoolers. I will post why Repositories and not DAO in a future blog.<br />
My application context file configures spring with the required setup for my application to run this test. First, the usual name spaces:<br />
<script class="brush: xml" type="syntaxhighlighter"><![CDATA[ <br /><?xml version="1.0" encoding="UTF-8"?><br /><beans xmlns="http://www.springframework.org/schema/beans"<br /> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"<br /> xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"<br /> xmlns:context="http://www.springframework.org/schema/context"<br /><br /> xsi:schemaLocation="http://www.springframework.org/schema/beans <br /> http://www.springframework.org/schema/beans/spring-beans.xsd<br /> http://www.springframework.org/schema/aop <br /> http://www.springframework.org/schema/aop/spring-aop.xsd<br /> http://www.springframework.org/schema/tx<br /> http://www.springframework.org/schema/tx/spring-tx.xsd<br /> http://www.springframework.org/schema/context<br /> http://www.springframework.org/schema/context/spring-context.xsd"><br />]]></script><br />
Then, we use automatic annotation discovery of out project<br />
<script class="brush: js" type="syntaxhighlighter"><![CDATA[ <br /> <context:annotation-config /><br /> <context:component-scan base-package="org.wentsoft.notable.domain" /><br />]]></script><br />
Here is wshere we configure the restTemplate to be used for this integration test of our controller<script class="brush: js" type="syntaxhighlighter"><![CDATA[ <br /> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"><br /> <property name="messageConverters"><br /> <list><br /> <bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"><br /> <property name="supportedMediaTypes" value="application/json" /><br /> </bean> <br /> </list><br /> </property><br /> </bean><br />]]></script><br />
Finally the required configuration for our persistence. Notice we are using JPA configuration here<br />
<script class="brush: xml" type="syntaxhighlighter"><![CDATA[ <br /> <bean id="myEMF" <br /> class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"><br /> <property name="persistenceUnitName" value="test-pu" /><br /> </bean><br /><br /> <tx:annotation-driven transaction-manager="myTM" /><br /><br /> <bean id="myTM" class="org.springframework.orm.jpa.JpaTransactionManager"><br /> <property name="entityManagerFactory" ref="myEMF" /><br /> </bean><br /><br /> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /><br /></beans><br />]]></script><br />
<br />
Finnaly the test code.<br />
<br />
We write an integration test for our controller that connects remotely to the deployed application, sends beans mapped from Pojo to json for the call, and then maps back json to Pojo to assert results.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-9537336328292046392011-07-08T05:11:00.000-07:002011-07-08T05:24:01.618-07:00A shout out to a framework for html5One of my readers sent me a link to <a href="http://www.strobecorp.com/">Strobe</a>, (<a href="http://www.strobecorp.com/">http://www.strobecorp.com/</a>) an open source platform built from the ground up to leverage html5, but still leverage MVC. Although my ship has already left down the river of <a href="http://www.springsource.org/">Spring</a>, I can longingly look at its innovation and see what we can adopt for ourselves. I know I will loose a few hours this weekend on playing with this one. <br /><br />Thanks<br /><br />PS, I also seem to be wasting my time playing with knew language ides: ever check out <a href="http://clojure.org/">Clojure</a>. Yes, it talks with a lisp as well.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-63820013958649600242011-07-08T04:07:00.000-07:002011-07-08T05:24:01.618-07:00Crossing the chasm to the new paradigmI titled this post in homage to Moore, the author of <a href="http://en.wikipedia.org/wiki/Crossing_the_Chasm">Crossing the Chasm</a>. He clearly described how marketing strategies for high-tech are different from other product categories. He heavily leverages the <a href="http://en.wikipedia.org/wiki/Technology_adoption_lifecycle">technology adoption life-cycle</a> bell curve where Rogers et. al. segments people into 5 categories: <ol><li>innovators, <li>early adopters, <li>early majority, <li>late majority and <li>laggards</ol> (although the last category seems a bit offensive :)). Moore emphasizes that getting from early adopter to early majority is like crossing a chasm: the former 'visionaries' have very different expectations then the later 'pragmatists'. To mix authors (like metaphors), a '<a href="http://en.wikipedia.org/wiki/The_Tipping_Point">tipping point</a>' seems to be required to make the shift. <br />So it is for developers: new ideas, languages and techniques come by all the time. Some jump in 'where angels dare to tread'. I seem to be the former. At least I like to think of myself as such. Others wait. Some are clearly risk averse, but I think for some, it is hard to let go of the 'it works, why change it' paradigm. Perhaps they are right. They are pragmatists, and by nature practical. But innovators and early adopters seem to reap benefits. Since the technology wave moves incesntly and swiftly, it is best to keep on its cusp, like a surfer, using its power and momentum to guide you on, lest you be left paddling to catch up, far behind.<br />But many on my team aren't of the innovator ilk. Nor is my company. I work for a very large software company that should be on the leading edge, if not the bleeding edge of innovation adoption. But we're not. We suffer from a stifling bureaucracy (like most large companies or governments, dare I say), and a hefty dose of 'not built here' syndrome. Interestingly enough, by boss isn't a 'laggard' in any stretch of the imagination. He, however, bears the brunt of having to argue for the wacky ideas of his reports in front of very risk averse corporate gatekeepers. Sometimes he wins, sometimes not.<br />But, within the team, it was surprising to me how shocked people were when I said, <blockquote>"we are not going to write JSP any more."</blockquote> <br />I'm sure some of the readers of this blog post (all 1 of you) might be shocked as well. But sometimes we need to be shocked. This is a broad and sweeping statement, but it is powerful, because it hints at our reliance on rendering the UI on the server. And we shouldn't. This is heresy, and would have been foolish in the past. From early cgi and perl (remember those days) to the development of J2EE, the browser was considered a dumb reader of html, not a smart client platform like thick client apps or client server. Applets were to be that, not the browser. If not Applets, then flash, if not flash then... well, ... the browser. <br />What is the browser today? Its not dumb. It's not just a reader. It is an operating system. It is a platform that has capabilities to run sophisticated programs delivered to it. HTML5, CSS3 and JavaScript are the languages it speaks. If you know the lingo, you can unleash a vast array of capabilities that just a couple of years before were left to the native or thick client world. <br />So, my aha moment came when I realized that what we needed to do was to go back to the client server days. In those days, you had vast power of the desktop, its graphical and multimedia power and local storage capability. You connected to the server for data. Yes, business rules and shaping of data occurred on the server. Certainly secure selection and delivery of data as well. It enforces transactionality and validity. It handles persistence and auditing. But its is never in the business of rendering the user interface--that's the client's job. <br />Rendering UI on the server was the compromise required to deliver applications over the web. And it was worth it too. Not to wory about the client footprint, not to have to wait for support to install and configure the next version. immediate access to everything a mouse click away. It rocked our world. It changed our world. Thanks <a href="http://en.wikipedia.org/wiki/Tim_Berners-Lee">Tim</a> for that. But he was thinking hyperlinked books after all, not applications. It was amazing what we can do even with its obvious limitations. And Tim has moved on too.<br /><br />But we no longer need to make this compromise. We can benefit from the zero footprint immediate access to all the worlds knowledge and applications but still leverage graphics, multimedia, processing power and even local storage of the desktop.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-42112393140842791122011-07-06T23:26:00.000-07:002011-07-08T05:24:01.618-07:00Fun with HTML5I guess I'm a bit late to the game. However, perhaps not too late. <a href="http://en.wikipedia.org/wiki/HTML5">HTML5</a> offers a host of solutions for the kind of application I want to build: rich client, local storage, animated, etc. I walked through several <a href="http://html5-demos.appspot.com/">HTML5 demos</a> and examples over the weekend.<div>I can deliver a rich client experience for an application needing local only storage with nothing but HTML5, JavaScript and CSS3. But for real 'client server' or mashups with shared information, we still need connectivity to the server (Ajax based, RESTful, transport in JSON). </div><div><br /><script type="syntaxhighlighter" class="brush: js"><![CDATA[<br />var xhr = new XMLHttpRequest();<br />xhr.open('GET', '/path/to/image.png', true);<br /><br />xhr.responseType = 'arraybuffer';<br /><br />xhr.onload = function(e) {<br />if (this.status == 200) {<br /><br /> var uInt8Array = new Uint8Array(this.response); // Note: not xhr.responseText<br /><br /> var byte3 = uInt8Array[4]; // byte at offset 4<br />}<br />};<br /><br />xhr.send(); <br />]]></script></div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-58991862217008972952011-07-03T07:13:00.000-07:002011-07-08T05:24:42.386-07:00New Technology StackIn my real life, I actually have to deliver software :). So most of my blogs are about the technologies I am using today as well as best practices I am actually practicing (or trying to practice) in my real life.<div>This blog will focus on the following technology stack for now:</div><div><ul><li><a href="http://jquery.com/">JQuery</a> and <a href="http://jqueryui.com/">JQuery UI</a></li><li><a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CCAQFjAA&url=http%3A%2F%2Fwww.springsource.org%2F&ei=RnoQTvLjGpC40AGg8YSXDg&usg=AFQjCNFJ5T0SOhZD5Qea_gMApqPC3dq_jQ">Spring</a> and<a href="http://static.springsource.org/docs/Spring-MVC-step-by-step/"> Spring MVC</a></li><li><a href="http://en.wikibooks.org/wiki/Java_Persistence">Java Persistence API (JPA 2.0)</a></li></ul>Also I will be looking at:<br /><ul><li><a href="http://www.springsource.org/roo">Spring Roo</a> </li><li>and <a href="http://code.google.com/webtoolkit/">GWT (Google Web Toolkit)</a></li></ul><div>Some techniques and tools include</div><div><ul><li>Sun NetBeans IDE</li><li>SpringSource Tool Suite</li><li>Test Driven Development</li><li>Continuous Integration</li><li>Maven</li></ul><div><br /></div></div><div><br /></div></div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com1tag:blogger.com,1999:blog-7203181491808683266.post-6474224525290236992011-07-03T06:19:00.000-07:002011-07-03T07:12:03.723-07:00Back again and focusing on Enterprise 2.0<div>Why did I rename this blog Enterprise 2.0 Appplication Tecchnologies?</div><div><br /></div><div>I am not abandoning ECM and SOA, but I am expanding to incorporate a more generalized concern.</div><div><br /></div>To me, enterprise 2.0 is bringing the promise of web 2.0 to enterprise applications. Not just in providing a better social experience, but providing a better user experience in general, with more user centered and natural user interfaces.<br /><br />Since Sun quoted "the network is the computer", many (myself included) have been trying to find a way to build enterprise applications that have a rich client experience but are not locked into a heavy installation footprint. Conventional web development has allowed us to build Software as a Service (SaaS), minimizing client and installation requirements and maximizing availability. But on the other hand, these applications are typically designed for the least-common-denominator, heavy on server side processing, have very unnatural user experiences, are constructed with a cacophony of languages, and tend to be a slave to bandwidth and availability of the network.<br /><br />An ignorant corporate sponsor might ask "Why can't we have the rich user experience of the desktop while leveraging the ubiquity of the web?"<br /><br />Well, now we can. Through the power of JavaScript and HTML5 the browser can support truly rich client experiences, while still leveraging the advantages of the web and being available anytime, anywhere on any device. Constructing access to information and actions using the power of REST and semantic net, different user experiences can be developed to share and leverage the knowledge of the enterprise in different ways. And still the enterprise applications can leverage well tested legacy services, be secure and achieve the availability and performance requirements demanded by the corporation.<br /><br />However, I am still concerned with requiring constant connectivity. Most of even the best internet applications that deal with transitional data require continuous high bandwidth connectivity to the services. Especially for the mobile worker (an ever increrasing demographic), being able to work 'off-line' sometimes is essential.<br /><br />So, my search for the answer to the question "Why can't we have the rich user experience of the desktop while leveraging the ubiquity of the web?" includes the following:<br /><ul><li>Rich client experience supporting many user types and many devices, including the use of graphics, multimedia and alternate input technologies</li><li>Secure access to semantically rich resources and services that are flexible and can be leveraged from many different clients</li><li>Connectivity to other legacy systems and services, supporting synchronous and asynchronous models</li><li>Mechanisms that allow users to share and collaborate and work off-line as well as on line</li></ul><div>We want secure access to corporation's transactional information and services available at any time, from anywhere on any device, while allowing users to organize their own experiences and collaborate with others the way they want to.</div><div><br /></div><div>The purpose of this blog is to try to address these concerns and others through technology available today, relevant to my own direct work requirements as well as the purely theoretical.</div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-4266581570143848132010-02-04T07:52:00.000-08:002010-02-04T08:41:28.005-08:00Moving OnI am shifting my focus in my career from Enterprise Content Management with Service Oriented Architectures to more Service Oriented Architectures and <a href="http://en.wikipedia.org/wiki/Rich_Internet_application">Rich Internet Application</a> development. I will be focusing on the <a href="http://www.oracle.com/us/products/middleware/index.htm">Oracle Fusion Middleware</a> stack, not <a href="http://www.alfresco.com/">Alfresco</a>, <a href="http://www.jboss.com/products/jbpm/">jBPM </a>and <a href="http://servicemix.apache.org/home.html">ServiceMix</a>. I will be developing applications using Oracle JDeveloper tools with <a href="http://www.oracle.com/technology/products/adf/index.html">Oracle's Application Development Framework (ADF)</a>.<br /><br />So, I will continue to use this blog, but I will be shifting articles to talk about these technologies.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-91032763452539591772009-07-07T08:00:00.001-07:002009-07-08T08:04:32.276-07:00Looking at Alfresco Share 3.2 Community - Part 3 - Customizing formsThis blog will look at how to display custom types and aspects in Share in the edit metadata feature. The <a href="http://edlovesjava.blogspot.com/2009/06/looking-at-share-32-community-part-2.html">previous blog article</a> showed how to associate content with predefined aspects and show them in the edit metadata form.<br /><br />This example requires <a href="http://wiki.alfresco.com/wiki/Download_Labs_Preview">Alfresco 3.2 Labs</a> (currently Preview).<br /><br />Resources:<br />* <a href="http://wiki.alfresco.com/wiki/Forms">Alfresco forms engine wiki page</a><br />* <a href="http://wiki.alfresco.com/wiki/Forms_Examples#Configuring_A_Form_Control">Alfresco forms engine examples wiki page</a><br /><br />Also see my previous blog entries showing how <a href="http://edlovesjava.blogspot.com/2009/06/looking-at-share-32-community-part-2.html">forms are used in Alfresco Share 3.2</a><br /><br />I will use the example project structure offered by Jeff Potts' great article - <a href="http://ecmarchitect.com/archives/2007/06/09/756">Extending the Alfresco Content model</a> to extend the content model in Alfresco. For the share customization, I will base my project on the <a href="http://edlovesjava.blogspot.com/2008/12/extending-share-1-creating-share.html">Share extension project example</a> in my blog.<br /><br /><span style="font-weight: bold;font-size:130%;" >Creating the new Custom content type</span><br /><br />For this example, I will create a custom content type and modify the edit metadata form shown in Alfresco Share. The content type I will create will center around a custom type called MarketingContent, with an additional Aspect called Brandable. This custom metadata will add the ability to identify the 'brand' to which the Marketing content should be associated, and will require a custom single-select drop down.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh42v9OYzeB2WhTiaIT2km0F-eEGfXsc7Y0F5wlMYFniR5v_pcB9y085624M3P2jgTGf3znD7-jP2Vb2w1aFKzpL-ZPfSiZteu_wojSbqyEcb48aTEXQUkKKeP12vdQ3wtOMvA4YgYwS_Lw/s1600-h/example_content_model.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 292px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh42v9OYzeB2WhTiaIT2km0F-eEGfXsc7Y0F5wlMYFniR5v_pcB9y085624M3P2jgTGf3znD7-jP2Vb2w1aFKzpL-ZPfSiZteu_wojSbqyEcb48aTEXQUkKKeP12vdQ3wtOMvA4YgYwS_Lw/s320/example_content_model.png" alt="" id="BLOGGER_PHOTO_ID_5355784868551031426" border="0" /></a><br /><span style="font-weight: bold;">Step 1 - Setup the project to extend Alfresco with custom content</span><br /><br />1. Download <a href="http://ecmarchitect.com/">Jeff Potts</a>' <a href="http://ecmarchitect.com/images/articles/alfresco-content/content-article-project.zip">custom content example source</a><br />2. Import into Eclipse - assume you already downloaded the Alfresco SDK and imported SDK AlfrescoRemote<br />3. Fix project references as needed<br />4. Update build.properties for ant build (alfresco.sdk.remote.home and alfresco.web.root)<br /><br /><span style="font-weight: bold;">Step 2 - Modify project for new custom content model</span><br /><br />Four files to modify:<br />* PROJECT_HOME/src/alfresco/extension/someco-model-content.xml<br />* PROJECT_HOME/src/alfresco/extension/scModel.xml<br />* PROJECT_HOME/src/alfresco/extension/web-client-config-custom.xml<br />* PROJECT_HOME/src/alfresco/extension/webclient.properties<br /><br /><span style="font-style: italic;">someco-model-content.xml (renamed to 'orbitz-model-content.xml')</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version='1.0' encoding='UTF-8'?><br /><!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'><br /><br /><beans><br /><!-- Registration of new models --><br /><bean id="extension.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap"><br /><property name="models"><br /><list><br /> <value>alfresco/extension/orbModel.xml</value><br /></list><br /></property><br /></bean><br /></beans><br /></code></pre><br /><span style="font-style: italic;">scModel.xml (renamed to 'orbModel.xml')</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version="1.0" encoding="UTF-8"?><br /><!-- Definition of new Model --><br /><model name="orb:orbitzmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0"><br /><br /><!-- Optional meta-data about the model --><br /><description>Orbitz Marketing Model</description><br /><author>Ed Wentworth</author><br /><version>1.0</version><br /><br /><!-- Imports are required to allow references to definitions in other models --><br /><imports><br /> <!-- Import Alfresco Dictionary Definitions --><br /> <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" /><br /> <!-- Import Alfresco Content Domain Model Definitions --><br /> <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" /><br /></imports><br /><br /><!-- Introduction of new namespaces defined by this model --><br /><namespaces><br /> <namespace uri="http://www.orbitz.com/model/content/1.0" prefix="orb" /><br /></namespaces><br /><constraints><br /> <constraint name="orb:brands" type="LIST"><br /> <parameter name="allowedValues"><br /> <list><br /> <value>Orbitz</value><br /> <value>CheapTickets</value><br /> <value>eBookers</value><br /> </list><br /> </parameter><br /> <parameter name="caseSensitive"><value>true</value></parameter><br /> </constraint><br /></constraints><br /><br /><types><br /> <!-- Enterprise-wide generic document type --><br /> <type name="orb:doc"><br /> <title>Orbitz Document</title><br /> <parent>cm:content</parent><br /> <mandatory-aspects><br /> <aspect>cm:generalclassifiable</aspect><br /> </mandatory-aspects><br /> </type><br /><br /> <type name="orb:marketing_content"><br /> <title>Orbitz Marketing Content</title><br /> <parent>orb:doc</parent><br /> <properties><br /> <property name="orb:effective_from"><br /> <type>d:date</type><br /> </property><br /> <property name="orb:effective_to"><br /> <type>d:date</type><br /> </property><br /> </properties><br /> </type><br /></types><br /><br /><aspects><br /> <aspect name="orb:brandable"><br /> <title>Orbitz Brandable</title><br /> <properties><br /> <property name="orb:brand"><br /> <type>d:text</type><br /> <mandatory>true</mandatory><br /> <constraints><br /> <constraint ref="orb:brands"/><br /> </constraints><br /> </property> <br /> </properties><br /> </aspect><br /><br /></aspects><br /></model><br /></code></pre><br /><span style="font-style: italic;">web-client-config-custom.xml</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><alfresco-config><br /><br /><!-- add webable aspect properties to property sheet --><br /><config evaluator="aspect-name" condition="orb:brandable"><br /> <property-sheet><br /> <show-property name="orb:brand" display-label-id="brand" /><br /> </property-sheet><br /></config><br /><br /><!-- show related documents association on doc property sheet --><br /><config evaluator="node-type" condition="orb:doc"><br /> <property-sheet><br /> </property-sheet><br /></config><br /><br /><!-- show related documents association on whitepaper property sheet --><br /><config evaluator="node-type" condition="orb:marketing_content"><br /> <property-sheet><br /> <show-property name="orb:effective_from" display-label-id="effective_from" /><br /> <show-property name="orb:effective_to" display-label-id="effective_to" /><br /> </property-sheet><br /></config><br /><br /><!-- add orbitz types to add content list --><br /><config evaluator="string-compare" condition="Content Wizards"><br /> <content-types><br /> <type name="orb:doc" /><br /> <type name="orb:marketing_content" /><br /> </content-types><br /></config><br /><br /><config evaluator="string-compare" condition="Action Wizards"><br /> <!-- The list of aspects to show in the add/remove features action --><br /> <!-- and the has-aspect condition --><br /> <aspects><br /> <aspect name="orb:brandable"/><br /> </aspects><br /><br /> <!-- The list of types shown in the is-subtype condition --><br /> <subtypes><br /> <type name="orb:doc" /><br /> <type name="orb:marketing_content" /><br /> </subtypes> <br /><br /> <!-- The list of content and/or folder types shown in the specialise-type action --><br /> <specialise-types><br /> <type name="orb:doc" /><br /> <type name="orb:marketing_content" /><br /> </specialise-types> <br /></config><br /><br /><config evaluator="string-compare" condition="Advanced Search"><br /> <advanced-search><br /> <content-types><br /> <type name="orb:doc" /><br /> <type name="orb:marketing_content" /><br /> </content-types><br /> <custom-properties><br /> <meta-data aspect="orb:brandable" property="orb:brand" display-label-id="brand" /><br /> </custom-properties><br /> </advanced-search><br /></config><br /></alfresco-config><br /></code></pre><br /><span style="font-style: italic;">webclient.properties</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>#orb:marketing_content<br />effective_from=Effective From<br />effective_to=Effective To<br /><br />#orb:brandable<br />brand=Brand<br /></code></pre><br /><br /><span style="font-weight: bold;">Step 3 - Deploy</span><br /><br />Make sure that build.properties is properly configured<br /><br /><ol><li>Run ant deploy</li></ol><br /><span style="font-weight: bold;">Step 4 - Test Model</span><br />As a prerequisite, you should go to Share client and create a new site (i.e. 'Merchandising')<br /><ol><li>In Alfresco Explorer client - Upload content as type 'Marketing Content'</li><li>Apply 'Brandable' aspect</li><li>Modify content propertis -- should look like picture below</li></ol><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLmJBgQvxumRCxnvwdSk3lBHG49hIN28n7jFaEbUdM6QOlEVLEbYoRPLax1OPRaGtDmOPvimZqs75w_Oiy0yMkPJrPLVNF-a0O5tofqcr-nyqEL_Goe-1Uet5GOVrMbcZRl7nUnQC9HbY2/s1600-h/example-custom-model-prop-sheet.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 262px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLmJBgQvxumRCxnvwdSk3lBHG49hIN28n7jFaEbUdM6QOlEVLEbYoRPLax1OPRaGtDmOPvimZqs75w_Oiy0yMkPJrPLVNF-a0O5tofqcr-nyqEL_Goe-1Uet5GOVrMbcZRl7nUnQC9HbY2/s320/example-custom-model-prop-sheet.png" alt="" id="BLOGGER_PHOTO_ID_5355783608553752402" border="0" /></a><br /><br /><span style="font-weight: bold;"></span>In order for this form to render, we need to modify the forms configuration to let Share know how to render our custom type and aspect. For details on this, see the <a href="http://wiki.alfresco.com/wiki/Forms">forms wiki</a> and <a href="http://wiki.alfresco.com/wiki/Forms_Examples">forms examples</a> wiki pages.<br /><br /><span style="font-weight: bold;">Step 5: Configure share to render the orb:market_content type and orb:brandable aspect</span><br /><br />Create a file named <i>'web-framework-config-custom.xml'</i> and deploy it to the share/WEB-INF/classes/alfresco/web-extension directory with the contents:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><alfresco-config><br /><br /> <config evaluator="node-type" condition="orb:marketing_content"><br /> <forms><br /> <form><br /> <field-visibility><br /> <!-- inherited from cm:content --><br /> <show id="cm:name" /><br /> <show id="cm:title" force="true" /><br /> <show id="cm:description" force="true" /><br /> <show id="mimetype" /><br /> <show id="cm:author" force="true" /><br /> <show id="size" for-mode="view" /><br /> <show id="cm:creator" for-mode="view" /><br /> <show id="cm:created" for-mode="view" /><br /> <show id="cm:modifier" for-mode="view" /><br /> <show id="cm:modified" for-mode="view" /><br /> <br /> <!-- specific for orb:marketing_content --><br /> <show id="orb:effective_from" /><br /> <show id="orb:effective_to" /><br /><br /> <!-- aspect orb:brandable --> <br /> <show id="orb:brand" /><br /> <br /> </field-visibility><br /> <appearance><br /> <field id="orb:effective_from" label="Effective From"/><br /> <field id="orb:effective_to" label="Effective To"/><br /> <field id="orb:brand" label="Brand"><br /> <control template="controls/selectone.ftl"><br /> <control-param name="options">Orbitz,CheapTickets,eBookers</control-param><br /> </control><br /> </field><br /> </appearance><br /> </form><br /> </forms><br /> </config><br /></alfresco-config><br /></code></pre><br /><br />As we see here, we needed to copy all of the default properties from cm:content as well as add our unique properties for orb:marketing_content, and add the orb:brands aspect. This gives us very fine-grained control, but we can see there could be a lot of repeated configurations if we use types with aspects in complex ways.<br /><br /><span style="font-weight: bold;">Step 5: Run the example</span><br /><br />Now we can go ahead and restart share and log in and select 'edit metadata' of our content previously checked in with the type orb:marketing_content and set with the orb:brandable aspect.<br /><br />Now we should see the following:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxTcwhsd93BrMlm2oYHAL0sNjmIvNBlJDd0xitL-xX17rhlSZu34fuGTpCYD7ZEB47nke5YqlZfztG2lepggQi4Suunw9PHhwF-GLNVGVeuCeQ_foFbVWsK0eLEalneiBnSAHPoz_Way1O/s1600-h/example-custom-model-share-prop-sheet.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxTcwhsd93BrMlm2oYHAL0sNjmIvNBlJDd0xitL-xX17rhlSZu34fuGTpCYD7ZEB47nke5YqlZfztG2lepggQi4Suunw9PHhwF-GLNVGVeuCeQ_foFbVWsK0eLEalneiBnSAHPoz_Way1O/s320/example-custom-model-share-prop-sheet.png" alt="" id="BLOGGER_PHOTO_ID_5356102348768725234" border="0" /></a><br /><br />We customized the selectone.ftl control template to provide our list of brands. One can consider an extension to this control would be to look up this list from a data web-script.<br /><br />We should create our own selectone lookup from an external source in an ajax way. We will do so in a further blog article.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com10tag:blogger.com,1999:blog-7203181491808683266.post-38128863616560427312009-06-30T10:46:00.001-07:002009-07-08T08:04:18.352-07:00Looking at Alfresco Share 3.2 Community - Part 2 - Edit Custom MetadataThis series of blog entries is intended to walk through the new features of Alfresco Share 3.2 Labs. In the <a href="http://edlovesjava.blogspot.com/2009/06/looking-at-share-32-community.html">previous blog entry</a>, I built the community version from source, and looked at the new Admin Console feature. This blog, I will look at how Share exposes Custom metadata, and allows users to assign aspects to uploaded content.<br /><br /><span style="font-size:130%;"> <span style="font-weight: bold;">Viewing and Editing Custom Metadata</span></span><br /><blockquote><span style="font-size:85%;"><span style="font-style: italic;">I have Alfresco 3.2 Labs running on my Windows XP Laptop, built from source updated June 23, 2009, and I have deployed both Alfrsco.war and share.war to an instance of Tomcat 6.</span></span></blockquote>In previous versions of Share, it wasn't possible to view or edit custom metadata. Share 3.2 Labs addresses this deficit. Further, it leverages Surf's new forms architecture to render the metadata form dynamically. We will look at this in detail later. First, I will walk through the user interface for metadata.<br /><br />In order to show Share's ability to view custom metadata, we need to create a new Site and Upload some content. Here are the steps briefly:<br /><ol><li>Log into Share (as admin/admin) --> The User Dashboard renders with the 'My Sites' dashlet.</li><li>Click on 'Create Site' in the My Sites dashlet (or select 'create site' from the 'Sites' drop down in the top menu) --> The Create Site popup will render.</li><li>Enter the Name, URL name, and Description (optional) of this new site. (In my example I created a site with Name=Merchandising, URL name=Merchandising).</li><li>Press 'Ok' when complete and the new site will be created, returning you to the user dashboard.<br /></li><li>Select the new site from the 'My Sites' dashlet, or the top menu's Sites dropdown --> the Site dashboard will render.</li><li>Navigate to the 'Document Library' page to upload new content --> the Document Library page renders.</li><li>Click on 'Upload' button to upload some content to the default documents folder.<br /></li></ol>Now that we have content, we should be able to view the metadata.<br /><br />Here I am looking at Document Library in the merchandising site, where have uploaded an XML file and a couple of image files...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggqkknA5Qr4QNLh6LBDXGZ-CtOQmIUvzXOGjDSuhT1DaENiTF9_3T2tOdNRi8jgEfAz1A-zQdsG9dCG_hbEbp9Ej6fmH4hPSYWvY0_a9-u2pQQAbZWOuBqiD15H1y71WNVeevkiL8N6ae3/s1600-h/share32-doclib.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggqkknA5Qr4QNLh6LBDXGZ-CtOQmIUvzXOGjDSuhT1DaENiTF9_3T2tOdNRi8jgEfAz1A-zQdsG9dCG_hbEbp9Ej6fmH4hPSYWvY0_a9-u2pQQAbZWOuBqiD15H1y71WNVeevkiL8N6ae3/s320/share32-doclib.png" alt="" id="BLOGGER_PHOTO_ID_5353182545772611762" border="0" /></a><br />Hovering over one of the files in the list renders the actions that can be performed on that file.<br />Actions include:<br /><ul><li>Download</li><li>Edit Metadata</li><li>Upload new Version</li><li>More > (we will look at some of these later)<br /></li></ul><br />This list of actions is very configurable. We may show how to do this in a later blog.<br />The 'Edit Metadata' action is what we want. Select the 'Edit Metadata' action for the uploaded document.<br /><br />Here I selected edit metadata for an XML ddocument named 'Article.EnableCookies.xml'...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgrzFoafA0WaQV529NnR57GzhOyGlz3rAuxPLcaaY2o7-qY0pgFgeQaYUuOjf9INjSNpibsKtiDVF9u62p152npl9XOtkk2QSawQntjdqXFhHx85iQph8WLzyDBw9o1ZVdpwFNo1Vugb-E/s1600-h/share32-doclib-editmetadata.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgrzFoafA0WaQV529NnR57GzhOyGlz3rAuxPLcaaY2o7-qY0pgFgeQaYUuOjf9INjSNpibsKtiDVF9u62p152npl9XOtkk2QSawQntjdqXFhHx85iQph8WLzyDBw9o1ZVdpwFNo1Vugb-E/s320/share32-doclib-editmetadata.png" alt="" id="BLOGGER_PHOTO_ID_5353184361058662162" border="0" /></a><br /><br />This looks pretty normal. It is the same popup as with previous version of Share allowing edit of basic metadata properties like Name, Title, Description and tags. But notice in the Upper right corner 'Full metadata Edit page...'. Selecting this will render a full screen rendering all of the document's metadata.<br /><br />Here I am showing the full metadata page of the xml file I uploaded...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhB4JM0rbnsK4Y5-oUXVZadkdpv-TFQc9UNgNksncR12H9VK1oDUlTuShzcnL2hC0NjBf-bzmb4LaXsrXlcbEIQrmF4bK4l4QbBaub71Q8z-17SkaF0GQfxWf8ytkfOFQPMr5uSXn2lvI5N/s1600-h/share32-doclib-editmetadata-full.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhB4JM0rbnsK4Y5-oUXVZadkdpv-TFQc9UNgNksncR12H9VK1oDUlTuShzcnL2hC0NjBf-bzmb4LaXsrXlcbEIQrmF4bK4l4QbBaub71Q8z-17SkaF0GQfxWf8ytkfOFQPMr5uSXn2lvI5N/s320/share32-doclib-editmetadata-full.png" alt="" id="BLOGGER_PHOTO_ID_5353184928268677522" border="0" /></a><br />In this example, two new fields are rendered: the Mimetype, and the Author.<br /><br />This full page renders the metadata form in 'Edit' mode using the new <a href="http://wiki.alfresco.com/wiki/Forms">forms engine</a>. The form is rendered by obtaining a form configuration from the remote alfresco repository. This form configuration is dynamically generated from the available metadata on the content item being viewed. The form archtiecture is capable of rendering the following types of controls (see more detail in the <a href="http://wiki.alfresco.com/wiki/Forms">forms service</a> wiki:<br /><ul><li>mimetype -- <select> <option> from a list of mime types (i.e. application/pdf, ...)</li><li>size -- display file size<br /></li><li>textarea -- <textarea> field configurable to r rows and c columns<br /></li><li>slectone -- <select> field with <option> tags dynamically rendered<br /></li><li>textfiled -- <input type="text"> field<br /></li><li>category -- picker selecting one or more categories to associate with the content<br /></li><li>endocding -- <select> field to choose <option> from list of encoding types (i.e. UTF-8, ISO-8859-1, ...)<br /></li><li>association -- picker selecting one or more other content items to associate this content item too<br /></li><li>checkbox -- <input type="checkbox"> field<br /></li><li>readonly -- <input type="text"> rendered as read only<br /></li><li>date -- renders a date picker setting value to an <input type="text"> field<br /></li><li>period -- selecting x days, weeks, etc.<br /></li></ul><span style="font-size:130%;"><span style="font-weight: bold;">Associating Aspects</span></span><br /><br />To really show the custom metadata feature, we will look at another great feature added to Share 3.2, the ability to associate new aspects to documents. When we associate a new aspect, new properties can be added to the document and the edit metadata full form should reflect those changes. To do this, let us add the 'dublin core' aspect to the document we just looked at.<br /><ol><li>Hover over the document --> the actions should render</li><li>Select the 'more' option --> the more options should render</li><li>Select the 'manage aspects' option --> the manage aspects popup will render</li><li>Select the '+' by the 'Dublin Core' aspect in the 'Available to Add' list --> The 'Dublin core' should be removed from the 'Available to Add' and added to the 'Currently Selected' list</li><li>Select the 'Apply changes' button to save the changes</li></ol>Now we can look at the edit metadata again and see the new metadata properties added via the Dublin Core aspect<br /><br />Here we see the edit metadata full page with the dublin core properties<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBNbAHAXmvjtZfeEXEEshyphenhyphenxTyOQTMrCk5M_yQfg8pMEcaQkLpdR3JlU8N5vP1BDFzsCC9pNk6hAYE29C8caumXVwqzMklrgSkCf_ae6dBjZOtAjluKAK-zEZpWbtqBIgZira2qM-Y_AmVx/s1600-h/share32-doclib-editmetadata-full-dublin.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBNbAHAXmvjtZfeEXEEshyphenhyphenxTyOQTMrCk5M_yQfg8pMEcaQkLpdR3JlU8N5vP1BDFzsCC9pNk6hAYE29C8caumXVwqzMklrgSkCf_ae6dBjZOtAjluKAK-zEZpWbtqBIgZira2qM-Y_AmVx/s320/share32-doclib-editmetadata-full-dublin.png" alt="" id="BLOGGER_PHOTO_ID_5353199408416108018" border="0" /></a><br /><br />The next blog entry will look at customizing share to display our own content types and aspects.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com2tag:blogger.com,1999:blog-7203181491808683266.post-7003657142923591652009-06-26T06:35:00.000-07:002009-07-08T08:03:59.816-07:00Looking at Alfresco Share 3.2 Community<span style="font-weight: bold;font-size:130%;" >Building Alfresco Labs 3.2 from Community</span><br /><br />This blog and others to follow will walk through the new features of Alfresco Share 3.2 Labs. First, I will walk through some of the key features. Then I will open up the box and look in side at a few choice features. Finally, I will show examples of the extensibility of this new version of Share.<br /><br /><a href="http://wiki.alfresco.com/wiki/Source_Code">Building from source</a> was fairly straight forward. I use <a href="http://tortoisesvn.tigris.org/">Tortoise SVN</a> as my svn client (on my Windows XP laptop). I 'updated' to the latest code (as of 23 June 09). I used ant to build, with my environment setup as recommended in the <a href="http://wiki.alfresco.com/wiki/Alfresco_SVN_Development_Environment">Alfresco SVN Development environment wiki</a>.<br /><br />When attempting to deploy to tomcat using '<span style="font-size:85%;"><span style="font-family:courier new;">ant build-tomcat</span></span>' and '<span style="font-size:85%;"><span style="font-family:courier new;">ant incremental-tomcat</span></span>', the ant task complained that I needed to use tomcat 6. I downloaded <a href="http://tomcat.apache.org/">Apache tomcat 6.0.20</a>. I noticed that tomcat 6 doesn't create a 'shared' folder by default, which is used by alfresco for extension. But I found a wiki page for <a href="http://wiki.alfresco.com/wiki/Install_Tomcat6">configuring tomcat 6</a> that explains how to configure tomcat 6 for this purpose.<br /><br />Because I have multiple versions of alfresco on this machine, I didn't want to sacrifice the default db (mysql database named alfresco). I had problems with configuring other db vendor, however. I followed the <a href="http://wiki.alfresco.com/wiki/Database_Configuration">configuring db wiki page for 3.2</a> and tried boty Derby and HSQL as alternatives. However, various problems occurred. HSQL and Derby didn't seem to have corrected hibernate mappings and/or sql build scripts. So I chose to create another db instance on mysql named alfresco2. I'm sure with a little more patience, I would have succeeded. But I'm impatient.<br /><br />In order to configure to this new db (and as described on the <a href="http://wiki.alfresco.com/wiki/Database_Configuration">database configuration wiki page</a>), I found that there is now a file 'alfresco-global.properties' that you can copy from your TOMCAT_HOME/webapps/alfresco/WEB-INF/classes directory (named 'alfresco-global.properties.sample') to TOMCAT_HOME/shared/classes directory, renamed to 'alfresc-global.properties'. I noted that this did NOT go into an 'alfresco/extensions' subdirectory as in the past with 'custom-repository.properties'.<br /><br />Starting up tomcat on my windows xp with the default configurations caused a problem with out of heap space and, later PermGen. I changed the startup parameters to add more heap and PermGen (-Xms256M -Xmx512M -XX:MaxPermSize=256m) and finally I was in business.<br /><span style="font-weight: bold;font-size:130%;" ><br />First Impressions of Share 3.2</span><br /><br />After logging in to share (i.e. http://localhost:8080/share) with the default user account admin/admin, the admin user dashboard is rendered first. Nothing new here. However in the header, right justified I saw a 'Admin Console' button. Clicking it, I see you can now create new users and groups from Share directly!<br /><br />Here I logged in as admin/admin...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizzU0K82lQDBcjuaytl7Y48L6Lqr0vomwJshPYVoDPravbTKe78zBtQP480CE4kbMq0Dr1hhqiYpDbcMD1ZlJR7ORoUmcMXOcn6TkWwgh9DjPOrqFsm6gs-yRimH4QIciflKJqaRn28Wy5/s1600-h/share32-userdashboard.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizzU0K82lQDBcjuaytl7Y48L6Lqr0vomwJshPYVoDPravbTKe78zBtQP480CE4kbMq0Dr1hhqiYpDbcMD1ZlJR7ORoUmcMXOcn6TkWwgh9DjPOrqFsm6gs-yRimH4QIciflKJqaRn28Wy5/s320/share32-userdashboard.png" alt="" id="BLOGGER_PHOTO_ID_5353147054314037650" border="0" /></a><br /><br /><br /><span style="font-weight: bold;font-size:130%;" >Admin Console</span><br /><br />It seems there is a new role for Share users, that of 'Manager'. This role is described in <a href="http://forums.alfresco.com/en/viewtopic.php?f=47&t=16600">the forums</a>. The 'Manager' role has permisions to expose the new 'admin console.'<br /><br />The Admin Console is accesssed by clicking the 'Admin Console' button on the top menu. This console is configured with an extensible list of console 'tools'. The console opens up showing the first tool: Groups.<br /><br /><span style="font-weight: bold; font-style: italic;">Groups Tool</span><br /><br />Here, I clicked on the 'Admin Console' button...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiVeAvvrMt1zGEaZo0fODlILePM1KQuwNuWiyM7xQ58-XpZ2n4T959pH1sc3zfAP86e_nYBGjGWDhyxcWJGLIIsShTz2rBmhc8JBD7pbIjJQI0naBD8ltp7tN50_FUQagr5FAX4igEKU_2/s1600-h/share32-adminconsole-groups.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 248px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiVeAvvrMt1zGEaZo0fODlILePM1KQuwNuWiyM7xQ58-XpZ2n4T959pH1sc3zfAP86e_nYBGjGWDhyxcWJGLIIsShTz2rBmhc8JBD7pbIjJQI0naBD8ltp7tN50_FUQagr5FAX4igEKU_2/s320/share32-adminconsole-groups.png" alt="" id="BLOGGER_PHOTO_ID_5352758573602125330" border="0" /></a><br /><br />The 'Groups' tool allows a user to drill down through the hierarchy of groups, and create new groups, edit them and remove them. To represent the hierarchy, the user interface represents levels of groups by showing child groups in a new list panel created to the right of the group list panel with the selected group. The successive levels of group lists all have similar functionality (recursively rendered) to add, edit and remove groups and their children. This UI approach is a bit odd, but effective.<br /><br />Clicking 'new group' opens the 'Create Group' form, prompting the user to enter the required fields for: Identifier and Display name. Once entered, the buttons 'Create Group', 'Create and Create Another' are enabled. The 'Create Group' creates a group with the given Identifier and Display name, and returns back to the 'Groups' tool main page. The 'Create and Create Another' creates a group as the 'Create Group' button but clears the form to enter another group. The labels of these buttons might be a bit confusing at first.<br /><br />Here I clicked the 'New group' button ...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjL4dWDAx1TfA3dcaa2vz2Ps5CRD5xftkKvQaFBbGfIHia7HK_z9hQ8JMel9T3Xvvj_GzeEFsEV9w8q7H3-rPSo_eT_zyan8qnUnTXHn3hWA-BHiccpB3k9D73UISBTFfIjna8we1CoRLIU/s1600-h/share32-adminconsole-groups-create.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 248px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjL4dWDAx1TfA3dcaa2vz2Ps5CRD5xftkKvQaFBbGfIHia7HK_z9hQ8JMel9T3Xvvj_GzeEFsEV9w8q7H3-rPSo_eT_zyan8qnUnTXHn3hWA-BHiccpB3k9D73UISBTFfIjna8we1CoRLIU/s320/share32-adminconsole-groups-create.png" alt="" id="BLOGGER_PHOTO_ID_5352760849808985922" border="0" /></a><br />I tried creating a new group and the group called 'testg', but it wasn't added to the list immediately. Reopening the admin console later, I saw the group I created listed correctly (see https://issues.alfresco.com/jira/browse/ALFCOM-3110).<br /><br />In the Groups tool of the Admin Console, hovering over an item indicates other actions that can be performed on that item, including 'edit' and 'delete'.<br /><br /><span style="font-weight: bold; font-style: italic;">Users tool</span><br /><br />The users tool of the 'Admin Console' allows the manager to create new users and edit users. The search box requires at least one character entered before search works. (Perhaps adding a note on this screen or disabling/enabling the 'search' button might help.) This search text seems to be a partial match to find anything with the text entered 'in' the user's name.<br /><br />Here I typed 'a' and pressed 'Search'...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9X0yRTCD-8xiR5hCd32dLZLnk99SYbWFIv6KrZF0orm1Xf_e4C3pif8x1MAI6zKDRIgKO9Nt_B_7jZYmcLig9xSMxrbty9GDGmZOWZMAy02K49FpaC-ghtlPHCxAXr-kJfr6Wi7yJCUEt/s1600-h/share32-adminconsole-users.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9X0yRTCD-8xiR5hCd32dLZLnk99SYbWFIv6KrZF0orm1Xf_e4C3pif8x1MAI6zKDRIgKO9Nt_B_7jZYmcLig9xSMxrbty9GDGmZOWZMAy02K49FpaC-ghtlPHCxAXr-kJfr6Wi7yJCUEt/s320/share32-adminconsole-users.png" alt="" id="BLOGGER_PHOTO_ID_5353144444128025986" border="0" /></a><br />A new user can be created from the users tool main page by clicking the 'New User' button upper right, or an existing user details can be viewed, and later edited, by clicking on the user name in the list.<br /><br />Here I clicked on the 'Jane Smith' name in the users list...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAx0fIArR8tGAV9CceaNo-fqBe-10Gvw4ai9H9yD0kVktpjhurwdo3dD6t3h3zs197Hc-UNgyJYB1oVzvuFQyrhrEAJg3-o75qG5ZVGpi7Eqz5KXXOLxF5ohsQ3VBx_NmOyV3celuTWlwz/s1600-h/share32-adminconsole-users-edit.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 275px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAx0fIArR8tGAV9CceaNo-fqBe-10Gvw4ai9H9yD0kVktpjhurwdo3dD6t3h3zs197Hc-UNgyJYB1oVzvuFQyrhrEAJg3-o75qG5ZVGpi7Eqz5KXXOLxF5ohsQ3VBx_NmOyV3celuTWlwz/s320/share32-adminconsole-users-edit.png" alt="" id="BLOGGER_PHOTO_ID_5353145960264743522" border="0" /></a>From the user profile view page there are two buttons: Edit User and Delete User. From the Edit User page, you can enter basic information, assign the user to groups, set a quota, change passwords and select photos.<br /><br /><span style="font-weight: bold;">Next blog entry -- look at edit metadata feature.</span>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com1tag:blogger.com,1999:blog-7203181491808683266.post-52989410758684164652009-05-12T14:32:00.000-07:002009-05-12T14:59:06.671-07:00Alfresco Regional MeetupOn Wednesday April 29, 2009, The Chicago area hosted a <a href=": http://www.alfresco.com/about/events/2009/04/chicagomeetup/">regional alfresco meetup</a> where I was in attendance. There were a few Alfrewsco speakers and a few community speakers. Among the speakers were <a href="http://ecmarchitect.com/">Jeff Potts</a> talking about Drupal integration, and <a href="http://blogs.alfresco.com/wp/uzi/">Michael Uzquiano</a> talking about WCM roadmap and Share. Also, <a href="http://drquyong.com/myblog/">Yong Qu</a> presented a bit on integration to email clients. <br /><br />I also presented some of our work on Alfresco Share developing a custom contribution client for our marketing content. For those interested, my <a href="http://www.wentsoft.com/Alf%20Community%20Meetup%20Presentation.zip">PowerPoint presentation</a> is located here.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-64513099443106426022009-04-07T18:47:00.001-07:002009-04-07T19:38:06.765-07:00Working with jBPM workflows in Alfresco - Part 3 : Alfresco scriptsIn the previous blog entry: <a href="http://edlovesjava.blogspot.com/2009/04/working-with-jbpm-workflows-in-alfresco.html">Working with jBPM Workflows in Alfresco - Part 1 : jBPM</a>, we saw how to create a simple jBPM workflow and test it.<br /><br />In a blog entry before that: <a href="http://edlovesjava.blogspot.com/2009/04/working-with-jbpm-workflows-in-alfresco_07.html">Working with jBPM Workflows in Alfresco - Part 2 : Embedded Alf - SDK</a>, we saw how to run our workflow in Alfresco Embedded using the SDK.<br /><br />In this blog entry, we will add some Alfresco scripts into our workflow that make use of Alfresco APIs.<br /><br />Step 1 - Logging in JavaScript via Alfresco logger<br /><br />Starting simple, lets modify our process definition to print out a log message every time we transition to a new state. This is a handy technique if we're not sure our logic is going the way we think it should. Here is the updated processs definiton:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version="1.0" encoding="UTF-8"?><br /><br /><process-definition xmlns="urn:jbpm.org:jpdl-3.2" name="publishContentBasic"><br /> <start-state name="start"><br /> <transition name="to_requested" to="requested"><br /> <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript"><br /> <script><br /> logger.log("Going to requested state");<br /> </script><br /> </action><br /> </transition><br /> </start-state><br /><br /> <state name="requested"><br /> <transition to="processing" name="to_processing"><br /> <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript"><br /> <script><br /> logger.log("Going to processing state");<br /> </script><br /> </action><br /> </transition><br /> </state><br /><br /> <state name="processing"><br /> <transition to="succeeded" name="to_succeeded"><br /> <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript"><br /> <script><br /> logger.log("Going to succeeded state");<br /> </script><br /> </action><br /> </transition><br /> <transition to="failed" name="to_failed"><br /> <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript"><br /> <script><br /> logger.log("Going to failed state");<br /> </script><br /> </action><br /> </transition><br /> </state><br /><br /> <state name="succeeded"><br /> <transition to="end" name="to_end"><br /> <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript"><br /> <script><br /> logger.log("Going to end state");<br /> </script><br /> </action><br /> </transition><br /> </state><br /><br /> <state name="failed"><br /> <transition to="end" name="to_end"><br /> <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript"><br /> <script><br /> logger.log("Finishing");<br /> </script><br /> </action><br /> </transition><br /> </state><br /><br /> <end-state name="end"></end-state><br /></process-definition><br /></code></pre>Here we see the new process containing an action element in the transition element calling out the org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript class. This class provides a java script environment with the Alfresco JavaScript API default objects instantiated and ready to use. One of which is the logger.<br />This action wraps a <script> element containing the javascript to run.<br /><br />To view the results, first change the log4j.properties config to show the javascipt log messages (and reduce some of the noise).<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code># Set root logger level to DEBUG and its only appender to CONSOLE.<br />log4j.rootLogger=WARN, CONSOLE<br /><br /># CONSOLE<br />log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender<br />log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout<br />log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%t] %-5p %C{1} : %m%n<br /><br /># LIMIT CATEGORIES<br />log4j.logger.org.jbpm=INFO<br />#log4j.logger.org.jbpm.graph=DEBUG<br />log4j.logger.com.sample=DEBUG<br /># Hibernate debugging levels and their output<br />log4j.logger.org.hibernate=WARN<br />#Log all SQL DML statements as they are executed<br />#log4j.logger.org.hibernate.SQL=TRACE<br />#Log all JDBC parameters<br />#log4j.logger.org.hibernate.type=TRACE<br />#Log all SQL DDL statements as they are executed<br />#log4j.logger.org.hibernate.tool.hbm2ddl=DEBUG <br />#Log the state of all entities (max 20 entities) associated with the session at flush time<br />#log4j.logger.org.hibernate.pretty=DEBUG <br />#Log all second-level cache activity<br />#log4j.logger.org.hibernate.cache=DEBUG <br />#Log transaction related activity<br />#log4j.logger.org.hibernate.transaction=DEBUG <br />#Log all JDBC resource acquisition<br />#log4j.logger.org.hibernate.jdbc=TRACE <br />#Log HQL and SQL ASTs and other information about query parsing<br />#log4j.logger.org.hibernate.hql.ast=DEBUG <br />#Log all JAAS authorization requests<br />#log4j.logger.org.hibernate.secure=DEBUG <br />#Log everything (a lot of information, but very useful for troubleshooting)<br />#log4j.logger.org.hibernate=DEBUG <br />#log4j.logger.org.hibernate.tools=DEBUG <br /><br />log4j.logger.org.alfresco=INFO<br /><br />log4j.logger.org.alfresco.repo.jscript=DEBUG<br /><br /><br /></code></pre><br />The last line: log4j.logger.org.alfresco.repo.jscript=DEBUG does the trick. Now run the PublishContentBasicProcessAlfTest as a JUnit test and see the results. You should get something like the following in the log:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>20:42:22,574 [main] DEBUG ScriptResourceHelper : Imports resolved, adding resource '_root<br />20:42:22,761 [main] DEBUG ScriptLogger : Going to requested state<br />20:42:22,765 [main] DEBUG RhinoScriptProcessor : Time to execute script: 189ms<br />20:42:22,893 [main] DEBUG ScriptResourceHelper : Imports resolved, adding resource '_root<br />20:42:22,899 [main] DEBUG ScriptLogger : Going to processing state<br />20:42:22,901 [main] DEBUG RhinoScriptProcessor : Time to execute script: 6ms<br />20:42:22,936 [main] DEBUG ScriptResourceHelper : Imports resolved, adding resource '_root<br />20:42:22,942 [main] DEBUG ScriptLogger : Going to succeeded state<br />20:42:22,943 [main] DEBUG RhinoScriptProcessor : Time to execute script: 5ms<br />20:42:23,004 [main] DEBUG ScriptResourceHelper : Imports resolved, adding resource '_root<br />20:42:23,010 [main] DEBUG ScriptLogger : Going to end state<br />20:42:23,011 [main] DEBUG RhinoScriptProcessor : Time to execute script: 5ms<br /></code></pre><br />Lets do something more interesting. Supposing we wanted to create and/or update some content. The Alfresco JavaScipt API cookbook has an interesting example which I will steal for this purpose. Add the following javascript to the script element of the 'to_requested' transition:<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code><script><br /> logger.log("Going to requested state");<br /> logger.log("trying to create file and make it versionable");<br /> <br /> var doc = userhome.createFile("checkmeout.txt");<br /> doc.addAspect("cm:versionable");<br /> doc.content = "original text";<br /> logger.log("created versionable doc with content '"+doc.content+"'");<br /><br /> var workingCopy = doc.checkout();<br /> workingCopy.content = "updated text 1";<br /><br /> doc = workingCopy.checkin();<br /><br /> workingCopy = doc.checkout();<br /> workingCopy.content = "updated text 2";<br /><br /> doc = workingCopy.checkin("a history note", true);<br /> logger.log("checked doc out and in a couple of times");<br /></script><br /><br /></code></pre><br />In this example, we create a new item of content with some text and make it versionable. Then we check it out and updated it. And again checkout and update it, and on check in we use a comment. <br /><br />Note: be careful as to what syntax you use in the script element. Since this appears in XML, we must escape any characters that can cause this script body to parse incorrectly (such as < and > characters). I had to remove the // comment lines to make this work correctly.<br /><br />Step 2 - Running a Java class with Alfresco<br /><br />Now we will configure to run <a href="http://wiki.alfresco.com/wiki/Java_Foundation_API">alfresco java foundation API</a> services.<br /><br />ENDedlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com2tag:blogger.com,1999:blog-7203181491808683266.post-66500326355309357732009-04-07T09:32:00.001-07:002009-04-07T14:02:28.556-07:00Working with jBPM workflows in Alfresco - Part 2: Embedded Alf - SDKThis part focuses on extending the <a href="http://edlovesjava.blogspot.com/2009/04/working-with-jbpm-workflows-in-alfresco.html">previous blog post: Working with Workflows in Alfresco - Part 1: jBPM</a> to run as an Embedded Alf SDK project, and to configure and use Alfresco scripts as actions in the workflows.<br /><br /><span style="font-size:130%;">Step 1 - Install SDK</span><br />Make sure you install the SDK according to the documentation on the Alfresco Wiki<br />Import the SDK projects into the same workspace you are using for this tutorial<br /><br /><span style="font-size:130%;">Step 2 - Configure Project to Run with Embedded Alfresco</span><br /><ol><li>Go to project properties > Java buid path > Projects tab and select 'add' to add SDK AlfrescoEmbedded</li><li>From the SDK FirstFoundationClient, copy alfresco.extensions package from the 'source' source folder and paste into the 'src/main/config' source directory of your project (i.e. 'orchestration-example'). This will include the files 'custom-alfresco-shared.xml', 'custom-repository-context.xml' and 'custom-repository.properties'</li><li>From the SDK FirstFoundationClient, copy the org.alfresco.sample package and paste into the src/main/java source folder of your project. This will give us something to test running as Alfresco Embedded<br /></li><li>Update the 'custom-repository.properties' to point dir.root to your Alfresco installation</li><li>Remove the JBPM library from project properties, as it will conflict with the Alfresco version of these dependencies</li><li>I recommend that you modify the log4j.properties to set the root logger to INFO, CONSOLE not DEBUG, CONSOLE or you will be watching the log messages for half the day<br /></li></ol><span style="font-size:130%;">Step 3 - Test it out</span><br />Run the FirstFoundationClient as Java Application. There should be no 'red ink' and the line near the end of the log messages should read something like 'Alfresco started (Labs): Current version 3.0.0 (Stable 1526) schema 1002 - Installed version 3.1.0 (142) schema 1008'<br />If you see this line, likely you were able to run using Alfresco Embedded SDK<br /><span style="font-size:130%;"><br />Step 4 - Create an Embedded Alf test</span><br />To test within the Alfresco context using Embedded approach, your workflow must be deployed, and you must run your test code, like the FirstFoundationClient example within a transaction service callback.<br />We will create a base test class, extending TestCase of JUnit 3 to simplify writing tests like this.<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>package com.sample;<br /><br />import junit.framework.TestCase;<br /><br />import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;<br />import org.alfresco.service.ServiceRegistry;<br />import org.alfresco.service.transaction.TransactionService;<br />import org.alfresco.util.ApplicationContextHelper;<br />import org.apache.log4j.Logger;<br />import org.springframework.context.ApplicationContext;<br /><br />public abstract class EmbeddedAlfTestBase extends TestCase {<br /><br />Logger logger = Logger.getLogger(EmbeddedAlfTestBase.class);<br />protected ServiceRegistry serviceRegistry;<br />protected TransactionService transactionService;<br />protected ApplicationContext ctx;<br /><br />public void setUp() throws Exception {<br />ctx = ApplicationContextHelper.getApplicationContext();<br />serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);<br />transactionService = serviceRegistry.getTransactionService();<br />}<br /><br />public void tearDown() throws Exception {<br />}<br /><br />public void runTestInEmbeddedAlf(RetryingTransactionCallback<Object> testWork) throws Exception {<br />transactionService.getRetryingTransactionHelper().doInTransaction(testWork);<br />}<br />}<br /></code></pre>This code gets the application context and gets some services from Spring, including the serviceRegistry and the transactionService. The helper method, runtestInEmbeddedAlf takes a class of type RetryingtransactionCallback and runs it it using the transactionService's RetryingTransactionHelper doInTransaction(callback) method.<br /><br />We will subclass this base test class and write our process test.<br /><ol><li>Create a class PublishContentBasicProcessAlfTest, copying from PublishContentBasicProcessTest created in the previous blog entry.</li><li>Change it to extend from EmbeddedAlfTestBase class</li><li>Rename testSimpleProcess to a signature 'public void doTestSimpleProcess(ServiceRegistry serviceRegistry) throws Exception'</li><li>Create a new testSimpleProcess like the following:</li></ol><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> public void testSimpleProcess() throws Exception {<br />RetryingTransactionCallback<Object> publishContentWFExistsCB = new RetryingTransactionCallback<Object>() {<br /> public Object execute() throws Exception {<br /> doTestSimpleProcess(serviceRegistry);<br /> return null;<br /> }<br />};<br />this.runTestInEmbeddedAlf(publishContentWFExistsCB);<br />}<br /></code></pre>Now run 'PublishContentBasicProcessAlfTest' as a JUnit test.<br />This should run as an alfresco project properly. But we are not leveraging Alfresco's services yet.<br /><br />Step 5 - Test in Alf Using Workflow Service (foundation services)<br /><br />What we are going to do:<br /><ol><li>Authenticate and deploy in setUp for test</li><li>Start workflow using workflow service</li><li>Signal workflow using workflow service</li></ol>Create a class PublishContentBasicProcessAlfTest extends EmbeddedAlfTestBase<br />To start, we will create a setUp() method to set us up for testing, like authenticating and deploying the service<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> private WorkflowService workflowService;<br /><br />public void setUp() throws Exception {<br />super.setUp();<br />workflowService = serviceRegistry.getWorkflowService();<br /><br />authenticate("admin","admin");<br /><br />deployDefinition(PROCESS_DEF_FILE);<br />}<br /><br /></code></pre>We get the workflow service from the service registry (obtained in the setUp of the EmbeddedAlfTestBase class). Then we authenticate, and then deploy our process definition 'publichContentBasic/processdefinition.xml'.<br />Lets look at the methods we need to create:<br /><br /><span style="font-weight: bold;">authenticate method</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> private void authenticate(String user, String password) {<br /> AuthenticationService authenticationService = serviceRegistry.getAuthenticationService();<br /> authenticationService.authenticate(user, password.toCharArray());<br /><br />}<br /><br /></code></pre>We get the AuthenticationService, and call authenticate. We need to do this first or else we wont be able to deploy our workflow.<br /><br /><span style="font-weight: bold;">deployDefinition method</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> private WorkflowDeployment deployDefinition(String processDefName) {<br /> //Deploy definition<br /> String engineId = ENGINE_ID;<br /> InputStream workflowDefinition = getClass().getResourceAsStream("/"+processDefName);<br /> String mimeType = XML_MIMETYPE;<br /> return workflowService.deployDefinition(engineId, workflowDefinition, mimeType);<br />}<br /><br /></code></pre>We use the deployDefinition method of the workflow service. THis requires us to pass an engineId. this Id for jbpm is 'jbpm'. The mime type is 'text/xml'. The stream is obtained as a resource from this class loader by reading the 'publishContentBasic/processdefinition.xml' file.<br /><br />Now we start the test like we did before, using the transactionservice and its callback:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> public void testSimpleProcess() throws Exception {<br /> RetryingTransactionCallback<Object> publishContentWFExistsCB = new RetryingTransactionCallback<Object>() {<br /> public Object execute() throws Exception {<br /> doTestSimpleProcessInAlf();<br /> return null;<br /> }<br /> };<br /> this.runTestInEmbeddedAlf(publishContentWFExistsCB);<br /><br />}<br /><br />public void doTestSimpleProcessInAlf() throws Exception {<br /><br />}<br /></code></pre><br /><br />Ok. Now that we've got the test method ready. Lets put something in it. The first thing we have to do is start our workflow:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> public void doTestSimpleProcessInAlf() throws Exception {<br /><br /><br /> NodeRef content = null;<br /> String wfAssigneeName = "admin";<br /> String workflowName = ENGINE_ID+"$"+PROCESS_DEF_NAME;<br /><br /> WorkflowPath wfPath = startWorkflow(content, wfAssigneeName, workflowName);<br /> assertNotNull(wfPath);<br /><br /> assertEquals(<br /> "Instance is in start state",<br /> wfPath.node.name,<br /> "start");<br /><br /></code></pre>This calls the startWorkflow helper method with the content to go in the workflow package (currently null for testing purposes). We also need a name to assign the workflow to. We use 'admin'. And, finally we need the workflow name to start. Alfresco uses a naming convention prepending the engine id and a '$' to the beginning of BPM engine objects. The value for jbpm is 'jbpm'.<br /><br /><span style="font-weight: bold;">startWorkflow method</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> private WorkflowPath startWorkflow(NodeRef content, String wfAssigneeName, String workflowName) throws Exception {<br /> //Start workflow<br /> NodeRef wfPackage = workflowService.createPackage(content );<br /> <br /> PersonService personService = serviceRegistry.getPersonService();<br /> NodeRef assigneeNodeRef = personService.getPerson(wfAssigneeName );<br /><br /> Map<QName, Serializable> workflowProps = new HashMap<QName, Serializable>(16);<br /> workflowProps.put(WorkflowModel.ASSOC_PACKAGE, wfPackage);<br /> workflowProps.put(WorkflowModel.ASSOC_ASSIGNEE, assigneeNodeRef);<br /><br /> // get the moderated workflow<br /><br /> WorkflowDefinition wfDefinition = workflowService.getDefinitionByName(workflowName );<br /> if (wfDefinition == null) {<br /> // handle workflow definition does not exist<br /> throw new Exception("noworkflow: " + workflowName);<br /> }<br /><br /> // start the workflow<br /> WorkflowPath wfPath = workflowService.startWorkflow(wfDefinition.getId(), workflowProps);<br /> return wfPath;<br /> }<br /></code></pre>In this code, we create the package for the workflow that should contain the content we are workflowing. This is a content manager after all :). Next we get the user we will assign this workflow too. Since this process has no 'tasks', nothing will be noticable to the user. For this kind of process orchestration, the 'admin' user will do nicely. Perhaps in a real system, we want to create a special user just for back office processing like this?<br /><br />Next we get the definition from the service using the given name 'jbpm$publishContentBasic'. Finally, we use the service to start the workflow, passing the definition and the workflow package. The startWorkflow service returns a WorkflowPath object. This corresponds to the workflow token of the execution of the workflow.<br /><br />We can test this now to make sure we can deploy and start our workflow.<br /><br />Next, we will signal the workflow to transition to the next state. This code snippet should be added to the end of the doTestSimpleProcessInAlf method<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> // Move the process instance from its start state to the first state.<br /> wfPath = signalTransition(wfPath, null);<br /> assertEquals(<br /> "Instance is in reqested state",<br /> wfPath.node.name,<br /> "requested");<br /><br /></code></pre>Here we call the 'signalTransition' helper method passing the current path (token) and a transition name to take. Our helper method takes the first transition from the current state if no transition name is supplied.<br /><span style="font-weight: bold;">signalTransition method</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> private WorkflowPath signalTransition(WorkflowPath wfPath, String transitionName) throws Exception {<br /> String wfPathId = wfPath.id;<br /> WorkflowTransition[] wfTransitions = wfPath.node.transitions;<br /> String wfTransitionId = null;<br /> if (transitionName == null || transitionName.trim().length()==0) {<br /> WorkflowTransition wfTransition = wfTransitions[0];<br /> wfTransitionId = wfTransition.id;<br /> } else {<br /> int i = 0;<br /> for (i = 0; i<wfTransitions.length; i++) {<br /> if (wfTransitions[i].title.equals(transitionName)) break;<br /> }<br /> if (i > wfTransitions.length) throw new Exception("Failed to find transition with nanme '"+transitionName+"'");<br /> WorkflowTransition wfTransition = wfTransitions[i];<br /> wfTransitionId = wfTransition.id;<br /> }<br /> <br /> wfPath = workflowService.signal(wfPathId, wfTransitionId);<br /> return wfPath;<br /> }<br /><br /></code></pre>Ok. This pattern can be repeated to transition to the next states to complete our test<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code> //move to processing state<br /> wfPath = signalTransition(wfPath, null);<br /> assertEquals(<br /> "Instance is in processing state",<br /> "processing",<br /> wfPath.node.name);<br /><br /> //move to succeeded state<br /> wfPath = signalTransition(wfPath, "to_succeeded");<br /> assertEquals(<br /> "Instance is in processing state", <br /> "succeeded", <br /> wfPath.node.name);<br /><br /> // Move the process instance to the end state. The configured action <br /> // should execute again. The message variable contains a new value.<br /> wfPath = signalTransition(wfPath, null);<br /> assertEquals(<br /> "Instance is in end state", <br /> "end", <br /> wfPath.node.name);<br /> }<br /><br /></code></pre><br /><br />This completes the code to use the workflow service foundation client approach to deploy, start and signal our workflow.<br /><br />Here is the complete code of the PublishContentBasicAlfTest<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>package com.sample;<br /><br />import java.io.InputStream;<br />import java.io.Serializable;<br />import java.util.HashMap;<br />import java.util.Map;<br /><br />import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;<br />import org.alfresco.repo.workflow.WorkflowModel;<br />import org.alfresco.service.cmr.repository.NodeRef;<br />import org.alfresco.service.cmr.security.AuthenticationService;<br />import org.alfresco.service.cmr.security.PersonService;<br />import org.alfresco.service.cmr.workflow.WorkflowDefinition;<br />import org.alfresco.service.cmr.workflow.WorkflowDeployment;<br />import org.alfresco.service.cmr.workflow.WorkflowPath;<br />import org.alfresco.service.cmr.workflow.WorkflowService;<br />import org.alfresco.service.cmr.workflow.WorkflowTransition;<br />import org.alfresco.service.namespace.QName;<br /><br />public class PublishContentBasicProcessAlfTest extends EmbeddedAlfTestBase {<br /><br /> private static final String XML_MIMETYPE = "text/xml";<br /> private static final String PROCESS_DEF_FILE = "publishContentBasic/processdefinition.xml";<br /> private static final String PROCESS_DEF_NAME = "publishContentBasic";<br /> private static final String ENGINE_ID = "jbpm";<br /><br /> private WorkflowService workflowService;<br /><br /> public void setUp() throws Exception {<br /> super.setUp();<br /> workflowService = serviceRegistry.getWorkflowService(); <br /><br /> authenticate("admin","admin");<br /> <br /> deployDefinition(PROCESS_DEF_FILE);<br /> }<br /> <br /> public void tearDown() throws Exception {<br /> super.tearDown();<br /> }<br /> <br /> public void testSimpleProcess() throws Exception {<br /> RetryingTransactionCallback<Object> publishContentWFExistsCB = new RetryingTransactionCallback<Object>() {<br /> public Object execute() throws Exception {<br /> doTestSimpleProcessInAlf();<br /> return null;<br /> }<br /> };<br /> this.runTestInEmbeddedAlf(publishContentWFExistsCB);<br /> <br /> }<br /><br /> public void doTestSimpleProcessInAlf() throws Exception {<br /> <br /> <br /> NodeRef content = null;<br /> String wfAssigneeName = "admin";<br /> String workflowName = ENGINE_ID+"$"+PROCESS_DEF_NAME;<br /> <br /> WorkflowPath wfPath = startWorkflow(content, wfAssigneeName, workflowName);<br /> assertNotNull(wfPath);<br /><br /> assertEquals(<br /> "Instance is in start state", <br /> wfPath.node.name, <br /> "start");<br /><br /> // Move the process instance from its start state to the first state.<br /> wfPath = signalTransition(wfPath, null);<br /> assertEquals(<br /> "Instance is in reqested state", <br /> wfPath.node.name, <br /> "requested");<br /><br /> //move to processing state<br /> wfPath = signalTransition(wfPath, null);<br /> assertEquals(<br /> "Instance is in processing state",<br /> "processing",<br /> wfPath.node.name);<br /><br /> //move to succeeded state<br /> wfPath = signalTransition(wfPath, "to_succeeded");<br /> assertEquals(<br /> "Instance is in processing state", <br /> "succeeded", <br /> wfPath.node.name);<br /><br /> // Move the process instance to the end state. The configured action <br /> // should execute again. The message variable contains a new value.<br /> wfPath = signalTransition(wfPath, null);<br /> assertEquals(<br /> "Instance is in end state", <br /> "end", <br /> wfPath.node.name);<br /> }<br /><br /> private WorkflowPath startWorkflow(NodeRef content, String wfAssigneeName, String workflowName) throws Exception {<br /> //Start workflow<br /> NodeRef wfPackage = workflowService.createPackage(content );<br /> <br /> PersonService personService = serviceRegistry.getPersonService();<br /> NodeRef assigneeNodeRef = personService.getPerson(wfAssigneeName );<br /> <br /> Map<QName, Serializable> workflowProps = new HashMap<QName, Serializable>(16);<br /> workflowProps.put(WorkflowModel.ASSOC_PACKAGE, wfPackage);<br /> workflowProps.put(WorkflowModel.ASSOC_ASSIGNEE, assigneeNodeRef);<br /> <br /> // get the moderated workflow<br /> <br /> WorkflowDefinition wfDefinition = workflowService.getDefinitionByName(workflowName );<br /> if (wfDefinition == null) {<br /> // handle workflow definition does not exist<br /> throw new Exception("noworkflow: " + workflowName);<br /> }<br /> <br /> // start the workflow<br /> WorkflowPath wfPath = workflowService.startWorkflow(wfDefinition.getId(), workflowProps);<br /> return wfPath;<br /> }<br /><br /> private WorkflowDeployment deployDefinition(String processDefName) {<br /> //Deploy definition<br /> String engineId = ENGINE_ID;<br /> InputStream workflowDefinition = getClass().getResourceAsStream("/"+processDefName);<br /> String mimeType = XML_MIMETYPE;<br /> return workflowService.deployDefinition(engineId, workflowDefinition, mimeType);<br /> }<br /><br /> private WorkflowPath signalTransition(WorkflowPath wfPath, String transitionName) throws Exception {<br /> String wfPathId = wfPath.id;<br /> WorkflowTransition[] wfTransitions = wfPath.node.transitions;<br /> String wfTransitionId = null;<br /> if (transitionName == null || transitionName.trim().length()==0) {<br /> WorkflowTransition wfTransition = wfTransitions[0];<br /> wfTransitionId = wfTransition.id;<br /> } else {<br /> int i = 0;<br /> for (i = 0; i<wfTransitions.length; i++) {<br /> if (wfTransitions[i].title.equals(transitionName)) break;<br /> }<br /> if (i > wfTransitions.length) throw new Exception("Failed to find transition with nanme '"+transitionName+"'");<br /> WorkflowTransition wfTransition = wfTransitions[i];<br /> wfTransitionId = wfTransition.id;<br /> }<br /> <br /> wfPath = workflowService.signal(wfPathId, wfTransitionId);<br /> return wfPath;<br /> }<br /><br /> private boolean authenticate(String user, String password) {<br /> AuthenticationService authenticationService = serviceRegistry.getAuthenticationService();<br /> authenticationService.authenticate(user, password.toCharArray());<br /> return authenticationService.authenticationExists(user);<br /> }<br />}<br /><br /></code></pre><br /><br />In the next blog post, we will use alfresco scripts in the workflow definition we have just created. And finally, for orchestration, we will interact with JMS.edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com0tag:blogger.com,1999:blog-7203181491808683266.post-28808288859877257912009-04-07T07:24:00.000-07:002009-05-13T03:52:44.676-07:00Working with jBPM workflows in Alfresco - Part 1: jBPMThe following set of blog articles will track my usage of jBPM in Alfresco to help orchestrate external content processing for publishing, and integrate with Review and Approval Advanced workflows.<br /><ul><li>My starting point is <a href="http://ecmarchitect.com/">Jeff Potts articles and blogs</a> on <a href="http://ecmarchitect.com/alfresco-developer-series">advanced workflows.</a> Also read <a href="http://ecmarchitect.com/archives/2008/10/29/862">his book</a>!<br /></li><li>Also, read the <a href="http://wiki.alfresco.com/wiki/Workflow_Reqs_and_Design">Alfresco wiki documentation on workflows</a><br /></li><li><a href="http://www.jboss.org/jbossjbpm/">jBoss jBPM documentation</a> </li></ul><br /><span style="font-size:130%;">Step 1 - Design</span><br />In order to collaborate with external publishing processes, we have chosen to use JMS topics. This approach offers robustness and flexibility in communication. The workflow described here will be an 'orchestration' workflow. Its responsibility is to call out external processes and monitor progress. This workflow will be associated to content, however, the content will be in the form of a 'work order' that is created when a user requests to publish a set of content. The user may view the workflow on this work order to see the progress of the external processes orchestrated by the workflow. Later on, we will integrate with the publish review and approve workflow where user tasks will be created to coordinate user activity.<br /><br />Alfresco will be the container for the orchestration workflows. Actions in the workflow will be responsible for creating and sending messages to JMS topics to choreograph external publishing processes. A simulator will be used to act as the publisher processes for testing purposes. ActiveMQ is used as the JMS provider in these examples. There will also be a message handler created with the responsibi8lity of subscribing to topics that the publisher processes use to provide feedback and communicate status to the workflow. The following diagram represents this intended configuration.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhea1AJlatkSmpjV6UlGF18nUiN9gzBE6-yv-gUj1DDnjP_MlkJPzuLzdP9cFe3gu7lQR9CZipx_aL0FEn9EDUiiGpfWNRbz_fDfHfSJxHgn_6QLLDqDAm722Oi-3RvNYS2iEXUeze7NOjt/s1600-h/Alf-BPM-Publishing.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 296px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhea1AJlatkSmpjV6UlGF18nUiN9gzBE6-yv-gUj1DDnjP_MlkJPzuLzdP9cFe3gu7lQR9CZipx_aL0FEn9EDUiiGpfWNRbz_fDfHfSJxHgn_6QLLDqDAm722Oi-3RvNYS2iEXUeze7NOjt/s320/Alf-BPM-Publishing.png" alt="" id="BLOGGER_PHOTO_ID_5321965300636435602" border="0" /></a><br />Component descriptions:<br /><ul><li><span style="font-weight: bold;">Alfresco</span> - contains jbpm implementation and content being workflowed</li><li><span style="font-weight: bold;">jBPM</span> - library for jBPM accessed via the workflowService</li><li><span style="font-weight: bold;">process definition</span> - jBPM process defining the process steps to orchestrate the publishing process</li><li><span style="font-weight: bold;">content event producer action</span> - BPM action invoked from process to publish a content event message to the content topic</li><li><span style="font-weight: bold;">content event message</span> - jms message indicating which content to publish and where to publish too</li><li><span style="font-weight: bold;">content event topic</span> - jms topic storing the content event messages to be processed by the publisher</li><li><span style="font-weight: bold;">content event consumer</span> - consumes content event messages and calls publishing services to invoke the publishing processes</li><li><span style="font-weight: bold;">publish process</span> - implements the publishing functionality and calls publisher event producer to<br /></li><li><span style="font-weight: bold;">publish event producer</span> - creates and publishes publish event messages to the publish event topic indicating status of publishing</li><li><span style="font-weight: bold;">publish event consumer handler</span> - consumes publish event messages and signals corresponding transitions on the publish process definition via the jbpm library</li></ul><br /><span style="font-size:130%;">Step 2 - Environment Setup</span><br /><ol><li>Install Alfresco SDK: follow instructions on the <a href="http://wiki.alfresco.com/wiki/Alfresco_SDK">alfresco wiki</a><br /></li><li>Install ActiveMQ: follow the instructions on the <a href="http://activemq.apache.org/getting-started.html">ActiveMQ site</a><br /></li><li>Install jBPM (3.2.x): follow the instructions on the <a href="http://docs.jboss.com/jbpm/v3.2/userguide/html/gettingstarted.html">JBoss jBPM</a> site: I installed <a href="http://sourceforge.net/project/showfiles.php?group_id=70542&package_id=145174">jbpm-3.2.2</a> for best compatibility with Alfresco<br /></li><li>Install <a href="http://docs.jboss.org/tools/2.0.0.GA/jbpm/en/html/index.html">jBPM Process Designer</a></li></ol>Note: compatibility problems. As of Alfresco 3.1, the jBPM 3.2.2 is supported. The only issue I had with this version is that there are no hibernate tasks for the Mail Node.<br /><br /><span style="font-size:130%;">Step 3 - Create the Eclipse Project</span><br />For this project, I created a project called 'orchestration -example' using the jboss - jbpm process project' wizard.<br /><br /><span style="font-size:130%;">Step 4 - Create the process</span><br />The following image shows the workflow configuration created to orchestrate and monitor the publishing process.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXQMQDvZIrOCUlAC5tPtHQo17jHnBcFNdrUfOJjFrPr1GEs4B0sInF_0lcftJNMq5mw5UNhaJ_cTNK5Da9tYeH7zrfq4C_bPnBI8Qbw6xpfaxM0cMj2iosRwRWlI9OVzqf3C0B2w0evuHR/s1600-h/processimage.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 306px; height: 228px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXQMQDvZIrOCUlAC5tPtHQo17jHnBcFNdrUfOJjFrPr1GEs4B0sInF_0lcftJNMq5mw5UNhaJ_cTNK5Da9tYeH7zrfq4C_bPnBI8Qbw6xpfaxM0cMj2iosRwRWlI9OVzqf3C0B2w0evuHR/s320/processimage.jpg" alt="" id="BLOGGER_PHOTO_ID_5321968248145178450" border="0" /></a>Workflow states:<br /><ul><li><span style="font-weight: bold;">Start</span> - initial state when workflow is created<br /></li><li><span style="font-weight: bold;">Requested</span> - state indicating that a request was made to the publisher (the content event msg was sent to the content topic)<br /></li><li><span style="font-weight: bold;">Processing</span> - state indicating that the publisher has received the request and is processing<br /></li><li><span style="font-weight: bold;">Succeeded</span> - the publish process completed normally<br /></li><li><span style="font-weight: bold;">Failed</span> - the publish process failed<br /></li><li><span style="font-weight: bold;">End</span> - workflow has ended</li></ul>Using this workflow, the state can be queried to indicate the status of the publishing process.<br />Here is the first version of this process definition:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version="1.0" encoding="UTF-8"?><br /><br /><process-definition xmlns="urn:jbpm.org:jpdl-3.2" name="publishContentBasic"><br /><start-state name="start"><br /><transition name="to_requested" to="requested"><br /> <action name="action" class="com.sample.action.MessageActionHandler"><br /> <message>Going to the first state - requested!</message><br /> </action><br /></transition><br /></start-state><br /><br /><state name="requested"><br /><transition to="processing" name="to_processing"></transition><br /></state><br /><br /><state name="processing"><br /><transition to="succeeded" name="to_succeeded"></transition><br /><transition to="failed" name="to_failed"></transition><br /></state><br /><br /><state name="succeeded"><br /><transition to="end" name="to_end"><br /><action name="action" class="com.sample.action.MessageActionHandler"><br /> <message>About to finish - succeeded!</message><br /> </action><br /></transition><br /></state><br /><br /><state name="failed"><br /><transition to="end" name="to_end"><br /> <action name="action" class="com.sample.action.MessageActionHandler"><br /> <message>About to finish - failed!</message><br /> </action><br /></transition><br /></state><br /><br /><end-state name="end"></end-state><br /></process-definition><br /></code></pre>This first configuration of the process is basic and simple. It will be embellished later with processing and business logic. This example uses the <span style="font-family:courier new;">com.sample.action.MessageActionHandler</span> class that is created by default using the 'create jbpm project' option in Eclipse.<br /><br /><span style="font-size:130%;">Step 5 - The first test</span><br />In this first pass, we are using jBPM out of the box, without Alfresco. This mode allows us to configure and test our process in as much isolation as possible. Later we will use the Alfresco SDK to run as an Embedded Alfresco when we need Alfresco workflow services.<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>package com.sample;<br /><br />import junit.framework.TestCase;<br /><br />import org.jbpm.graph.def.ProcessDefinition;<br />import org.jbpm.graph.exe.ProcessInstance;<br /><br />public class PublishContentBasicProcessTest extends TestCase {<br /><br />public void testSimpleProcess() throws Exception {<br /><br /> // Extract a process definition from the processdefinition.xml file.<br /> ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource("publishContentBasic/processdefinition.xml");<br /> assertNotNull("Definition should not be null", processDefinition);<br /><br /> // Create an instance of the process definition.<br /> ProcessInstance instance = new ProcessInstance(processDefinition);<br /> assertEquals(<br /> "Instance is in start state",<br /> instance.getRootToken().getNode().getName(),<br /> "start");<br /> assertNull(<br /> "Message variable should not exist yet",<br /> instance.getContextInstance().getVariable("message"));<br /><br /> // Move the process instance from its start state to the first state.<br /> // The configured action should execute and the appropriate message<br /> // should appear in the message process variable.<br /> instance.signal();<br /> assertEquals(<br /> "Instance is in reqested state",<br /> instance.getRootToken().getNode().getName(),<br /> "requested");<br /> assertEquals(<br /> "Message variable contains message",<br /> "Going to the first state - requested!",<br /> instance.getContextInstance().getVariable("message"));<br /><br /> //move to processing state<br /> instance.signal();<br /> assertEquals(<br /> "Instance is in processing state",<br /> "processing",<br /> instance.getRootToken().getNode().getName());<br /><br /> //move to succeeded state<br /> instance.signal("to_succeeded");<br /> assertEquals(<br /> "Instance is in processing state",<br /> "succeeded",<br /> instance.getRootToken().getNode().getName());<br /><br /> // Move the process instance to the end state. The configured action<br /> // should execute again. The message variable contains a new value.<br /> instance.signal();<br /> assertEquals(<br /> "Instance is in end state",<br /> "end",<br /> instance.getRootToken().getNode().getName());<br /> assertTrue("Instance has ended", instance.hasEnded());<br /> assertEquals(<br /> "Message variable contains message",<br /> "About to finish!",<br /> instance.getContextInstance().getVariable("message"));<br />}<br />}<br /></code></pre>Next Part, on to running workflows in Alfresco Embedded mode and running alfresco scripts in the workflow.<br /><br /><br />Thisedlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com3tag:blogger.com,1999:blog-7203181491808683266.post-60156875511748067672009-01-08T10:02:00.000-08:002009-04-12T06:51:17.680-07:00Extending Share 3 - Adding new pages to shareIn a previous blog article '<a href="http://edlovesjava.blogspot.com/2008/12/extending-share-2-adding-new-content.html">Extending Share 2 - Adding a New Content button to Document Library</a>', we walked through the process of modifying Document Library to add a new toolbar component that included a button titled 'New Content'. In this article, We will walk through the process of creating a new page for Share that can be included for a site, adding to the existing pages including wiki, blog, discussion, document library, calendar etc. that can be selected from the site navigation bar. As an example, the new page we add will present a new view of document library content, leveraging some existing components form the Document Library page such as the tree navigation component.<br /><br />This blog article will add these new pages into the share extension project structure discussed in the previous blog article '<a href="http://edlovesjava.blogspot.com/2008/12/extending-share-1-creating-share.html">Extending Share 1 - Creating a share extension project</a>' that provides a clean way to isolate our changes from the existing share code. The new page will be able to be added to existing or new sites by using the 'Customize Site' feature of the site dashboard and selecting the new page.<br /><br /><span style="font-weight: bold;">Step 1 - Setting up the project</span><br /><br />We will start with creating a project structure similar to that referenced in the previous blog article '<a href="http://edlovesjava.blogspot.com/2008/12/extending-share-1-creating-share.html">Extending Share 1 - Creating a share extension project</a>' and the latest <a href="http://wiki.alfresco.com/wiki/Alfresco_SDK">Alfresco SDK</a>.<br /><br />1. Create a new project (i.e. 'someco-share-extension') with the following source directories :<br /><br /><ul><li>source/java - contains java code to be packaged in someco-share-ext.jar</li><li>config/alfresco/web-extension - contains 'web-framework-config-custom.xml'</li><li>config/alfresco/web-extension/site-data - contains custom model objects like pages, template-instances, and components<br /></li><li>config/alfresco/web-extension/site-webscripts containing folder /com/someco to contain webscripts in a unique namespace<br /></li><li>config/alfresco/templates - contains custom templates containing folder /com/someco to contain templates in unique namespace<br /></li><li>source/web - contains web assets and delivered to the war, used for javascript and css as included by html templates<br /></li><li>lib - contains required library elements (includes junit.jar for testing<br /></li><li>test - contains java test code and configuration<br /></li></ul>I created each of these folders as a 'source directory' in Eclipse. Source directories in Eclipse automatically copy files to the classpath. This project depends on SDK Embedded to allow embedded testing (unit testing of custom data web scripts) (see the '<a href="http://edlovesjava.blogspot.com/2008/12/writing-webscripts-1-unit-testing-web.html">Unit Testing Web Scripts</a>' blog article for using the SDK to run embedded unit tests of data web scripts.<br /><br />2. Create the 'web-framework-config-custom.xml' file in the to contain a reference to our new page so that it can be included by customizing a site.<br /><br />3. Copy the ant build.xml file from the source in the previous '<a href="http://edlovesjava.blogspot.com/2008/12/extending-share-1-creating-share.html">Extending Share</a>' blog article into the root of the project. This build script has some handy tasks. Some of which were derived from the great works by <a href="http://ecmarchitect.com/">Jeff Potts</a> and his great new book <a href="http://www.packtpub.com/alfresco-developer-guide/book">Alfresco Developers Guide from Packt Publishing</a>. Make sure to change the project name in the build.xml file as appropriate (i.e. 'someco-share-extension').<br /><br /><ul><li>The 'deploy' tasks will call the 'package' task and copy the created jar to the APP_TOMCAT_HOME/WEB-INF/lib directory (should be configured to point to the expanded share war file directory in tomcat), and copy the contents of the 'source/web' and 'config/alfresco/*' folders to the APP_TOMCAT_HOME/WEB-INF/classes/alfresco directory.</li><li>The 'package' task will call the 'compile' taks and create a jar (someco-share-ext.jar) including our java code in the source/java folder, web scripts in the 'config/someco/site-webscripts' and templates in the 'config/someco/templates' folders. </li></ul>Make sure APP_TOMCAT_HOME environment variable is configured to point to the directory where share war is deployed. In Eclipse, you can add this to the ant configuration (windows > preferences > ant > runtime, 'properties' tab as env.APP_TOMCAT_HOME).<br /><br />4. Create (or copy) the build.properties file to the project root directory containing the following:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>#directory names<br /><br />dir.name.assemble=assemble<br />dir.name.bin=bin<br />dir.name.build=build<br />dir.name.classes=classes<br />dir.name.config=config<br />dir.name.source=source<br />dir.name.devenv=devenv<br />dir.name.dist=dist<br />dir.name.distro=distro<br />dir.name.docs=docs<br />dir.name.generated=generated<br />dir.name.java=java<br />dir.name.lib=lib<br />dir.name.test.results=<br />dir.name.test.results=test-results<br />dir.name.test.resources=test-resources<br />dir.name.web=web<br /><br />file.name.jar=someco-share-ext.jar<br /><br />dir.junit.lib=lib<br /><br /></code></pre>Now we are ready to start creating our custom configuration. You can adjust these to make the ant build work with your own project structure.<br /><br /><span style="font-weight: bold;">Step 2 - Creating the page and template instance</span><br /><br />To start out, we will create a new page called 'Content Grid' that is intended to display a list of content in a way similar to Document Library but as a grid showing meta-data and a content extract in columns. This extension follows the model object structure as outlined in the <a href="http://wiki.alfresco.com/wiki/Surf_Platform_-_Developers_Guide">Alfresco Surf Platform - Developers Guide</a> page, and analyzed in the '<a href="http://edlovesjava.blogspot.com/2008/10/examining-slingshot-configuration.html">Learning Surf 2 - Examining Slingshot configuration</a>' blog article.<br /><br />The custom model objects we create will be added to the 'config/alfresco/web-extension' source directory. The contents of this directory will be copied into the Share war directory using the <span style="font-weight: bold;">ant deploy</span> task in the Ant build file.<br /><br />1. Create 'contentgrid.xml' file in the 'pages' folder of the 'source/alfresco/web-extension/site-data' source directory, with the following content:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version='1.0' encoding='UTF-8'?><br /><page><br /><title>Content Grid</title><br /><description>Document library with Tree view</description><br /><template-instance>contentgrid</template-instance><br /><authentication>user</authentication><br /></page><br /></code></pre>This defines a page with the title 'Content Grid'. It refers to a template instance called 'contentgrid' defined next.<br /><br />2. Create the 'contentgrid.xml' template instance in the 'template-instances' folder of the 'config/alfresco/web-extension/site-data' source folder with the following contents:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version='1.0' encoding='UTF-8'?><br /><template-instance><br /><template-type>org/alfresco/simple-contentgrid</template-type><br /><properties><br /><hasBreadcrumb>true</hasBreadcrumb><br /><hasTreeview>true</hasTreeview><br /><hasPackager>true</hasPackager><br /></properties><br /></template-instance><br /></code></pre>This file tells Surf to use the template found from path org/alfresco/contentgrid path of the config/alfresco/templates folder.<br /><br /><span style="font-weight: bold;">Step 3 - Creating the template type</span><br /><br />The template we will create first will be a simple test template with no functionality, to test our configuration and deployment. This template is a gutted out version of the documentlibrary.ftl found in the config/alfresco/templates source folder in the org/alfresco package. It includes a standard set of included global regions with a simple body announcing that the template has rendered.<br /><br />1. Create 'simple-contentgrid.ftl' freemarker template in the org/alfresco path of the 'config/alfresco/templates' source folder with the following contents:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><#import "/org/alfresco/import/alfresco-template.ftl" as template /><br /><@template.header><br /><link rel="stylesheet" type="text/css" href="${url.context}/templates/documentlibrary/documentlibrary.css" /><br /><script type="text/javascript">//<![CDATA[<br />(function()<br />{<br />// If no location.hash exists, convert a location.search to a location.hash and replace the page<br />var loc = window.location;<br />if (loc.hash === "" && loc.search !== "")<br />{<br /> var url = loc.protocol + "//" + loc.host + loc.pathname + "#" + loc.search.substring(1);<br /> window.location.replace(url);<br />}<br />})();<br />//]]></script><br /><script type="text/javascript" src="${url.context}/templates/documentlibrary/documentlibrary.js"></script><br /><script type="text/javascript" src="${url.context}/modules/documentlibrary/doclib-actions.js"></script><br /></@><br /><br /><@template.body><br /><div id="hd"><br /><@region id="header" scope="global" protected=true /><br /><@region id="title" scope="template" protected=true /><br /><@region id="navigation" scope="template" protected=true /><br /></div><br /><div id="bd"><br />This is the Content Grid template<br /></div><br /><p>&nbsp;</p><br /><p>&nbsp;</p><br /></@><br /><br /><@template.footer><br /><div id="ft"><br /><@region id="footer" scope="global" protected=true /><br /></div><br /></@><br /></code></pre><br />2. Modify 'web-framework-config-custom.xml' file in the 'config/alfresco/web-extensions' folder to add a reference to the new page we just created to allow this page to be added when customizing the site. It should read:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><alfresco-config><br /><br /><br /><config evaluator="string-compare" condition="SitePages" replace="true"><br /><pages><br /> <page id="calendar">calendar</page><br /> <page id="wiki-page">wiki-page?title=Main_Page</page><br /> <page id="documentlibrary">documentlibrary</page><br /> <page id="contentgrid">contentgrid</page><br /> <page id="discussions-topiclist">discussions-topiclist</page><br /> <page id="blog-postlist">blog-postlist</page><br /></pages><br /></config><br /><br /></alfresco-config><br /></code></pre>When Share is started, the new page will not be configured to display with any sites. The new page can be added to existing sites by using the 'customize site' feature. If you wish this new page to be automatically added to new sites, add it to the 'presets.xml' file.<br /><br />3. Run the ant deploy task to copy the configuration components to the deployed share war (APP_TOMCAT_HOME/webapps/share)<br /><br />4. Start alfresco and share by starting tomcat<br /><br />5. Login to share<br /><br />6. Create the new site (or select it if already created)<br /><br />7. Select the 'Customize Site' button and click 'Add Page' and select the 'Content Grid' page. The 'Content Grid' page should be added to the navigation bar.<br /><br />8. Press this option to see the page we just created with the simple-contentgrid.ftl template. The message 'This is the Content Grid template' should be displayed.<br /><br /><span style="font-weight: bold;">Step 4 - Mapping the page components</span><br />Now we can advance to a more interesting page template. The previous simple-contentgrid.ftl only demonstrated that we could add a page and it would render, now we will create a page template that will be similar to the Document Library template, but display our custom grid view UI.<br />1. create contentgrid.ftl in the /org/alfresco directory of the /config/alfresco/templates source folder, with containing the following:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><#import "/org/alfresco/import/alfresco-template.ftl" as template /><br /><@template.header><br /><link rel="stylesheet" type="text/css" href="${url.context}/templates/documentlibrary/documentlibrary.css" /><br /><script type="text/javascript">//<![CDATA[<br />(function()<br />{<br /> // If no location.hash exists, convert a location.search to a location.hash and replace the page<br /> var loc = window.location;<br /> if (loc.hash === "" && loc.search !== "")<br /> {<br /> var url = loc.protocol + "//" + loc.host + loc.pathname + "#" + loc.search.substring(1);<br /> window.location.replace(url);<br /> }<br />})();<br />//]]></script><br /><script type="text/javascript" src="${url.context}/templates/documentlibrary/documentlibrary.js"></script><br /><script type="text/javascript" src="${url.context}/modules/documentlibrary/doclib-actions.js"></script><br /></@><br /><br /><@template.body><br /><div id="hd"><br /> <@region id="header" scope="global" protected=true /><br /> <@region id="title" scope="template" protected=true /><br /> <@region id="navigation" scope="template" protected=true /><br /></div><br /><div id="bd"><br /> <div class="yui-t1" id="divDocLibraryWrapper"><br /> <div id="yui-main"><br /> <div class="yui-b" id="divDocLibraryDocs"><br /> <@region id="toolbar" scope="template" protected=true /><br /> <@region id="gridview" scope="template" protected=true /><br /> </div><br /> </div><br /> <div class="yui-b" id="divDocLibraryFilters"><br /> <@region id="filter" scope="template" protected=true /><br /> <@region id="tree" scope="template" protected=true /><br /> <@region id="tags" scope="template" protected=true /><br /> </div><br /> </div><br /></div><br /><p>&nbsp;</p><br /><p>&nbsp;</p><br /></@><br /><br /><@template.footer><br /><div id="ft"><br /> <@region id="footer" scope="global" protected=true /><br /></div><br /></@><br /></code></pre><br /><br />This template contains several regions, some global and some template specific. The template specific regions must have component configuraton xml files to indicate what ui webscript will be used to render in that region. The components follow a naming convention: <scope>.<region>.<template>.xml. Each template contains the following:<br /><ul><li>a scope (global or template in our example)</li><li>a region-id refering to the region of the template</li><li>a source-id refering to the template</li><li>a url refering to the ui web script to render in this region</li></ul>Components are found in the share webapp in WEB-INF/classes/alfresco/site-data/components. To start with, we will copy similar 'template' scope components from the documentlibrary template. For each of these 'template' components, we will also have to change the source-id value to refer to our 'contentgrid' template. We will just use the existing 'global' templates as is.<br /><table cellpadding="4"><br /><tbody><tr><td><b>Region</b></td><td><b>Scope</b></td><td><b>Component</b></td></tr><br /><tr><td>header</td><td>global</td><td>global.header.xml exists</td></tr><br /><tr><td>title</td><td>template</td><td>template.title.contentgrid.xml copied from template.title.documentlibrary.xml</td></tr><br /><tr><td>navigation</td><td>template</td><td>template.navigation.contentgrid.xml copied from template.navigation.documentlibrary.xml</td></tr><br /><tr><td>toolbar</td><td>template</td><td>template.toolbar.contentgrid.xml copied from template.toolbar.documentlibrary.xml</td></tr><br /><tr><td>gridview</td><td>template</td><td>template.gridview.contentgrid.xml created new</td></tr><br /><tr><td>filter</td><td>template</td><td>template.filter.contentgrid.xml copied from template.filter.documentlibrary.xml</td></tr><br /><tr><td>tree</td><td>template</td><td>template.tree.contentgrid.xml copied from template.tree.documentlibrary.xml</td></tr><br /><tr><td>tags</td><td>template</td><td>template.tags.contentgrid.xml copied from template.tags.documentlibrary.xml</td></tr><br /><tr><td>footer</td><td>global</td><td>global.footer.xml exists</td></tr><br /></tbody></table>All the other template components should be copied as is, with the source-id changed to refer to the 'contentgrid' template. The 'template.gridview.contentgrid.xml' component is new. Create the 'template.gridview.contentgrid.xml' file to contain our custom view. It should contain the following:<pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version='1.0' encoding='UTF-8'?><br /><component><br /> <scope>template</scope><br /> <region-id>gridview</region-id><br /> <source-id>contentgrid</source-id><br /> <url>/someco/components/contentgrid/gridview</url><br /></component><br /></code></pre><br />Create a temporary ui webscript to render a simple message. In the alfresco/site-webscripts source folder, create folders com/orbitz/components/contentgrid to contain our ui webscripts supporting the content grid. Create the following three files:<br /><br />gridview.get.desc.xml<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code><webscript><br /> <shortname>gridview</shortname><br /> <description>A simple grid view to view content in a grid layout</description><br /> <url>/orbitz/components/contentgrid/gridview</url><br /></webscript><br /></code></pre><br /><br />gridview.get.html.ftl<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>This is the grid view!<br /></code></pre><br /><br />gridview.get.properties<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>## Titles<br />title=New Content<br />header=New Content Details<br /><br />## Labels<br />label.name=Name<br />label.title=Title<br />label.description=Description<br />label.content=Content<br /></code></pre><br /><br />The last file 'gridview.get.properties' isn't needed yet, but we should have it around when we need it.<br /><br />Next, modify the contentgrid.xml file in the config/alfresco/site-data/template directory to reference this 'contentgrid.ftl' template.<br /><br />Finally, create a content template: . Restart the Share app and navigate to the site where this page is configured, and view the page. It should render similar to the document library page.<br /><br />Now we can test our new contentgrid template with our temporary ui webscript displayed. Deploy (ant deploy) and restart Share. Navigate to our site and display the 'content grid' page.<br /><br /><br /><span style="font-weight: bold;">Step 5 - Creating a new page component using ui web scripts<br /><br /></span><span>The next blog articles will add the desired functionality to share as time allows. We will create a new UI web script that calls a data webscript that serves the metadata and content extract from the document library to display in a grid form. </span><span style="font-weight: bold;"><br /></span></template></region></scope>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com1tag:blogger.com,1999:blog-7203181491808683266.post-19622042340787665782008-12-19T14:15:00.000-08:002009-01-08T08:38:58.050-08:00Writing Webscripts 1 - Unit Testing Web ScriptsThere is a big push in the <a href="http://www.agilealliance.org/">agile community</a> to develop code using a <a href="http://en.wikipedia.org/wiki/Test-driven_development">Test Driven Development</a> approach. This could be anything from 'test first' <a href="http://www.extremeprogramming.org/">extreme programming</a> to assuring a certain coverage metric is achieved with unit tests that accompany delivered code. Either case, the test driven approach yields better code in a shorter period of time, is more self documented, better factored, and is not as prone to chaos effects when changed. Should web scripts be subject to unit testing? I believe it is important to test at least the data web scripts using a testing harness like <a href="http://www.martinfowler.com/bliki/Xunit.html">xUnit</a> that can be run during builds or in continuous integration. There are examples of this type of test within the <a href="http://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/HEAD/root/projects/remote-api/source/test/java/org/alfresco/repo/cmis/ws/">Alfresco 3.0 source tree</a>.<div><br /></div><div>This blog entry will focus on creating a project in Eclipse that leverages the <a href="http://wiki.alfresco.com/wiki/Installing_Labs_3#Alfresco_SDK_and_APIs">Alfresco SDK</a> (currently 3c) to test and deploy web scripts to the Alfresco repository. These unit tests will run with Embedded Alfresco configuration to be as self-contained as possible. Although these tests will not be unit tests in the strictest sense of the word, they will help facilitate more rapid development of code with the appropriate tests within an IDE. These tests can also be run as part of continuous integration.<br /><br />The sourcecode for this article is available from <a href="http://www.wentsoft.com/unit-test-webscripts.tar.gz">this site</a>.<br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="font-weight: bold;">Step 1 - Download and Install SDK with Eclipse</span><span class="Apple-style-span"><br />Follow the <a href="http://wiki.alfresco.com/wiki/Alfresco_SDK">Alfresco SDK wiki page</a> to install the SDK. Essentially, you will execute the following steps:<br /></span></span></div><div><ol><li>Download <a href="http://wiki.alfresco.com/wiki/Installing_Labs_3#Alfresco_SDK_and_APIs">alfresco-sdk-3c</a><br /></li><li>Import into Eclipse<br /></li><li>Add dependency libraries to SDK AlfrescoEmbedded<br /></li><li>Validate with SDK FirstFoundationClient</li></ol></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="font-weight: bold;">Step 2 - Setup the project<span style="font-weight: bold;"><span style="font-weight: bold;"><br /></span></span></span>In order to run unit tests that test data web scripts properly, we need to set up a project that can lauch Alfresco as an Embedded server, with sufficient configuration files to allow us to test our data web scripts. <span class="Apple-style-span" style="font-weight: bold;"><span style="font-weight: bold;"><span style="font-weight: bold;"><br /></span></span></span></span></div><div>Here is a summary of the steps I followed:</div><div><ol><li>Copy SDK FirstFoundatoinClient as a starting point.<br /></li><li>Set project dependency to SDK AlfrescoEmbedded<br /></li><li>Modify dir.root of custom-repository.properties</li><li>Test run as application main of FirstFoundationClient </li></ol></div><div>To create the project 'unit-test-webscripts', I copied the SDK's FirstFoundationClient in Eclipse and renamed it to 'unit-test-webscripts'. This project should already reference the SDK AlfrescoEmbedded project, and be configured to run Alfresco as Embedded, but it will need to be altered to run web scripts this way. </div><div>Modify the 'custom-repository.properties' file to reference the alfresco data root directory that you have configured when running Alfresco. It will be necessary to reference the same dir.root as alfresco when it is run standalone to share a database instance on the same machine, and allow you to test in either embedded mode or remote mode. I also added the line 'index.recovery.mode=FULL' to force the indexer to recover any out of sync references.</div><div><br /></div><div>At this point, you should be able to run the 'main' method of the FirstFoundationClass of this project with eclipse' 'Run as Application' to make sure all is well. </div><div><br /></div><div></div><blockquote><div>NOTE: This step failed for me with a class not found error until I included a library reference to: alfresco-jlan-embed.jar in the java build path of the SDK AlfrescoEmbedded project and exported it. </div><div></div></blockquote><div><br /></div><div>After running, you should see in the console the line 'Alfresco started' to indicate the test was completed. The result of the run should be a new content item created in the repository with the name 'Foundation API sample (<span class="Apple-style-span" style="font-style: italic;">current Time in Milliseconds</span>)' within the 'Company Home' folder. Start tomcat where alfresco is deployed and log in to view the Company Home folder. You should see the new files added. Make sure to stop tomcat before running embedded tests.</div><div><br /></div><div><span class="Apple-style-span" style="font-weight: bold;">Step 3 - Configure to Run Web scripts via embedded alfresco</span></div><div><br />Once the project is copied and we can run the test class, we can now configure it to run our test webscripts. To summarize the steps:</div><div><br /></div><div><ol><li>Create a source folder 'test' to contain configuration files and unit test code<br /></li><li>Create a folder 'alfresco' within the 'test' source folder to which we will copy the necessary spring context files<br /></li><li>copy web-scripts-application-context.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco<br /></li><li>copy webscript-framework-application-context.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco<br /></li><li>copy web-scripts-application-context-test.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco<br /></li><li>comment out web Script messages bean 'webscripts.resources' of webscript-framework-application-context.xml<br /></li><li>copy web-scripts-config.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco</li><li>comment out bean 'webscript.org.alfresco.repository.dictionary.getchildassoc.get' from web-scripts-application-context.xml since class 'org.afresco.repo.web.scripts.dictionary.GetChildAssociationDef' no longer exists in jars<br /></li><li>comment out bean 'webscript.org.alfresco.repository.dictionary.getchildassocs.get' from web-scripts-application-context.xml since class 'org.afresco.repo.web.scripts.dictionary.GetChildAssociationDefs' no longer exists in jars<br /></li><li>create required directory source/alfresco/templates/webscripts<br /></li><li>create required directory source/alfresco/webscripts</li><li>copy webscript-framework-config.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco</li><li>create required directory source/alfresco/templates/activities</li><li>create directory source/alfresco/extension/templates/webscripts where our webscritps will go</li><li>copy 'status.ftl' from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco/extension/templates/webscripts to the new extension/templates/webscripts directory to provide a default status template</li></ol></div><div></div><div>Now the project is set up to run our webscripts.</div><div><br /></div><div><span style="font-weight: bold;">Step 4 - Write the unit test and webscript</span><br /></div><div><br />Add our data webscript to test into the source/alfresco/extension/templates/webscripts folder<br /></div><div>Create 'test.get.desc.xml' with the following contents:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><webscript><br /><shortname>test</shortname><br /><description>Return runas user name</description><br /><format>argument</format><br /><url>/someco/test</url><br /><authentication runas="RunAsOne">user</authentication><br /><transaction>required</transaction><br /></webscript><br /></code></pre></div>This description describes the webscript 'someco/test' that will run as the user RunAsOne.<br />Create 'test.get.html.ftl' with the following contents:<br /><div><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>${userName!"<notset>"}<br /></code></pre>This template will display the value of userName.<br />Create 'test.get.js' with the following code:<br /><div><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>model.userName = person.properties.userName;<br /></code></pre></div>This script will set the value of userName with the current userName we are running as.<br />Thats it for the webscript. Now create the unit test into the test/org/someco directory called 'RunAsTest' to test the data webscript:<br /><div><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>package org.someco;<br /><br />import org.alfresco.model.ContentModel;<br />import org.alfresco.repo.web.scripts.BaseWebScriptTest;<br />import org.alfresco.service.cmr.security.AuthenticationService;<br />import org.alfresco.service.cmr.security.PersonService;<br />import org.alfresco.util.PropertyMap;<br />import org.alfresco.web.scripts.TestWebScriptServer.GetRequest;<br />import org.alfresco.web.scripts.TestWebScriptServer.Response;<br /><br />public class RunAsTest extends BaseWebScriptTest {<br /> private AuthenticationService authenticationService;<br /> private PersonService personService;<br /><br /> private static final String USER_ONE = "RunAsOne";<br /><br /> private static final String URL_GET_CONTENT = "/someco/test";<br /><br /> @Override<br /> protected void setUp() throws Exception<br /> {<br /> super.setUp();<br /><br /> this.authenticationService = (AuthenticationService) getServer().getApplicationContext().getBean(<br /> "AuthenticationService");<br /> this.personService = (PersonService) getServer().getApplicationContext().getBean("PersonService");<br /><br /> // Create users<br /> createUser(USER_ONE);<br /> }<br /><br /> private void createUser(String userName)<br /> {<br /> if (this.authenticationService.authenticationExists(userName) == false)<br /> {<br /> this.authenticationService.createAuthentication(userName, "PWD".toCharArray());<br /><br /> PropertyMap ppOne = new PropertyMap(4);<br /> ppOne.put(ContentModel.PROP_USERNAME, userName);<br /> ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");<br /> ppOne.put(ContentModel.PROP_LASTNAME, "lastName");<br /> ppOne.put(ContentModel.PROP_EMAIL, "email@email.com");<br /> ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");<br /><br /> this.personService.createPerson(ppOne);<br /> }<br /> }<br /><br /> @Override<br /> protected void tearDown() throws Exception<br /> {<br /> super.tearDown();<br /> }<br /><br /> public void testRunAs() throws Exception<br /> {<br /> Response response = sendRequest(new GetRequest(URL_GET_CONTENT), 200, "admin");<br /> assertEquals(USER_ONE, response.getContentAsString());<br /> }<br /><br />}<br /><br /></code></pre></div>This test creates 'UserOne', runs the 'someco/test' webscript, and checks the result code as 200 (success) and to see if the user name this script 'run as' was returned in the response.<br /><br /><span style="font-weight: bold;">Step 5 - Run the unit test</span><div><br />In order to see results, you should copy the log4j.properties file from $TOMCAT_HOME/webapps/alfresco/WEB_INF/classes into your source directory and configure log4j.properties to show debugging information for running web scripts by setting 'log4j.logger.org.alfresco.web.scripts=debug' and 'log4j.logger.org.alfresco.repo.web.scripts=debug' as recommended in the logging session of the <a href="http://wiki.alfresco.com/wiki/Web_Scripts_Framework#Logging">3.0 Web scripts Framework</a> page of alfresco wiki.<br /><br />To run the test, simply select the test/com/someco/RunAsTest class in Eclipse and select the 'run as' JUnit test. The test should pass and the console should show something like 'Processed script url (get) /someco/test in 517.601ms' as its last line.<br /><br /></div><div style="font-weight: bold;">Step 6 - Deploy the web script</div><div>Once confirmed, the web script tested can be deployed either by uploading it to the Company Home/Data Dictionary/Web Scripts Extensions space or add the web scripts to the $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco/extension/templates/webscripts folder and restarting alfresco.<br /></div><div><br /></div></div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com6tag:blogger.com,1999:blog-7203181491808683266.post-73664896274180912982008-12-08T13:53:00.001-08:002009-04-11T09:19:54.314-07:00Extending Share 2 - Adding a New Content button to Document LibraryIn the <a href="http://edlovesjava.blogspot.com/2008/12/extending-share-1-creating-share.html">Extending Share 1 blog entry</a>, we created a share extension project. Now we will write code to add a 'new content' button to the document library section of Share, based on the code created in the <a href="http://edlovesjava.blogspot.com/2008/12/learning-surf-6-creating-webscript.html"> learning surf 1-6 blog entries</a>.<br /><div><blockquote>NOTE: the complete sourceocde for this project can be downloaded from <a href="http://www.wentsoft.com/deals-share-extension.tar.gz">here.</a></blockquote></div><span class="Apple-style-span" style="font-weight: bold;font-size:130%;" ><span class="Apple-style-span">Step 1 - Create a new form component</span></span><div>We will create a ui web script to open a create content form. In the 'config/alfresco/site-webscripts' source folder in package 'com.orbitz.components.documentlibrary', add the following files:<br /><br /></div><div style="font-weight: bold;">create-content.get.desc.xml</div><div><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><webscript><br /><shortname>create-content</shortname><br /><description>Create Content module primarily for Document Library</description><br /><url>/components/documentlibrary/create-content</url><br /></webscript><br /></code></pre></div><div style="font-weight: bold;">create-content.get.html.ftl</div><div><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><div id="${args.htmlid}-dialog" class="create-folder"><br /><div class="hd">${msg("title")}</div><br /><div class="bd"><br /><form id="${args.htmlid}-form" action="" method="post" accept-charset="utf-8"><br /><div class="yui-g"><br /><h2>${msg("header")}:</h2><br /></div><br /><div class="yui-gd"><br /><div class="yui-u first"><label for="${args.htmlid}-name">${msg("label.name")}:</label></div><br /><div class="yui-u"><input id="${args.htmlid}-name" type="text" name="name" tabindex="1" />&nbsp;*</div><br /></div><br /><div class="yui-gd"><br /><div class="yui-u first"><label for="${args.htmlid}-title">${msg("label.title")}:</label></div><br /><div class="yui-u"><input id="${args.htmlid}-title" type="text" name="title" tabindex="2" /></div><br /></div><br /><div class="yui-gd"><br /><div class="yui-u first"><label for="${args.htmlid}-description">${msg("label.description")}:</label></div><br /><div class="yui-u"><textarea id="${args.htmlid}-description" name="description" rows="3" cols="20" tabindex="3" ></textarea></div><br /></div><br /><div class="yui-gd"><br /><div class="yui-u first"><label for="${args.htmlid}-content">${msg("label.content")}:</label></div><br /><div class="yui-u"><textarea id="${args.htmlid}-content" name="content" rows="3" cols="20" tabindex="3" ></textarea></div><br /></div><br /><div class="bdft"><br /><input type="button" id="${args.htmlid}-ok" value="${msg("button.ok")}" tabindex="4" /><br /><input type="button" id="${args.htmlid}-cancel" value="${msg("button.cancel")}" tabindex="5" /><br /></div><br /></form><br /></div><br /></div><br /><br /></code></pre><br /></code></pre></div><div style="font-weight: bold;">create-content.get.properties</div><div><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>## Titles<br />title=New Content<br />header=New Content Details<br /><br />## Labels<br />label.name=Name<br />label.title=Title<br />label.description=Description<br />label.content=Content<br /></code></pre>To test the new form,<br /><ol><li>Deploy the web script by running the 'deploy' an task. (Make sure that share was started at least once so that the share war is expanded in the webapps/share directory of tomcat, and that the APP_TOMCAT_HOME and TOMCAT_HOME is properly set. This will copy the web scripts to the share directory.</li><li>Start tomcat</li><li>Check the ui web script by going to the url <a href="http://localhost:8080/share/service/components/documentlibrary/create-content?htmlid">http://localhost:8080/share/service/components/documentlibrary/create-content?htmlid </a>which should render the form (without any CSS, though). </li></ol><br /></div><div style="font-weight: bold;"><span style="font-size:130%;">Step 2 - Create a new toolbar for Document Library adding the 'New Content' button<br /></span></div><div>The document library toolbar will be modified to render the 'New Content' button linked to an action handler that will open the create-content form via the simple dialog mechanism. To do this, we will create a new toolbar component '/orbitz/components/documentlibrary/toolbar' that will augment the existing '/components/documentlibrary/toolbar' with minimal code replication. This is a bit dangerous since it is entirely possible that the document library toolbar will change over time, so perhaps overriding it in this way is not the best approach, however, I have tried to minimize the exposure to code change as much as possible. if others have a better approach, I would gladly accept it. The source for this new component will also be in the 'config/alfresco/site-webscripts' source folder in the 'com.orbitz.components.documentlibrary' package, with the following files:<br /><span style="font-weight: bold;">toobar.get.desc.xml<br /></span><span>This component description alters the url to '/orbitz/component/documentlibrary/toolbar' which will have to be mapped for the 'toolbar' the region of the documentlibary template.</span><span style="font-weight: bold;"><span style="font-weight: bold;"><span style="font-weight: bold;"></span></span><br /></span><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><webscript><br /><shortname>DocLib Toolbar</shortname><br /><description>Document Library: Toolbar Component</description><br /><url>/orbitz/components/documentlibrary/toolbar</url><br /></webscript><br /></code></pre><br /><span style="font-weight: bold;">toolbar.get.head.ftl<br /></span><span>This is a copy of the original 'toolbar.get.head.ftl' with the addition of DocListOrbitzToolbar Assets including 'orbitz.toolbar.css' and 'orbitz.toolbar.js' which provide custom styles and behavior for the 'New Content' button.</span><span style="font-weight: bold;"><br /></span><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><!-- DocListOrbitzToolbar Assets --><br /><link rel="stylesheet" type="text/css" href="${page.url.context}/components/documentlibrary/orbitz.toolbar.css" /><br /><script type="text/javascript" src="${page.url.context}/components/documentlibrary/orbitz.toolbar.js"></script><br /><!-- DocListToolbar Assets --><br /><link rel="stylesheet" type="text/css" href="${page.url.context}/components/documentlibrary/toolbar.css" /><br /><script type="text/javascript" src="${page.url.context}/components/documentlibrary/toolbar.js"></script><br /><!-- Simple Dialog Assets --><br /><script type="text/javascript" src="${page.url.context}/modules/simple-dialog.js"></script><br /><!-- File-Upload Assets --><br /><link rel="stylesheet" type="text/css" href="${page.url.context}/modules/flash-upload.css" /><br /><script type="text/javascript" src="${page.url.context}/modules/flash-upload.js"></script><br /><link rel="stylesheet" type="text/css" href="${page.url.context}/modules/html-upload.css" /><br /><script type="text/javascript" src="${page.url.context}/modules/html-upload.js"></script><br /><script type="text/javascript" src="${page.url.context}/modules/file-upload.js"></script><br /></code></pre></div><div><br /><span style="font-weight: bold;">toolbar.get.html.ftl</span><br />This is a copy of the original 'toolbar.get.html.ftl' with a new javascript block instantiating 'alfresco.DocListOrbitzToolbar' as well as 'Alfresco.DocListToolbar'. This new javascript object will be defined in the new javascript file 'orbitz.toolbar.js' referenced in 'toolbar.get.head.ftl' above. Also, the 'new-content' button is added beneath the 'new folder' button.<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><script type="text/javascript">//<![CDATA[<br />new Alfresco.DocListToolbar("${args.htmlid}").setOptions(<br />{<br />siteId: "${page.url.templateArgs.site!""}"<br />}).setMessages(<br />${messages}<br />);<br />new Alfresco.DocListOrbitzToolbar("${args.htmlid}").setOptions(<br />{<br />siteId: "${page.url.templateArgs.site!""}"<br />}).setMessages(<br />${messages}<br />);<br />//]]></script><br /><div id="${args.htmlid}-body" class="toolbar"><br /><br /><div id="${args.htmlid}-headerBar" class="header-bar flat-button"><br /><div class="new-folder hideable DocListTree"><button id="${args.htmlid}-newFolder-button" name="newFolder">${msg("button.new-folder")}</button></div><br /><div class="new-content hideable DocListTree"><button id="${args.htmlid}-newContent-button" name="newContent">${msg("button.new-content")}</button></div><br /><div class="separator hideable DocListTree">&nbsp;</div><br /><div class="file-upload hideable DocListTree"><button id="${args.htmlid}-fileUpload-button" name="fileUpload">${msg("button.upload")}</button></div><br /><div class="separator hideable DocListTree">&nbsp;</div><br /><div class="selected-items hideable DocListTree DocListFilter DocListTags"><br /><button class="no-access-check" id="${args.htmlid}-selectedItems-button" name="doclist-selectedItems-button">${msg("menu.selected-items")}</button><br /><div id="${args.htmlid}-selectedItems-menu" class="yuimenu"><br /><div class="bd"><br /><ul><br /> <li><a rel="" href="#"><span class="onActionCopyTo">${msg("menu.selected-items.copy")}</span></a></li><br /> <li><a rel="" href="#"><span class="onActionMoveTo">${msg("menu.selected-items.move")}</span></a></li><br /> <li><a rel="delete" href="#"><span class="onActionDelete">${msg("menu.selected-items.delete")}</span></a></li><br /> <li><a type="document" rel="" href="#"><span class="onActionAssignWorkflow">${msg("menu.selected-items.assign-workflow")}</span></a></li><br /> <li><a rel="permissions" href="#"><span class="onActionManagePermissions">${msg("menu.selected-items.manage-permissions")}</span></a></li><br /> <li><hr/></li><br /> <li><a rel="" href="#"><span class="onActionDeselectAll">${msg("menu.selected-items.deselect-all")}</span></a></li><br /></ul><br /></div><br /></div><br /></div><br /><div class="rss-feed"><button id="${args.htmlid}-rssFeed-button" name="rssFeed">${msg("link.rss-feed")}</button></div><br /></div><br /><br /><div id="${args.htmlid}-navBar" class="nav-bar flat-button"><br /><div class="folder-up hideable DocListTree"><button class="no-access-check" id="${args.htmlid}-folderUp-button" name="folderUp">${msg("button.up")}</button></div><br /><div class="separator hideable DocListTree">&nbsp;</div><br /><div id="${args.htmlid}-breadcrumb" class="breadcrumb hideable DocListTree"></div><br /><div id="${args.htmlid}-description" class="description hideable DocListFilter DocListTags"></div><br /></div><br /><br /></div><br /><br /></code></pre></div><div><br /><span style="font-weight: bold;">toobar.get.properties</span><br />This is a copy of the original toolbar.get.properties with only a new couple of new properties:<br /><ul><li>button.new-content</li><li>message.new-content</li></ul><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>## Buttons<br />button.delete=Delete<br />button.new-folder=New Folder<br />button.new-content=New Content<br />button.up=Up<br />button.upload=Upload<br /><br />## Links<br />link.rss-feed=RSS Feed<br /><br />## Drop-down Menus<br />menu.selected-items=Selected Items...<br />menu.selected-items.copy=Copy to...<br />menu.selected-items.move=Move to...<br />menu.selected-items.delete=Delete<br />menu.selected-items.assign-workflow=Assign Workflow...<br />menu.selected-items.manage-permissions=Manage Permissions...<br />menu.selected-items.deselect-all=Deselect All<br /><br />## Pop-up Messages<br />message.new-folder.success=Folder '{0}' created<br />message.new-content.success=Content '{0}' created<br />message.new-folder.failure=Could not create '{0}'<br />message.new-content.failure=Could not create '{0}'<br />message.multiple-delete.success=Successfully deleted {0} item(s)<br />message.multiple-delete.failure=Could not delete items<br />title.multiple-delete.confirm=Confirm Multiple Delete<br />message.multiple-delete.confirm=Are you sure you want to delete the following {0} items?<br />message.multiple-delete.please-wait=Please wait. Files being deleted...<br /><br />## Toolbar Modes<br />description.path=<br />description.path.more=<br />description.all=All Documents in the Document Library<br />description.all.more=<br />description.editingMe=Documents I'm Editing<br />description.editingMe.more=(working copies)<br />description.editingOthers=Documents Others are Editing<br />description.editingOthers.more=(working copies)<br />description.recentlyModified=Documents Recently Modified<br />description.recentlyModified.more=<br />description.recentlyAdded=Documents Added Recently<br />description.recentlyAdded.more=<br />description.tag=Documents and Folders Tagged with<br />description.tag.more={0}<br /></code></pre></div><div><span style="font-size:130%;"><span style="font-weight: bold;">Step 3, create the javascript to support the toolbar</span></span><br /><br />Within the 'source/web' source folder 'components.documentlibrary' package, the new javascript and css assets referenced in 'toolbar.get.head.ftl' are defined:</div><div><br /><span style="font-weight: bold;">orbitz.toolbar.js</span></div><div>This javascript creates a new object: 'Alfresco.DocListOrbitzToolbar' instantiated in 'toolbar.get.html.ftl' that decorates the 'newContent' button as a YUI button mapped to the 'onNewContent' action handler to open the new-content form using the SimpleDialog mechanism<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>/**<br />* DocumentList Toolbar component.<br />*<br />* @namespace Alfresco<br />* @class Alfresco.DocListToolbar<br />*/<br />(function()<br />{<br />/**<br />* YUI Library aliases<br />*/<br />var Dom = YAHOO.util.Dom,<br />Event = YAHOO.util.Event,<br />Element = YAHOO.util.Element;<br /><br />/**<br />* Alfresco Slingshot aliases<br />*/<br />var $html = Alfresco.util.encodeHTML;<br /><br />/**<br />* DocListToolbar constructor.<br />*<br />* @param {String} htmlId The HTML id of the parent element<br />* @return {Alfresco.DocListToolbar} The new DocListToolbar instance<br />* @constructor<br />*/<br />Alfresco.DocListOrbitzToolbar = function(htmlId)<br />{<br />// Mandatory properties<br />this.name = "Alfresco.DocListOrbitzToolbar";<br />this.id = htmlId;<br /><br />// Initialise prototype properties<br />this.widgets = {};<br />this.modules = {};<br />this.selectedFiles = [];<br />this.currentFilter =<br />{<br />filterId: "",<br />filterOwner: "",<br />filterData: ""<br />};<br /><br />// Register this component<br />Alfresco.util.ComponentManager.register(this);<br /><br />// Load YUI Components<br />Alfresco.util.YUILoaderHelper.require(["button", "menu", "container"], this.onComponentsLoaded, this);<br /><br />// Decoupled event listeners<br />//YAHOO.Bubbling.on("pathChanged", this.onPathChanged, this);<br />//YAHOO.Bubbling.on("folderRenamed", this.onPathChanged, this);<br />//YAHOO.Bubbling.on("filterChanged", this.onFilterChanged, this);<br />YAHOO.Bubbling.on("deactivateAllControls", this.onDeactivateAllControls, this);<br />//YAHOO.Bubbling.on("selectedFilesChanged", this.onSelectedFilesChanged, this);<br />YAHOO.Bubbling.on("userAccess", this.onUserAccess, this);<br /><br />return this;<br />};<br /><br />Alfresco.DocListOrbitzToolbar.prototype =<br />{<br />/**<br />* Object container for initialization options<br />*<br />* @property options<br />* @type object<br />*/<br />options:<br />{<br />/**<br />* Current siteId.<br />*<br />* @property siteId<br />* @type string<br />*/<br />siteId: "",<br /><br />/**<br />* ContainerId representing root container<br />*<br />* @property containerId<br />* @type string<br />* @default "documentLibrary"<br />*/<br />containerId: "documentLibrary",<br /><br />/**<br />* Number of multi-file uploads before grouping the Activity Post<br />*<br />* @property groupActivitiesAt<br />* @type int<br />* @default 5<br />*/<br />groupActivitiesAt: 5,<br /><br />/**<br />* Flag indicating whether navigation bar is visible or not.<br />*<br />* @property hideNavBar<br />* @type boolean<br />*/<br />hideNavBar: false<br />},<br /><br />/**<br />* Current path being browsed.<br />*<br />* @property currentPath<br />* @type string<br />*/<br />currentPath: "",<br /><br />/**<br />* Current filter to choose toolbar view and populate description.<br />*<br />* @property currentFilter<br />* @type string<br />*/<br />currentFilter: null,<br /><br />/**<br />* FileUpload module instance.<br />*<br />* @property fileUpload<br />* @type Alfresco.module.FileUpload<br />*/<br />fileUpload: null,<br /><br />/**<br />* Object container for storing YUI widget instances.<br />*<br />* @property widgets<br />* @type object<br />*/<br />widgets: null,<br /><br />/**<br />* Object container for storing module instances.<br />*<br />* @property modules<br />* @type object<br />*/<br />modules: null,<br /><br />/**<br />* Array of selected states for visible files.<br />*<br />* @property selectedFiles<br />* @type array<br />*/<br />selectedFiles: null,<br /><br />/**<br />* Set multiple initialization options at once.<br />*<br />* @method setOptions<br />* @param obj {object} Object literal specifying a set of options<br />* @return {Alfresco.DocListToolbar} returns 'this' for method chaining<br />*/<br />setOptions: function DLTB_setOptions(obj)<br />{<br />this.options = YAHOO.lang.merge(this.options, obj);<br />return this;<br />},<br /><br />/**<br />* Set messages for this component.<br />*<br />* @method setMessages<br />* @param obj {object} Object literal specifying a set of messages<br />* @return {Alfresco.DocListToolbar} returns 'this' for method chaining<br />*/<br />setMessages: function DLTB_setMessages(obj)<br />{<br />Alfresco.util.addMessages(obj, this.name);<br />return this;<br />},<br /><br />/**<br />* Fired by YUILoaderHelper when required component script files have<br />* been loaded into the browser.<br />*<br />* @method onComponentsLoaded<br />*/<br />onComponentsLoaded: function DLTB_onComponentsLoaded()<br />{<br />Event.onContentReady(this.id, this.onReady, this, true);<br />},<br /><br />/**<br />* Fired by YUI when parent element is available for scripting.<br />* Component initialisation, including instantiation of YUI widgets and event listener binding.<br />*<br />* @method onReady<br />*/<br />onReady: function DLTB_onReady()<br />{<br /><br />// New Content button: user needs "create" access<br />this.widgets.newContent = Alfresco.util.createYUIButton(this, "newContent-button", this.onNewContent,<br />{<br />disabled: true,<br />value: "create"<br />});<br /><br />// Finally show the component body here to prevent UI artifacts on YUI button decoration<br />//Dom.setStyle(this.id + "-body", "visibility", "visible");<br />},<br /><br /><br />/**<br />* YUI WIDGET EVENT HANDLERS<br />* Handlers for standard events fired from YUI widgets, e.g. "click"<br />*/<br /><br /><br /><br />/**<br />* New Content button click handler<br />*<br />* @method onNewContent<br />* @param e {object} DomEvent<br />* @param p_obj {object} Object passed back from addListener method<br />*/<br />onNewContent: function DLTB_onNewContent(e, p_obj)<br />{<br />var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "slingshot/doclib/action/folder/site/{site}/{container}/{path}",<br />{<br />site: this.options.siteId,<br />container: this.options.containerId,<br />path: this.currentPath<br />});<br /><br />var doSetupFormsValidation = function DLTB_oNF_doSetupFormsValidation(p_form)<br />{<br />// Validation<br />// Name: mandatory value<br />p_form.addValidation(this.id + "-createContent-name", Alfresco.forms.validation.mandatory, null, "keyup");<br />// Name: valid filename<br />p_form.addValidation(this.id + "-createContent-name", Alfresco.forms.validation.nodeName, null, "keyup");<br />p_form.setShowSubmitStateDynamically(true, false);<br />};<br /><br />if (!this.modules.createContent)<br />{<br />this.modules.createContent = new Alfresco.module.SimpleDialog(this.id + "-createContent").setOptions(<br />{<br />width: "30em",<br />templateUrl: Alfresco.constants.URL_SERVICECONTEXT + "components/documentlibrary/create-content",<br />actionUrl: actionUrl,<br />doSetupFormsValidation:<br />{<br /> fn: doSetupFormsValidation,<br /> scope: this<br />},<br />firstFocus: this.id + "-createContent-name",<br />onSuccess:<br />{<br /> fn: function DLTB_onCreateContent_callback(response)<br /> {<br /> var file = response.json.results[0];<br /> YAHOO.Bubbling.fire("folderCreated",<br /> {<br /> name: file.name,<br /> parentPath: file.parentPath,fileCopied<br /> nodeRef: file.nodeRef<br /> });<br /> Alfresco.util.PopupManager.displayMessage(<br /> {<br /> text: this._msg("message.new-content.success", file.name)<br /> });<br /> },<br /> scope: this<br />}<br />});<br />}<br />else<br />{<br />this.modules.createContent.setOptions(<br />{<br />actionUrl: actionUrl,<br />clearForm: true<br />});<br />}<br />this.modules.createContent.show();<br />},<br />/**<br />* Deactivate All Controls event handler<br />*<br />* @method onDeactivateAllControls<br />* @param layer {object} Event fired<br />* @param args {array} Event parameters (depends on event type)<br />*/<br />onDeactivateAllControls: function DLTB_onDeactivateAllControls(layer, args)<br />{<br />var widget;<br />for (widget in this.widgets)<br />{<br />if (this.widgets.hasOwnProperty(widget))<br />{<br />this.widgets[widget].set("disabled", true);<br />}<br />}<br />},<br /><br />/**<br />* User Access event handler<br />*<br />* @method onUserAccess<br />* @param layer {object} Event fired<br />* @param args {array} Event parameters (depends on event type)<br />*/<br />onUserAccess: function DLTB_onUserAccess(layer, args)<br />{<br />var obj = args[1];<br />if ((obj !== null) && (obj.userAccess !== null))<br />{<br />var widget, widgetPermissions, index;<br />for (index in this.widgets)<br />{<br />if (this.widgets.hasOwnProperty(index))<br />{<br /> widget = this.widgets[index];<br /> if (widget.get("srcelement").className != "no-access-check")<br /> {<br /> widget.set("disabled", false);<br /> if (widget.get("value") !== null)<br /> {<br /> widgetPermissions = widget.get("value").split(",");<br /> for (var i = 0, ii = widgetPermissions.length; i < ii; i++)<br /> {<br /> if (!obj.userAccess[widgetPermissions[i]])<br /> {<br /> widget.set("disabled", true);<br /> break;<br /> }<br /> }<br /> }<br /> }<br />}<br />}<br />}<br />},<br /><br />/**<br />* Gets a custom message<br />*<br />* @method _msg<br />* @param messageId {string} The messageId to retrieve<br />* @return {string} The custom message<br />* @private<br />*/<br />_msg: function DLTB__msg(messageId)<br />{<br />return Alfresco.util.message.call(this, messageId, "Alfresco.DocListOrbitzToolbar", Array.prototype.slice.call(arguments).slice(1));<br />}<br /><br />};<br />})();<br /></code></pre>This script at first will be configured to run the web script to create a new folder:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "slingshot/doclib/action/folder/site/{site}/{container}/{path}",<br /></code></pre>In the following steps, we will create our own script that will create a new file.<br /><br /></div><div style="font-weight: bold;">orbitz.toolbar.css</div><div><br />This css defines the images and style used for the 'newContent' button:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>.toolbar .new-content button<br />{<br />background: transparent url(images/content-new-16.png) no-repeat 12px 4px;<br />padding-left: 32px;<br />}<br /><br />.toolbar .new-content .yui-button-disabled button<br />{<br />background-image: url(images/content-new-disabled-16.png);<br />}<br /></code></pre>these images are copies of the slingshot project's 'source/web/components/images/edit-blog-16.png' image.<br /></div><div><br /><span style="font-size:130%;"><span style="font-weight: bold;">Step 4 - Create the component to map to the 'toolbar' region of the document library template</span></span><br /><br />Finally in the 'config/alfresco/site-data' source folder in the 'components' package, we override the existing 'toolbar' region in the 'documentlibrary' template to reference the /orbitz/components/documentlibrary/toolbar' component:<br /><br /><span style="font-weight: bold;">template.toolbar.documentlibrary.xml</span><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><?xml version='1.0' encoding='UTF-8'?><br /><component><br /><scope>template</scope><br /><region-id>toolbar</region-id><br /><source-id>documentlibrary</source-id><br /><url>/orbitz/components/documentlibrary/toolbar</url><br /></component><br /></code></pre><span style="font-size:130%;"><span style="font-weight: bold;">Step 5 - Deploy and Test</span></span><br />First, we need to make sure the 'share' war is installed and expanded in the tomcat host. I use the Alfresco project build.xml's<br /><blockquote>ant incremental-slingshot-tomcat-exploded</blockquote>But for a war distribtuion, just make sure it is run at least once.<br /><br />To use the build.xml, make sure to set APP_TOMCAT_HOME and TOMCAT_HOME environment variables as recommended in the previous blog article: <a href="http://edlovesjava.blogspot.com/2008/12/extending-share-1-creating-share.html">Extending Share 1 - Creating a Share Extension Project</a><br /><br />To build and deploy the share webapp expanded in the tomcat directory. Now we can deploy our extension code from the 'deals-share-extension' project's build.xml file:<br /><blockquote>ant deploy</blockquote>This will create the 'deals-share-ext.jar' jar file and copy to the share/WEB-INF/lib directory, copy the configuration files from the config/alfresco/** source folders to the share/weB-INF/classes directory, and copy the web assets from the source/web source folders to the share root directory.<br /><br />We can test our toolbar code directly using the url: <a href="http://localhost:8080/share/service/orbitz/components/documentlibrary/toolbar?htmlid">http://localhost:8080/share/service/orbitz/components/documentlibrary/toolbar?htmlid</a> , but this will not render with CSS, and without CSS, the YUI components wont work.<br /><br />Now we can test within the Share:<br /><ol><li>Launch share <a href="http://localhost:8080/share">http://localhost:8080/share</a>, opens to the share dashboard<br /></li><li>Create and/or select a Site, opens to the site dashboard<br /></li><li>Select the 'document library' section should render our new toolbar with the 'New Content' button</li><li>Press the 'New Content' button and the 'create-content' form should open</li><li>Enter the new content's name, title, description and content int he form and press 'ok' and the dialog should close, but instead of a file created, we should see a new folder created. In the next steps we will create the web script necessary to create a new file.<br /></li></ol><span style="font-weight: bold;">Step 6 - Creating a custom web script action</span><br /><br />Now we need to create a webscript service to use instead of the slingshot/doclib/action/folder service. for this blog, I will extend the action services to implement the following url pattern:<br /><br />POST slingshot/doclib/action/file/site/{site}/{container}/{path}<br /><br />To do this, we will add a new webscript service to the remote alfresco.war by uploading it to the Data Dictionary space. Later, we can separate this to our own amp module. As a starting point, we will copy the folder.post.* files to create file.post.* files in Remote API/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action directory to create the following files:<br /><ul><li>file.post.desc.xml</li><li>file.post.json.ftl</li><li>file.post.json.js</li></ul>Now we can modify these files to do the work of creating a file instead of a folder. for 'file.post.desc.xml' we modify the url pattern to the following:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><webscript><br /><shortname>folder</shortname><br /><description>Document List Action - Create folder</description><br /><url>/slingshot/doclib/action/file/site/{site}/{container}</url><br /><url>/slingshot/doclib/action/file/site/{site}/{container}/{path}</url><br /><format default="json">argument</format><br /><authentication>user</authentication><br /><transaction>required</transaction><br /></webscript><br /></code></pre><br />file.post.json.ftl will be changed, it imports the action.lib.ftl to create a standard results response json format. But I will combine the import. Its contents are the following:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><#macro resultsJSON results><br /><#escape x as jsonUtils.encodeJSONString(x)><br />{<br />"totalResults": ${results?size},<br />"overallSuccess": ${overallSuccess?string},<br />"successCount": ${successCount},<br />"failureCount": ${failureCount},<br />"results":<br />[<br /> <#list results as r><br /> {<br /> <#list r?keys as key><br /> <#assign value = r[key]><br /> <#if value?is_number || value?is_boolean><br /> "${key}": ${value?string}<#if key_has_next>,</#if><br /> <#else><br /> "${key}": "${value}"<#if key_has_next>,</#if><br /> </#if><br /> </#list><br /> }<#if r_has_next>,</#if><br /> </#list><br />]<br />}<br /></#escape><br /></#macro><br /><@resultsJSON results=results /><br /></code></pre><br />file.post.json.js needs to be modified to handle a file instead of a folder.<br /><br />The main changes I will make to this script:<br /><ol><li>get a 'content' field from the json request object from the create-content form that we added previously.<pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> if (!json.isNull("content"))<br />{<br />content = json.get("content");<br />}<br /></code></pre></li><li>Rename variables folderName, folderDescription, folderTitle, folderPath to fileName, fileDescription, fileTitle, fileDescription and filePath respectively.<br /></li><li>Create a file node instead of a folder node<pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>var fileNode = parentNode.createFile(fileName);<br /></code></pre><br /></li><li>set the content on the newly created fileNode variable arfter the fileNode.save(); method<pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> fileNode.save();<br />// Add uifacets aspect for the web client<br />fileNode.content = content;<br /></code></pre></li><li>Change messaging to reflect saving a file, not a folder.</li></ol>The completed file.post.json.js looks like this:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>/**<br />* Document List Component: action<br />*<br />* For a single-asset action, template paramters address the asset.<br />* For multi-asset actions, template parameters address the source or destination node,<br />* and a JSON body addresses the assets involved in the action.<br />* (note: HTTP DELETE methods must use URI)<br />*<br />* @param uri {string} site/{siteId}/{containerId}/{filepath} : full path to file or folder name involved in the action<br />* @param uri {string} node/{store_type}/{store_id}/{id}/{filepath} : full path to file or folder name involved in the action<br />*/<br /><br />/**<br />* Main script entry point<br />* @method main<br />*/<br />function main()<br />{<br />// Params object contains commonly-used arguments<br />var params = {};<br />var files, rootNode;<br /><br />if (url.templateArgs.store_type != undefined)<br />{<br /> params = getNodeRefInputParams();<br />}<br />else if (url.templateArgs.site != undefined)<br />{<br /> params = getSiteInputParams();<br />}<br />if (typeof params == "string")<br />{<br /> status.setCode(status.STATUS_BAD_REQUEST, params);<br /> return;<br />}<br /><br />// Resolve path if available<br />var path = url.templateArgs.path;<br />// Path might be null for the root folder<br />if (!path)<br />{<br /> path = "";<br />}<br />// Remove any leading or trailing "/" from the path<br />// Fix-up parent path to have no leading or trailing slashes<br />if (path.length > 0)<br />{<br /> var aPaths = path.split("/");<br /> while (aPaths[0] === "")<br /> {<br /> aPaths.shift();<br /> }<br /> while (aPaths[aPaths.length-1] === "")<br /> {<br /> aPaths.pop();<br /> }<br /> path = aPaths.join("/");<br />}<br />params.path = path;<br /><br />// Multiple input files in the JSON body?<br />files = getMultipleInputValues("nodeRefs");<br />if (typeof files != "string")<br />{<br /> params.files = files;<br />}<br /><br />// Check runAction function is provided the action's webscript<br />if (typeof runAction != "function")<br />{<br /> status.setCode(status.STATUS_BAD_REQUEST, "Action webscript must provide runAction() function.");<br /> return;<br />}<br /><br />// Actually run the action<br />var results = runAction(params);<br />if ((results !== null) && (results !== undefined))<br />{<br /> if (typeof results == "string")<br /> {<br /> status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, results);<br /> }<br /> else if (typeof results.status == "object")<br /> {<br /> // Status fields have been manually set<br /> status.redirect = true;<br /> for (var s in results.status)<br /> {<br /> status[s] = results.status[s];<br /> }<br /> }<br /> else<br /> {<br /> /**<br /> * NOTE: Webscripts run within one transaction only.<br /> * If a single operation fails, the transaction is marked for rollback and all<br /> * previous (successful) operations are also therefore rolled back.<br /> * We therefore need to scan the results for a failed operation and mark the entire<br /> * set of operations as failed.<br /> */<br /> var overallSuccess = true;<br /> var successCount = 0;<br /> var failureCount = 0;<br /> for (var i = 0, j = results.length; i < j; i++)<br /> {<br /> overallSuccess = overallSuccess && results[i].success;<br /> results[i].success ? ++successCount : ++failureCount;<br /> }<br /> model.overallSuccess = overallSuccess;<br /> model.successCount = successCount;<br /> model.failureCount = failureCount;<br /> model.results = results;<br /> }<br />}<br />}<br /><br /><br />/**<br />* Get and check existence of mandatory input parameters (Site-based)<br />*<br />* @method getSiteInputParams<br />* @return {object|string} object literal containing parameters value or string error<br />*/<br />function getSiteInputParams()<br />{<br />var params = {};<br />var error = null;<br />var template = url.template;<br /><br />try<br />{<br /> var siteId, containerId, sideNode, rootNode;<br /><br /> // Try to get the parameters from the URI<br /> siteId = url.templateArgs.site;<br /> containerId = url.templateArgs.container;<br /><br /> // SiteId<br /> if (template.indexOf("{site}") != -1)<br /> {<br /> if ((siteId === null) || (siteId.length === 0))<br /> {<br /> return "'site' parameter is missing.";<br /> }<br /><br /> // Find the site<br /> siteNode = siteService.getSite(siteId);<br /> if (siteNode === null)<br /> {<br /> return "Site '" + siteId + "' not found.";<br /> }<br /><br /> // ContainerId<br /> if (template.indexOf("{container}") != -1)<br /> {<br /> if ((containerId === null) || (containerId.length === 0))<br /> {<br /> return "'container' parameter is missing.";<br /> }<br /><br /> // Find the component container<br /> var rootNode = siteNode.getContainer(containerId);<br /> if (rootNode === null)<br /> {<br /> rootNode = siteNode.createContainer(containerId);<br /> if (rootNode === null)<br /> {<br /> return "Component container '" + containerId + "' not found in '" + siteId + "'.";<br /> }<br /> }<br /> }<br /><br /> // Populate the return object<br /> params =<br /> {<br /> usingNodeRef: false,<br /> siteId: siteId,<br /> containerId: containerId,<br /> siteNode: siteNode,<br /> rootNode: rootNode<br /> }<br /> }<br />}<br />catch(e)<br />{<br /> error = e.toString();<br />}<br /><br /> // Return the params object, or the error string if it was set<br /> return (error !== null ? error : params);<br />}<br /><br />/**<br />* Get and check existence of mandatory input parameters (nodeRef-based)<br />*<br />* @method getNodeRefInputParams<br />* @return {object|string} object literal containing parameters value or string error<br />*/<br />function getNodeRefInputParams()<br />{<br />var params = {};<br />var error = null;<br /><br />try<br />{<br /> // First try to get the parameters from the URI<br /> var storeType = url.templateArgs.store_type;<br /> var storeId = url.templateArgs.store_id;<br /> var id = url.templateArgs.id;<br /><br /> var nodeRef = storeType + "://" + storeId + "/" + id;<br /> var rootNode = null;<br /><br /> if (nodeRef == "alfresco://company/home")<br /> {<br /> rootNode = companyhome;<br /> }<br /> else if (nodeRef == "alfresco://user/home")<br /> {<br /> rootNode = userhome;<br /> }<br /> else<br /> {<br /> rootNode = search.findNode(nodeRef);<br /><br /> if (rootNode === null)<br /> {<br /> return "'" + nodeRef + "' is not a valid nodeRef.";<br /> }<br /> }<br /><br /> // Populate the return object<br /> params =<br /> {<br /> usingNodeRef: true,<br /> nodeRef: nodeRef,<br /> rootNode: rootNode<br /> }<br />}<br />catch(e)<br />{<br /> error = e.toString();<br />}<br /><br /> // Return the params object, or the error string if it was set<br /> return (error !== null ? error : params);<br />}<br /><br />/**<br />* Get multiple input values<br />*<br />* @method getMultipleInputValues<br />* @return {array|string} Array containing multiple values, or string error<br />*/<br />function getMultipleInputValues(param)<br />{<br />var values = [];<br />var error = null;<br /><br />try<br />{<br /> // Was a JSON parameter list supplied?<br /> if (typeof json == "object")<br /> {<br /> if (!json.isNull(param))<br /> {<br /> var jsonValues = json.get(param);<br /> // Convert from JSONArray to JavaScript array<br /> for (var i = 0, j = jsonValues.length(); i < j; i++)<br /> {<br /> values.push(jsonValues.get(i));<br /> }<br /> }<br /> }<br />}<br />catch(e)<br />{<br /> error = e.toString();<br />}<br /><br /> // Return the values array, or the error string if it was set<br /> return (error !== null ? error : values);<br />}<br /><br /><br />/**<br />* Obtain the asset node for the given rootNode and filepath<br />*<br />* @method getAssetNode<br />* @param p_rootNode {object} valid repository node<br />* @param p_assetPath {string} rootNode-relative path to asset<br />* @return {object|string} valid repository node or string error<br />*/<br />function getAssetNode(p_rootNode, p_assetPath)<br />{<br />var assetNode = p_rootNode;<br />var error = null;<br /><br />try<br />{<br /> if (p_assetPath && (p_assetPath.length > 0))<br /> {<br /> assetNode = assetNode.childByNamePath(p_assetPath);<br /> }<br /><br /> if (assetNode === null)<br /> {<br /> return "Asset '" + p_assetPath + " not found.";<br /> }<br />}<br />catch(e)<br />{<br /> error = e.toString();<br />}<br /><br /> // Return the node object, or the error string if it was set<br /> return (error !== null ? error : assetNode);<br />}<br /><br />/**<br />* Create file action<br />* @method POST<br />* @param uri {string} /{siteId}/{containerId}/{filepath}<br />* @param json.name {string} New file name<br />* @param json.title {string} Title metadata<br />* @param json.description {string} Description metadata<br />* @param json.content {string} Content of file<br />*/<br /><br />/**<br />* Entrypoint required by action.lib.js<br />*<br />* @method runAction<br />* @param p_params {object} common parameters<br />* @return {object|null} object representation of action result<br />*/<br />function runAction(p_params)<br />{<br />var results;<br /><br />try<br />{<br />// Mandatory: json.name<br />if (json.isNull("name"))<br />{<br /> status.setCode(status.STATUS_BAD_REQUEST, "File name is a mandatory parameter.");<br /> return;<br />}<br />var fileName = json.get("name");<br /><br />var parentPath = p_params.path;<br />var filePath = parentPath + "/" + fileName;<br /><br />// Check file doesn't already exist<br />var existsNode = getAssetNode(p_params.rootNode, filePath);<br />if (typeof existsNode == "object")<br />{<br /> status.setCode(status.STATUS_BAD_REQUEST, "File '" + filePath + "' already exists.");<br /> return;<br />}<br /><br />// Check parent exists<br />var parentNode = getAssetNode(p_params.rootNode, parentPath);<br />if (typeof parentNode == "string")<br />{<br /> status.setCode(status.STATUS_NOT_FOUND, "Parent folder '" + parentPath + "' not found.");<br /> return;<br />}<br /><br />// Title and description<br />var fileTitle = "";<br />var fileDescription = "";<br />if (!json.isNull("title"))<br />{<br /> fileTitle = json.get("title");<br />}<br />if (!json.isNull("description"))<br />{<br /> fileDescription = json.get("description");<br />}<br />if (!json.isNull("content"))<br />{<br /> content = json.get("content");<br />}<br />// Create the folder and apply metadata<br />var fileNode = parentNode.createFile(fileName);<br />// Always add title & description, default icon<br />fileNode.properties["cm:title"] = fileTitle;<br />fileNode.properties["cm:description"] = fileDescription.substr(0, 100);<br />fileNode.properties["app:icon"] = "space-icon-default";<br />fileNode.save();<br />// Add uifacets aspect for the web client<br />fileNode.content = content;<br />fileNode.addAspect("app:uifacets");<br /><br />// Construct the result object<br />results = [<br />{<br /> id: filePath,<br /> name: fileName,<br /> parentPath: parentPath,<br /> nodeRef: fileNode.nodeRef.toString(),<br /> action: "createFile",<br /> success: true<br />}];<br />}<br />catch(e)<br />{<br />status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, e.toString());<br />return;<br />}<br /><br />return results;<br />}<br /><br />/* Bootstrap action script */<br />main();<br /><br /></code></pre>This service uses the action.lib.js and action.lib.ftl libraries to simplify coding of similar functions, including: checkin, checkout, cancel-checkout, copy-to, move-to, file or folder delete and others. The action.lib.js is included in the folder.post.json.js script and provides a main() entry point to do common setup, clean up and host shared functions. The main entry point calls the runAction() function defined in the specific action script 'folder.post.json.js', effectively implementing a strategy pattern.<br /><br /><span style="font-weight: bold;font-size:130%;" >Step 7 - upload, build and test</span><br />Log into the alfresco server as admin. Navigate to Company Home > Data Dictionary > Web Scripts > org > alfresco<span style="text-decoration: underline;"><br /><br /></span>Create a new directory 'doclib' and copy the three files in to this directory: 'file.post.desc.xml', 'file.post.json.ftl', and 'file.post.json.js'.<br />Navigate to <a href="http://localhost:8080/alfresco/service/index">http://localhost:8080/alfresco/service/index</a> and select 'refresh webscripts' button.<br /><br />To view the new service, use the url <a href="http://localhost:8081/alfresco/service/index/uri/slingshot/doclib/action/file/site/%7Bsite%7D/%7Bcontainer%7D">http://localhost:8080/alfresco/service/index/uri/slingshot/doclib/action/file/site/%7Bsite%7D/%7Bcontainer%7D</a> which should show the service correctly added.<br /><br /><span style="font-weight: bold;font-size:130%;" >Step 8 - alter 'New Content' button action to invoke new service</span><br />Now we have to use this new service we have created when we press the 'new content' button orbitz-toolbar.js onNewContent action line to read:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "slingshot/doclib/action/file/site/{site}/{container}/{path}",<br /><br /></code></pre>this will call our new slingshot service to create a file, which in turn will call the new remote api service we added to alfresco.<br /><br /><span style="font-weight: bold;font-size:130%;" >Step 9 - deploy and test</span><br />Now, you should be able to press 'New Content' in share Document Library and create a new content item.</div>edlovesjavahttp://www.blogger.com/profile/07515369822547982127noreply@blogger.com2