Štítky Alfresco
Creating rules programaticaly
25. Led
Nowadays I’ve been working on policies and triggering a behavior based on user action, so I wanted to write about it, but I found more interesting stuff I did with it than triggering itself (which is by the way well described by Jeff Potts). I wanted a routine, which runs on every node created and adds a rule, if that node is a folder.
At first I did some research on rules. How are rules bound to nodes? We’re working on repository, so it should be a node. When you open a node browser and look on node with rule bound, you’ll see ‘ruleFolder’ child of that node of ruleFolder type. Children of this node are rules itself – one child, one rule. Inside each are actions defined with conditions and actions with their parameters, like on following structure:
Conditions may be structured too – if condition is not „no-condition“ – there may be much complicated structure: either condition itself (actioncondition node type) with parameters as children, or composite-condition (compositeactioncondition node type) with conditions nested inside. Each condition has „invert“ boolean variable, which inverts condition meaning. Properties of simpliest condition (no-condition) are on following image:
Parameters are another types of node – actionparameter. Each has two important properties: parameterName and parameterValue and those values depends on action (or condition) on which is this parameter bind to. Example of parameter for copy-to-webproject action is on following image:
And of course third node type – action. With or without parameters. In both cases with properties like definitionName, executeAsynchronously, etc. see following image:
Examples on how to set rule on node are in nodeRef cookbook. I worked with CopyToWebProject action, which can be with parameters defined like this:
Action myAction = actionService.createAction(CopyToWebProjectActionExecuter.NAME); Map<String, Serializable> actionProps = myAction.getParameterValues(); actionProps.put(CopyToWebProjectActionExecuter.PARAM_DESTINATION_FOLDER, avmRef); myAction.setParameterValues(actionProps);
Note static variables with parameter names (prefix PARAM_). Executers are in org.alfresco.repo.action.executer package, or it’s possible to use own action executer (extending ActionExecuterAbstractBase).
Next step is to add a condition, in my case HasAspectEvaluator like this:
ActionCondition condition = actionService.createActionCondition(HasAspectEvaluator.NAME); Map<String, Serializable> conditionProps = new HashMap<String, Serializable>(); conditionProps.put(HasAspectEvaluator.PARAM_ASPECT, ContentModel.ASPECT_WORKING_COPY); condition.setParameterValues(conditionProps); condition.setInvertCondition(true);
Evaluators (package org.alfresco.repo.action.evaluator) also have parameter names defined with prefix PARAM_. Previous code creates condition testing aspect is NOT set on node (note setInvertCondition() method) – this condition is for testing copied content is not working copy.
Created condition and action should be placed in CompositeAction:
CompositeAction compositeAction = actionService.createCompositeAction(); compositeAction.addActionCondition(condition); compositeAction.addAction(myAction);
And the final step is to add this whole composition inside created rule and use ruleService to bind created rule to node – through its nodeRef:
rule.setAction(compositeAction); ruleService.saveRule(sourceRef, rule);
This is enough and it should work. What am I using it for? I need CopyToWebProject rule to foolow tree structure in DM, not just copy everything in subdirs to given folder. So I have my own implementation of OnCreateNodePolicy, which binds this rule to every created folder (under some conditions of course).
Adding columns to custom browse.jsp
10. Říj
Last week I had to change browse.jsp view in alfresco and I need to add custom columns – attributes from my own data model. It’s pretty simple and I’ll explain that in this blogpost.
I tried to do is as much simple as possible, so I removed all other view types but details and in this view I added some boolean properties, which have own action handler – so are clickable and after click change their state. Result of this changes is on following screenshot.
You can see I added two boolean properties (in Czech – Ano = true, Ne = false) among others (Type). To do that I needed to override BrowseBean and create own NodePropertyResolver. Now from the begining:
- I created custom document model,
- wrote custom browse.jsp (just copied and edited original one),
- overrode outcomes to new view,
- overwrote BrowseBean and wrote custom NodePropertyResolver.
Creating custom document model is well described everywhere else (at least in alfresco wiki), so I’m not going to describe it here, also overriding views. Lets start with altering browse.jsp.
Items in list are created by RichList component, in which we are interested in just some attributes: viewMode and value. ViewMode is a String value (details, icons, list) with default view mode. When I tried to change ViewMode value to details, it doesn’t work correctly, so I had to override getBrowseViewMode() to return „details“.
public class BrowseTeletextBean extends BrowseBean {
. . .
private NodePropertyResolver pageExpiredResolver = new BooleanNodePropertyResolver(TxtModel.txtExpired, getNodeService());
. . .
@Override
public String getBrowseViewMode(){
return "details";
}
@Override
public List<Node> getContent() {
List<Node> list = super.getContent();
for(Node node : list){
node.addPropertyResolver("pageExpired", pageExpiredResolver);
. . .
}
return list;
}
. . .
Value should return list of Node type, in which I wanted to add custom fields, so I needed to override it too. I put there some propertyResolvers – I did my own BooleanPropertyResolver:
public class BooleanNodePropertyResolver implements NodePropertyResolver {
private NodeService nodeService;
private QName qName;
public BooleanNodePropertyResolver(QName qName, NodeService nodeService){
this.qName = qName;
this.nodeService = nodeService;
}
@Override
public Object get(Node node) {
boolean expired = NodeUtils.getBoolProperty(node.getNodeRef(), qName, nodeService);
return (expired ? "Ano" : "Ne");
}
}
Now it’s possible to build a list in view like this:
<a:richList id="contentRichList" binding="#{BrowseTeletextBean.contentRichList}" viewMode="#{BrowseTeletextBean.browseViewMode}" pageSize="#{BrowseTeletextBean.pageSizeContent}" styleClass="recordSet" headerStyleClass="recordSetHeader" rowStyleClass="recordSetRow" altRowStyleClass="recordSetRowAlt" width="100%" value="#{BrowseTeletextBean.content}" refreshOnBind="true" var="r">
. . .
<a:column id="col14" style="text-align:left">
<f:facet name="header">
<a:sortLink id="col14-sort" label="Expired" value="expired" styleClass="header"/>
</f:facet>
<a:actionLink id="col14-act1" value="#{r.pageExpired}" actionListener="#{BrowseTeletextBean.clickPageExpired}">
<f:param name="id" value="#{r.id}" id="param14-1" />
</a:actionLink>
</a:column>
. . .
Here you can see actionLink with value defined by custom NodePropertyResolver (r.pageExpired), which renders either „Ano“ or „Ne“ based on value in TxtModel.txtExpired (QName) property in model.
public static final QName txtExpired = QName.createQName("http://www.alfresco.org/model/txt/1.0", "expired");
ActionLink has an action handler – actionListener parameter – this handler just change value of the expired property and is as simple as this:
public void clickPageExpired(ActionEvent event){
NodeRef nodeRef = getNodeRefFromParams(event);
if(nodeRef != null) {
boolean status = NodeUtils.getBoolProperty(nodeRef, TxtModel.txtExpired, getNodeService());
getNodeService().setProperty(nodeRef, qName, !status);
UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
}
}
Note that UIContext…notifyBeans() is needed to notify beans, that some change had been done and we need to refresh view. For this refresh also refreshOnBind parameter on richList is needed to be true.
It was easy to put it together, but like everything in Alfresco – badly documented. So I hope this post help someone. Feel free to ask questions, if something is not clear enough
.
MVC and Spring surf
7. Úno
One of things I missed in Spring Surf are Models and Controllers. I tried many ways to get them working, but still no useful solution. So I chose really simple way to do that – to define another dispatcher and to play with urlrewrite. In this post I’ll describe what I did and as a little bonus you’ll find a breathtaking example
– YUI based Ajax whisperer.
Let’s start with empty Spring Surf skeleton:
project --topLevelPackage cz.shmoula.controllers surf install perform eclipse
Now add dispatcher – in web.xml we have to define new servlet with name controller, loaded as second and with url-pattern /app (default dispatcher is mapped to /service), which is filtered by urlrewrite.
<servlet> <servlet-name>controller</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>controller</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping>
In next step we’ll need web application config – Spring names them as -servlet.xml prefixed with servlet name. So let’s create controller-servlet.xml in WEB-INF (in STS New/Spring bean configuration file) and add some useful stuff there: for first we want to use annotations for our controllers, so let’s add there two beans – DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter.
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
Then we need to define where are our controllers lying, so let’s define base package.
<context:component-scan base-package="cz.shmoula.controllers.controller" use-default-filters="false"> <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" /> </context:component-scan>
Last thing is to tell, where will be our views and which suffix they use.
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>
Okay, folks, that’s almost all! Now the interesting part: Writing the controller. Make a class in package we defined earlier. It should extend some AbstractController or another stuff, but it’s not necessary, we’ll simple it!
Put a test method inside:
package cz.shmoula.controllers.controller;
@Controller
public class AutocompleteController{
@RequestMapping(value = "/test")
public String test(Model model){
model.addAttribute("attribute", "Some sample string");
return "test";
}
}
That’s our controller. Now we need a view. Previously we defined parameters for view resolver and in controller we’re returning name of our model. Putted together result is: /WEB-INF/jsp/test.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test page backed by controller</title>
</head>
<body>
<h1>Hi, what we have there?</h1>
<p>${attribute}</p>
</body>
</html>
Last thing to do is to define some rules for urlrewrite. We’ll try to forward all /app/ requests to our dispatcher and anything else to Default Spring Dispatcher. That’s done by these rules (urlrewrite.xml):
<rule> <from>/app/(.*)</from> <to>/app/$1</to> </rule> <rule> <from>/</from> <to>/service/</to> </rule>
Done? So let’s test it! Run it up and open browser at http://localhost:8080/controllers/app/test. Does it work? I hope so!
That was the first part – worst one, now came pure fun – some functionality. We want to implement some buzzlike thingy as Autocompletion is – whisperer. To keep it simple our model layer will be just a Collection with Strings, so let’s modify our controller with constructor:
private Collection<String> termList = new HashSet<String>();
public AutocompleteController() {
termList.add("Prague");
termList.add("Paris");
termList.add("Poznan");
}
Now we need controller code – something returning some results. So at /autocomplete will be routine, which waits for „query“parameter with part of String, which tries to find in termList and return:
@RequestMapping(value = "/autocomplete")
public void autocomplete(HttpServletRequest httpRequest, Model model) {
String query = httpRequest.getParameter("query");
String response = "";
if (query != null) {
for (Iterator<String> i = termList.iterator(); i.hasNext();) {
String term = i.next();
if (term.startsWith(query))
response += "\n" + term;
}
}
model.addAttribute("response", response);
}
Now we have to render our model through something, so add view /WEB-INF/jsp/autocomplete.jsp:
${response }
And now to add all that into widget. Let’s extend calendar widget in /WEB-INF/webscripts/calendar – first add styles and YUI scripts into calendar.get.head.ftl:
<link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.8.2r1/build/autocomplete/assets/skins/sam/autocomplete.css">
<script src="http://yui.yahooapis.com/2.8.2r1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script src="http://yui.yahooapis.com/2.8.2r1/build/datasource/datasource-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.2r1/build/connection/connection-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.2r1/build/autocomplete/autocomplete-min.js"></script>
<style type="text/css">
#myAutoComplete {
width:25em;
padding-bottom:2em;
}
</style>
The last step is to create div and put a script into calendar.get.html.ftl:
<div>
<label for="myInput">Autocomplete:</label>
<div id="myAutoComplete">
<input id="myInput" type="text">
<div id="myContainer"></div>
</div>
</div>
<script type="text/javascript">
YAHOO.example.BasicRemote = function() {
var oDS = new YAHOO.util.XHRDataSource("/controllers/app/autocomplete");
oDS.responseType = YAHOO.util.XHRDataSource.TYPE_TEXT;
oDS.responseSchema = {
recordDelim: "\n",
fieldDelim: "\t"
};
oDS.maxCacheEntries = 5;
var oAC = new YAHOO.widget.AutoComplete("myInput", "myContainer", oDS);
return {
oDS: oDS,
oAC: oAC
};
}();
</script>
And that should be all, now let’s test it, if it works! So open http://localhost:8080/controllers/calendar and voila…
Ok, that’s all for this post, looks like it works! So you can download backup of this code somewhere around here and enjoy it on your own devbox. Keep looking forward for next stuff, maybe using facebook login with Spring social, who knows
.
JSP based components in Spring Surf
4. Úno
In my last blogpost we got Java backed webscripts working, now let’s try something completely different: defining page snippet as a component in .JSP. Just let’s continue with our last code, which is possible to download, but it’s not necessary, it’s possible to build it all on green meadow (aka after project in roo is created and surf installed).
First thing to do is to add dependency for jstl into Maven’s pom.xml and then update maven dependencies and refresh our project in Eclipse.
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>com.springsource.javax.servlet.jsp.jstl</artifactId>
<version>1.2.0</version>
</dependency>
Now you can get glue on Spring forums, or trust me and create new component type with JSP Renderer: it’s needed to create new directory – /WEB-INF/classes/surf/site/component-types and inside new component type configuration – test.xml.
<?xml version="1.0" encoding="UTF-8"?>
<component-type>
<id>test</id>
<title>Test Component Type</title>
<description>Test Component Type</description>
<processor mode="view">
<id>jsp</id>
<jsp-path></jsp-path>
</processor>
</component-type>
Jsp-path is at this time undefined, because we haven’t created any page yet. So let’s do something with that. We need to create new template and new template instance:
roo> surf template create --path test Created SRC_MAIN_WEBAPP/WEB-INF/templates/test.ftl roo> surf template instance create --id test --path templates --template test Created SRC_MAIN_WEBAPP/WEB-INF/templates/test.xml
At this time we have Freemarker template (test.ftl) and template instance (test.xml) inside directory /WEB-INF/templates. We need to edit those files. First let’s add following code into test.ftl. There are four regions defined – three of them inside template scope (already defined by installation of Surf addon) and one with page scope, which we’ll define and bind later.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>${page.title}</title>
</head>
<body>
<div id="page">
<div id="header">
<@region id="header" scope="template" />
</div>
<div id="horznav">
<@region id="horznav" scope="template" />
</div>
<div id="content">
<@region id="content" scope="page" />
</div>
<div id="footer">
<@region id="footer" scope="template" />
</div>
</div>
</body>
</html>
Following code (template instance) is definition of components in template scope and is also located in templates directory: test.xml.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<template-instance>
<title>test</title>
<description>Instance of test</description>
<template-type>test</template-type>
<components>
<component>
<region-id>header</region-id>
<url>/company/header</url>
</component>
<component>
<region-id>horznav</region-id>
<url>/navigation/horizontal</url>
</component>
<component>
<region-id>footer</region-id>
<url>/company/footer</url>
</component>
</components>
</template-instance>
Now we can create new page based on previously defined template-instance and add link to our new page to horizontal bar. For those actions we can use roo:
roo> surf page create --id test --path pages\test --templateInstance test Created SRC_MAIN_WEBAPP/WEB-INF/pages/test Created SRC_MAIN_WEBAPP/WEB-INF/pages/test/test.xml roo> surf page association create --sourceId home --destId test Managed SRC_MAIN_WEBAPP/WEB-INF/pages/home/home.xml
As written in roo output, directory test inside pages is created and test.xml within. This file is page configuration and we have to define our content for region defined in template. But not necessary, it’s good idea to check our work now. So let’s try to Run it on server… It’s ugly, but it works!
That’s cool, but our aim is to create working .jsp page. How to do that? What about simply create that page?
So let’s create test.jsp inside /WEB-INF/pages/test and fill it with some simple code:
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt'%>
<%@ page import="java.util.*" %>
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<h1>Test jsp</h1>
<%
String someText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
List<String> l = new ArrayList<String>();
l.add("Maecenas iaculis odio id eros fermentum non tempus erat interdum.");
l.add("Sed mi orci, dignissim vitae cursus hendrerit, congue sed odio. ");
l.add("Vivamus quam diam, dapibus sit amet placerat a, tempus at magna.");
session.setAttribute("someText", someText);
session.setAttribute("l", l);
%>
<p>${someText }</p>
<c:forEach var="x" items="${l }" >
<p>${x}</p>
</c:forEach>
That’s our page. Now we need to connect it with previously created component type – /WEB-INF/classes/surf/site/component-types/test.xml. In time of creation we omit jsp-path, so fill it up now:
<jsp-path>/WEB-INF/pages/test/test.jsp</jsp-path>
Last thing to do is to put our shiny new component-type into page. In template we defined region, which is filled in page scope. That’s that components section in page config – /WEB-INF/pages/test/test.xml. It’s good idea to add some title and some styling like border and background thanks to chrome element:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<page>
<id>test</id>
<title>test</title>
<template-instance>test</template-instance>
<authentication>none</authentication>
<components>
<component>
<region-id>content</region-id>
<component-type-id>test</component-type-id>
<chrome>box</chrome>
<title>JSP inside</title>
</component>
</components>
</page>
After rebuild and redeploy we have our jsp component working, as you can see at http://localhost:8080/webscripts/test. Congratulations, good work!
Ah, in case you got lost, there is package with working sourcecode for this whole example.
Freemarker templates and java backed webscripts
13. Led
Wondering about output to freemarker template from java backed webscript in Spring Surf? You’re on the right place, it’s simple so I’ll try to describe it on a few lines. For first I suppose some prerequisites you have:
- you have Spring Roo and (or) Springsource Tool Suite (or Eclipse with plugin),
- you have Spring surf roo addon installed (note: I can’t get it working under newest roo, so I’m using 1.0.2.RELEASE),
- you know at least basics of Spring Surf,
- knowledge of Alfresco WebScripts (and Java Backed Web Scripts) is a big plus.
Ok, let’s get it done quickly! Create new directory, for example webscripts. Go into it, fire up roo, create a new project, install surf extension into it and create an Eclipse project:
project --topLevelPackage cz.shmoula.webscripts surf install perform eclipse
Now you should have base skeleton of our future app (you can also download it here) and you can import it into SpringSource Tool Suite.
Webscripts are located in src/main/webapp/WEB-INF/webscripts folder, so open that folder and create two files: example.get.html.ftl (template) and example.get.desc.xml (description).
<p>logged user: ${userId}</p>
<webscript>
<shortname>Example</shortname>
<description>Sample java backed webscript</description>
<url>/example</url>
</webscript>
Now we have to create our java source code. It’s not bad idea to put webscripts to their own package, for example webscript. In that package create a new class, which extends org.springframework.extensions.webscripts.AbstractWebScript – Example. By doing that we have to implement an execute method, which is called, when webscript is executed:
@Override
public void execute(WebScriptRequest req, WebScriptResponse res)
throws IOException {
}
Let’s fill that method. We need a model map, which holds values as pair (String)key – (Object)value. For our desired function we also need to get HttpServletRequest:
Map<String, Object> model = new HashMap<String, Object>(); HttpServletRequest httpRequest = ServletUtil.getRequest();
Now to fill up model (aka our function). Let’s find out if there is logged in user and if so, write out it’s userId:
if(AuthenticationUtil.isAuthenticated(httpRequest)){
model.put("userId", AuthenticationUtil.getUserId(httpRequest));
}else{
model.put("userId", "guest");
}
Final step is to put out our model. We need to get writer and renderTemplate, which we specified by its path (and also flush and close our writer):
Writer writer = res.getWriter(); String templatePath = "webscripts/example.get.html.ftl"; renderTemplate(templatePath, createTemplateParameters(req, res, model), writer); writer.flush(); writer.close();
Last thing we have to do is to let Surf know about our shiny new webscript. You can add new bean directly inside applicationContext, but it’s not bad idea to keep them separately. So in src/main/webapp/WEB-INF/config create new Spring Bean Definition file – custom-webscripts-config.xml and put inside new bean definition:
<bean id="webscript.webscripts.example.get" class="cz.shmoula.webscript.Example" parent="webscript"/>
Now we have to import that new config: in the same directory is surf-config.xml file, so let’s add there new import:
<import resource="custom-webscripts-config.xml"/>
At this time we can run our project and visit http://localhost:8080/webscripts/example, which shows following page:
It’s possible to login (if you have set up user factory) as an Alfresco user and after that another userId is shown. I’m planning to write new blogpost about custom user factory and stuff like that, so be patient please. Finally, you can download source code for this example at this link. Structure of project is shown at following screenshot (note that red dots – those are modified files):
Finally, try to experiment with some examples from Alfresco wiki and look forward to next blogpost.










Nejnovější komentáře