Programování

Custom configs in Alfresco

0

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:

<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>

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:

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;
    }

        . . .
}

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

<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>

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

org.springframework.extensions.config.Config config = configService.getConfig(imports");
Boolean splitTransactions = MisConfigService.getChildValue(config, "processor", "split-transactions", false);

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

ConfigElement myConfig = config.getConfigElement("config-element");
for(ConfigElement child : myConfig.getChildren()) {
    // child.getAttributes()
    // child.getName()
    // child.getValue()
}

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:

<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>

More on invoking scripts in Alfresco

0

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:

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}};

and a Freemarker template simple as that:

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>

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:

<#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

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:

Configuration cfg = new Configuration();
RhinoWrapper rhinoWrapper = new RhinoWrapper();

rhinoWrapper.setSimpleMapWrapper(true);
cfg.setObjectWrapper(rhinoWrapper);

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:

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);

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:

OutputStreamWriter streamWriter = new OutputStreamWriter(java.lang.System.out);
template.process(rootMap, streamWriter);

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

Invoking scripts in Alfresco programatically

4

I’m working on universal CRON runner, which runs scripts saved somewhere in repository (more on this next time). Running webscripts isn’t so easy, as I thought, there is no scriptService.runScriptWithThisDescription(path) method, so I did some research and got some results, which I’d like to share.

It’s possible to run java backed actions from js-webscripts. Everything you need is just a bean extending ActionExecuterAbstractBase with definition parent=“action-executer“ and it’s possible to access that bean from webscript like actions.create(„that_bean_id“). But not in reverse, at least not so simple.

First think I found was post on forum and advice to look at unit tests of webscripts, but everything I found were invocations through http. So I went deeper and figured out that Alfresco uses Rhino JavaScript implementation for scripts. At Rhino pages are good examples, so I had first working script soon. At following codelist I firstly load content of node (script body) from repository and then invocate script itself.

NodeRef nodeRef = new NodeRef("workspace://SpacesStore/4a96aaaa-bb80-eeee-aaaa-800a43fcddb8");
ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
InputStreamReader isr = new InputStreamReader(contentReader.getContentInputStream());
BufferedReader reader = new BufferedReader(isr);

Context cx = Context.enter();
try {
    Scriptable scope = cx.initStandardObjects();
    Object result = cx.evaluateReader(scope, reader, "scriptInvocation", 1, null);
} finally {
    Context.exit();
}

But I wanted something more – some model in and out and something simplier. So I found ScriptService. Now all those long lines can be ommited, because there is executeScript method!

Map<String, Object> model = new HashMap<String, Object>();
model.put("foo", "bar");

Object result = scriptService.executeScript(scriptRef, model);

Template rendering (I’m using FreeMarker’s ftls) is done the same way, but through TemplateService and processTemplate(nodeRef, model) method, which accepts the same model (altered by script running).

The most beautiful thing is ability of script (and also template) to call java methods, which classes are „injected“ through model, let’s see an example.

Map<String, Object> model = new HashMap<String, Object>();

model.put("something", new Something());
model.put("bar", "foo");

Object result = scriptService.executeScript(scriptRef, model);
String templateResult = templateService.processTemplate("workspace://SpacesStore/noderef", model);

. . .

public class Something {
    public String text = "aaa";

    public String getText() {
        return "bbb";
    }
}

Now it’s possible to use script / template:

(** script **)
bar = something.text;

(** template **)
${something.text} ?=? ${bar}

What is output? Is in templateResult „aaa ?=? aaa“? Nope! Script accesses class attributes directly, on the other side template accesses methods. So output is „bbb ?=? aaa“!!!

One more important thing: this JavaScript implementation is just a stub, so every alfresco service needs to be „injected“ through model, so for example it’s impossible to run stuff like this without modifications:

var connector = remote.connect("http");
var result = connector.call("http://www.google.com/");

Remote object has to be injected (this script is from Share/Surf), more on this in this blogpost. You have to add ScriptRemote to model and create spring-webscripts-config-custom.xml with endpoints defined, see that post. Also another services needs to be injected – to get access to repository, search service… good starting point is Alfresco Wiki.

Edit: Native Java API Access described in wiki is wrong, IT IS POSSIBLE to access maps in JavaScript like this:

var value = javaMap.key;

Pozadí ubytovníčku

2

Všichni chtějí přijít s nápadem, který změní svět, který vydělá miliony a já nevím, co ještě. Většina těchto nápadů končí rozčarováním, protože nejsou realizovatelé, nebo nejsou realizovatelné v podobě, jak si je dotyčný budoucí milionář představuje. To je v pořádku. Protože jsem už pár nápadů zkusil realizovat a neuspěl, zkusím v tomto miniseriálu představit kroky, které jsem se naučil a které doufám, že v případě Ubytovníčku povedou k dokončení a úspěšné realizaci tohoto nápadu.

