Custom configs in Alfresco

While creating import services I want some configuration mechanism for (not just) BatchProcessor. I like Alfresco’s *-config-custom.xml files (aka Spring framework XMLConfigService), so I did simple custom config service. Anyone, who customized Alfresco, knows format of this file (web-client-config-custom.xml). I put that structure to new file – mis-config-custom.xml: [code lang=“xml“] <alfresco-config> <config evaluator="string-compare" condition="imports"> <processor> … Pokračovat ve čtení „Custom configs in Alfresco“

While creating import services I want some configuration mechanism for (not just) BatchProcessor. I like Alfresco’s *-config-custom.xml files (aka Spring framework XMLConfigService), so I did simple custom config service.

Anyone, who customized Alfresco, knows format of this file (web-client-config-custom.xml). I put that structure to new file – mis-config-custom.xml:

[code lang=“xml“]
<alfresco-config>
<config evaluator="string-compare" condition="imports">
<processor>
<worker-threads-num>3</worker-threads-num>
<batch-size-num>6</batch-size-num>
<split-transactions>false</split-transactions>
<logging-interval>1000</logging-interval>
</processor>
<token>
<time-refresh>1000</time-refresh>
<time-expiration>20000</time-expiration>
</token>
</config>
</alfresco-config>
[/code]

Please note those constants are just for ilustration purposes now, more on them in scripting series. My MisConfigService extends org.springframework.extensions.config.xml.XMLConfigService, but uses nothing from that class (except init method in bootstrap), I just added some statics inside to keep things together:

[code lang=“java“]
public class MisConfigService extends XMLConfigService {
public MisConfigService(ConfigSource configSource) {
super(configSource);
}

public static String getChildValue(Config config, String elementId, String childName, String defaultValue) {
ConfigElement myConfig = config.getConfigElement(elementId);
String result = myConfig.getChildValue(childName);

return (result==null ? defaultValue : result);
}

public static Integer getChildValue(Config config, String elementId, String childName, Integer defaultValue) {
Integer result = defaultValue;
try {
String number = getChildValue(config, elementId, childName, defaultValue.toString());
result = Integer.parseInt(number);
} finally { /* O:-) */ }
return result;
}

. . .
}
[/code]

To instantiate this service just add bean definition with UrlConfigSource(-s), which defines path to custom xml configuration:

[code lang=“xml“]
<bean id="misConfigService" class="cz.mis.service.MisConfigService" init-method="init">
<constructor-arg>
<bean class="org.springframework.extensions.config.source.UrlConfigSource">
<constructor-arg>
<list>
<value>classpath:alfresco/extension/mis-config-custom.xml</value>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
[/code]

Now it’s possible to access those properties in code like this:

[code lang=“java“]
org.springframework.extensions.config.Config config = configService.getConfig(imports");
Boolean splitTransactions = MisConfigService.getChildValue(config, "processor", "split-transactions", false);
[/code]

It’s also possible to access attributes of nodes in that config:

[code lang=“java“]
ConfigElement myConfig = config.getConfigElement("config-element");
for(ConfigElement child : myConfig.getChildren()) {
// child.getAttributes()
// child.getName()
// child.getValue()
}
[/code]

That’s all! And now for something completely different! I also spent some time trying to get properties files working and putting those properties into Spring configurations. There’s nice solved thread in Alfresco forums. Prefix helps:

[code lang=“xml“]
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:alfresco/extension/mis-settings.properties</value>
</list>
</property>
<property name="ignoreResourceNotFound" value="true" />
<property name="placeholderPrefix" value="${mis:" />
<property name="placeholderSuffix" value="}" />
</bean>

<!– and I can use stuff from properties file with prefix –>
<bean class="foo.bar.Example">
<property name="fooAttribute">
<value>${mis:some.sample.property}</value>
</property>
</bean>
[/code]

More on invoking scripts in Alfresco

In my last blogpost on this topic I wrote about invoking rhino scripts programatically from Alfresco and about passing its model into freemarker templates. After that I realized that alfresco templateService is unable to render more complex models (nested classes and collections) if called this way (please note on Alfresco webscripts invoked standard way this … Pokračovat ve čtení „More on invoking scripts in Alfresco“

In my last blogpost on this topic I wrote about invoking rhino scripts programatically from Alfresco and about passing its model into freemarker templates. After that I realized that alfresco templateService is unable to render more complex models (nested classes and collections) if called this way (please note on Alfresco webscripts invoked standard way this works correctly!). I think the problem is – no wrapper is used while passing model. I spent lot of time trying many things, read a lot of posts and topics on that, played with built-ins, trying to find that part in Alfresco source, but still no result, just exceptions. But solution is VERY SIMPLE – to write own templateService :-). Well, maybe this post is more about Freemarker and Rhino than Alfresco, sorry about that!

So, what I want to achieve? Suppose some simple script like this one:

[code lang=“javascript“]
model.bar="foo";
model.foo = "bar";
model.obj = {q:1, w:3, e:9};

model.arr = new Array();
model.arr[0] = {a:1, b:8, c:9, d:{d:66}};
model.arr[1] = {a:2, b:7, c:10, d:{d:66}};
model.arr[2] = {a:3, b:6, c:11, d:{d:66}};
model.arr[3] = {a:4, b:5, c:12, d:{d:66}};
[/code]

and a Freemarker template simple as that:

[code lang=“plain“]
q attribute: ${obj.q}

<#list arr?keys as value>
– ${value} = ${arr[value].a} = ${arr[value].b} = ${arr[value].c}
<#assign obj_d = arr[value].d>
+ d: ${obj_d.d}
</#list>
[/code]

Now after calling templateService.processTemplate( . . . ) the output is Exception this and Exception that… That happens, because Rhino creates NativeObjects for those objects and after passing those objects to Freemarker (through Java – without any ObjectWrapper), which doesn’t know how to handle them. The same situation happens when passing a collection with generics like <String, Object> into model – those Objects are standard Java objects and in Freemarker acts like them, they really don’t have attributes like q, a, b, etc. :-). Similar situation can happen with collections – they needs to be wrapped too. But stop with theory, back to practise. For those interested in wrapping and so, follow Freemarker documentation on wrappers.