Když jsem přišel s úplně prvním nápadem, okamžitě jsem koupil doménu a pak teprve začal přemýšlet, jak to vlastně udělat, co tam bude… Postupem času (dlouhého času) jsem pivotoval a pivotoval a vždycky se dostal k začátku realizace, ale tam jsem skončil. Proč? Nápad nebyl dopracovaný, nevěděl jsem, na koho budu cílit, co z toho bude dotyčný mít, co z toho budu mít já… Teď už vím víc: pokud dostanu nápad, chvíli nad ním přemýšlím a pak se ho snažím zapomenout. Pokud se další den ráno probudím a ten nápad tam je pořád a hlodá, pokračuju k další fázi – a tou je třídění myšlenek spojených s tímto nápadem.

Co mám: chuť něco dělat
Co by mělo být výstupem fáze: nápady = množství trojic problém, řešení, zákazník
Co bude výstupem fáze: nápady, o kterých si myslím, že jsou dobré
Co budu dělat: brainstormovat
Co pro to použiju: mindmapper

Pro prvotní utřídění myšlenek používám myšlenkové mapy. Konkrétně jsem si zvyknul na nástroj mindmeister, který funguje jako online aplikace i jako aplikace pro Android, takže můžu přidávat myšlenky kdykoliv a kdekoliv. Používám dva postupy: „blití všeho“ a „brainstormovací mód“.

Mindmeister @ Android

Mindmeister @ Android

Blití všeho používám především na začátku a je to takové prvotní nakopnutí, kdy ze sebe hrnu naprosto všechno, co mě napadá, a sázím to bez ladu a skladu (víceméně) do mapy, je to takové základní vytvoření budoucích uzlů celé mapy. K této fázi se už později moc vracet nedá, protože by tento přístup narušil započatou strukturu, takže je vhodné zauvažovat o vytvoření nové mapy a potom je nějak spojit. Ale popravdě – do této fáze se v udoucnu už nevracím, protože ji dobře pokryje fáze následující.

A to je fáze brainstormovací. Začnu v kořenu celého stromu a postupně procházím k listům, kdykoliv mě kdekoliv cokoliv napadne, okamžitě to přidám. Nedělám smyčky, postupuju systematicky dál a dál. Dost často skončím na listu a najednou se roztrhne pytel s nápadama a tak se chrlí.

Potom nastupuje třídicí mód, který je vhodné provádět způsobem „ráno moudřejší večera“, ale mnohdy se to ráno zvrhne v další chrlení :-). Jde to to, že se musí vyházet spousta nesmyslů (tohle je ovšem první fáze, větší odstraňování bude následovat příště), kterých je tam zákonitě velké množství, odstranit duplicity a trochu přeskládat strukturu. Ideální je, když je na projekt více lidí a tuto část udělá někdo jiný, nebo je při tomto čištění a uspořádávání přítomen. Člověk, který tyto nápady vychrlil může být slepý k některým nelogičnostem a nesmyslům a kontrola dalším člověkem tato místa odhalí a odstraní, případně poupraví. Pravdou ale je, že než hledat kompromis, lepší je danou věc úplně odstranit. Pokud bude potřeba, vyplyne tento fakt v budoucnu a může se zpětně zapracovat – méně je více.

Tato prvotní část se dá shrnout do celku „nápad“. Jde o to si uvědomit, že nápad není nápad sám, ale jeho rozpracování a zaznamenání do nějaké takovéto podoby, aby si člověk (a to především někdo druhý) mohl prohlédnout všechny souvislosti, náležitosti… Ještě pořád není vhodný čas na nákup domény, následuje další část – část ověření myšlenek a jejich ořezání, ale o tom zase až příště.

(Repost z blogu Ubytovníčku.)

Deleting workflows in Alfresco

7

I tried to find out how to delete completed workflows in Alfresco, but I find no way to do that. I’m talking about HUGE number of completed workflows – a thousands. There are many questions on that topic, but no solution. I tried to play with Workflow console, also tried Alfresco Workflow Purge webscript by Rothbury software and also tried to write own deleters based on Workflow service (more on this next time), but no success at all. In better cases process dies with „out of memory“, or completely locks Alfresco in worst cases. So I choose hardcore solution – to delete database tables. And it works :-).

I’m talking about 10 tables, which are bind with foreign keys. So to remove ALL workflows follow those steps (note I’m not responsible for data loses, you’re on your own, so backup carefully!):

  1. Stop Alfresco and stop mysql
  2. Do backups – at least alf_data and /var/lib/mysql
  3. Start mysql and open mysql client
  4. Turn off foreign key checking: SET FOREIGN_KEY_CHECKS=0;
  5. Truncate following tables:
    • JBPM_TOKEN
    • JBPM_TOKENVARIABLEMAP
    • JBPM_TASKINSTANCE
    • JBPM_SWIMLANEINSTANCE
    • JBPM_PROCESSINSTANCE
    • JBPM_MODULEINSTANCE
    • JBPM_LOG
    • JBPM_BYTEBLOCK
    • JBPM_BYTEARRAY
    • JBPM_VARIABLEINSTANCE
  6. Maybe it’s good idea to set index.recovery.mode to FULL
  7. Start Alfresco and hope everything is well
  8. Turn off reindexing set in step 6

Simple yet effective. Please, let me know, if it works on your Alfresco too. I tested it od Community versions 3.4.e, 3.4.d and 4.1.0 (built from svn).

Go to Top