Before wrapping – when I printed out keys of model, there were’nt my attributes defined in Javascript. What I found inside is on following listing, note my attributes are also there – bar, foo, arr, obj:

[code lang=“plain“]
<#assign keys = model?keys>
<#list keys as key>
+ ${key}
</#list>

(** output **)
+ getClass
+ clone
+ put
+ remove
+ get
+ equals
+ entrySet
+ class
+ hashCode
+ foo
+ keySet
+ size
+ clear
+ isEmpty
+ containsKey
+ values
+ arr
+ containsValue
+ empty
+ toString
+ putAll
+ bar
+ obj
[/code]

Those properties belongs to hashMap and is possible to get rid of them using simpleMapWrapper, this technique is described well in Rizwan Ahmed post.

What about my templateService implementation? First I tried to use JSONtoFmModel and convert javascript objects to strings, but that was no way forward. That was before I discovered wrappers :-). There is RhinoWrapper class in Freemarker and it solves all my problems! It can be used as simple as this:

[code lang=“java“]
Configuration cfg = new Configuration();
RhinoWrapper rhinoWrapper = new RhinoWrapper();

rhinoWrapper.setSimpleMapWrapper(true);
cfg.setObjectWrapper(rhinoWrapper);
[/code]

Previous listing creates new Freemarker configuration instance and rhino wrapper instance and binds them together. With that configuration it’s possible to create template instance, which takes reader with template content, let’s read it from repository:

[code lang=“java“]
NodeRef nodeRef = new NodeRef("workspace://SpacesStore/56ba2237-f776-494a-939b-d259b68c021a");

ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
InputStream inputStream = contentReader.getContentInputStream();

Template template = new Template("my_template", new InputStreamReader(inputStream), cfg);
[/code]

That’s all we need to render. Rendering takes one more parameter – model map, which is HashMap defined in my previous blogpost. To keep it simple (and post a little shorter) I’m writing output to sysout:

[code lang=“java“]
OutputStreamWriter streamWriter = new OutputStreamWriter(java.lang.System.out);
template.process(rootMap, streamWriter);
[/code]

Those few lines and everything works fine now! But I got some silver hairs with trying to solve that, now I can feel like a boss, so I achieved new „Alfresco guru badge“ :-). Also I’m thinking about some public GIT repository with my Alfresco inventions, what you think about it?

Like an Alfresco guru!
Like an Alfresco guru!

PostScriptum: Can anyone explain me why Alfresco uses Rhino version 1.6R7 which is from year 2007? :-O

BFU vyzva: Zkuste setrit cas svuj i druhych

Vcera jsem se nehorazne nasral vytocil a vyustilo to v potrebu odpovedet na vsechny mejlove adresy uvedene v dotycnem mejlu a budu to delat znovu a znovu, dokud mi neco bude chodit. Text tohoto mejlu prikladm nize, treba to nekoho inspiruje. Jako takovou maturitu pro pokrocilejsi muzu jeste doporucit deset bodu – 10 Rules to … Pokračovat ve čtení „BFU vyzva: Zkuste setrit cas svuj i druhych“

Vcera jsem se nehorazne nasral vytocil a vyustilo to v potrebu odpovedet na vsechny mejlove adresy uvedene v dotycnem mejlu a budu to delat znovu a znovu, dokud mi neco bude chodit. Text tohoto mejlu prikladm nize, treba to nekoho inspiruje. Jako takovou maturitu pro pokrocilejsi muzu jeste doporucit deset bodu – 10 Rules to Reverse the Email Spiral.

Dobry den,

jmenuji se Vaclav Balak a vubec Vas neznam. Proc Vam pisu? Protoze mi davate kontaktni informace na Vas, prestoze jsem si je vubec nevyzadal a nemam o ne zajem. Ale klidne o Vas muzou mit zajem moji klienti, kterym Vase kontaktni udaje velmi rad prodam!! A proto pisu – NAUCTE SE CHRANIT SOUKROMI SVYCH KONTAKTU!!! Kdyz uz mate potrebu preposilat mejly nadepsane „Fwd: FW: FWD:……Tohle musis videt!!!!“, zkuste udelat tu malickost a SMAZAT OBSAH ZPRAVY, nebo jej aspon upravit, at nepredavate adresy lidi, kterym to prislo pred Vami dal a dal a dal a dal, dokud je nekdo nezneuzije!! A vubec – jeden znamy na toto tema napsal pekny clanek, zkuste se na nej podivat: BFU výzva: Rádi e-mailujete? Má to však své zásady!

A proc pisu jeste? Reaguju na mejl samotny – na clanek o Kosteleckych Uzeninach. Za clanek dekuji, byl velmi zajimavy, jeste zajimavejsi vsak byla forma, v jake jsem clanek obdrzel – dokument Wordu! Proc? Pokud odmyslim fakt, ze nekdo tento program vubec nema (tudiz tento dokument ani neotevre a neprecte si jej), nemuzu pochopit, proc kopirujete clanek volne pristupny na internetu dovnitr nejakeho dokumentu? Dochazi ke ztrate informace – k puvodnimu clanku pribyl odkaz na video, pribyly k nemu nove informace… O tohle vsechno ctenari tohoto .doc dokumentu prisli! Posudte nasledujici dva zpusoby poslani informace:

Zpusob jedna:
1) Prectu si clanek, ktery se mi libi
2) zkopiruju adresu na tu stranku a
3) vlozim ji do noveho mejlu a odeslu

Zpusob dva:
1) Prectu si clanek, ktery se mi libi
2) zkopiruju cely clanek a
3) otevru word a protoze neumim vytvorit novy dokument, tak
4) otevru nejaky jiny dokument
5) dam ulozit jako a ulozim ho
6) smazu obsah toho dokumentu a (postupne oznacim vse mysi samozrejme)
7) pro jistotu ho ulozim
8 ) vlozim do tohoto prazdneho dokumentu obsah toho clanku
9) ulozim a dlouho hledam na disku, kam jsem ten dokument vubec ulozil
10) prilozim tento dokument k mejlu a odeslu

Proc proboha volite zpusob dva? Nehlede na to, ze v 90% pripadu to stejne zkopirujete blbe, takze tam polovina informaci vubec neni, nejsou tam obrazky, nejsou tam nova zjisteni, ktere autor publikoval po odeslani zmineneho souboru mejlem, ma to rozhazene formatovani… a predevsim – NENI TAM DISKUZE, kde jsou mnohdy podstatnejsi informace, nez v clanku samotnem (pokud to teda neni kopie z novinky.cz, kde se nadava na ods, nebo z idnes.cz, kde se nadava na komunisty) a navic se teda nemuzete do teto diskuze zapojit s vlastnim nazorem!!

Zkuste se nad tim prosim aspon zamyslet a pripadne tento mejl predat dal svym znamym – ovsem na zaklade vyse zminenych informaci – nemam zajem, aby moje emailova adresa kolovala svetem az do 21.12.2012.

Dekuji za cas a preji pekny den
Vaclav Balak

PS. puvodni adresa onoho clanku je
http://www.bizwiz.cz/2012/02/11/kostelecke-uzeniny-tvurce-hororovych-zazitku/