Posts tagged java

Sending content to java webscript via POST method

0

Yesterday I stucked on problem of getting data from WebScriptRequest, which were sent by POST method. There is some bug in Apache Tomcat, so there is a simple workaround described for example here. My task was to create webscript, which tries to find a file in repository (or create it, if it doesn’t exist) and to write content inside – data of POST request.

My webscript is Java backed, so only description in WEB-INF/classes/alfresco/templates/client/webscripts/org/alfresco/archivace is needed:

<webscript>
<shortname>SaveModif file</shortname>
<description>Ulozi dorucena data do *.modif.xml</description>
<url>/archivace/savemodif?nodeRef={noderef}</url>
<authentication>user</authentication>
<transaction>required</transaction>
</webscript>

Also definition in web-scripts-application-context.xml:

<bean id=“webscript.org.alfresco.archivace.savemodif.post“ class=“cz.shmoula.webscripts.SaveModif“ parent=“webscript“>
<property name=“authenticationComponent“ ref=“AuthenticationComponent“/>
<property name=“nodeService“ ref=“NodeService“/>
<property name=“searchService“ ref=“SearchService“/>
<property name=“fileFolderService“ ref=“FileFolderService“/>
<property name=“contentService“ ref=“ContentService“/>
</bean>

Now let’s get Node object itself:

// try to get DescriptionNode
Node xmlNode = getDescriptionNode(nodeRef);

// if DescriptionNode doesn’t exist, create one
if (xmlNode == null) {
String path = noda.getName();
String filename = path.substring(0, path.lastIndexOf(„.“)) + „.modif.xml“;
NodeRef spaceRef = nodeService.getPrimaryParent(noda.getNodeRef()).getParentRef();
xmlNode = createDescriptionNode(spaceRef, filename);
}

At this time in xmlNode is Node object – either existing one, or created one, so let’s write that request content inside:

ContentWriter writer = contentService.getWriter(xmlNode.getNodeRef(), ContentModel.PROP_CONTENT, true);
writer.putContent(req.getContent().getInputStream());

Simple, but sometimes it doesnt work. Why? Due to that bug mentioned in perex. So how are data sent through POST method? Let’s try it with wget:

wget –post-data=’pokus‘ –http-user=admin –http-password=admin http://localhost:8080/alfresco/service/archivace/savemodif?nodeRef=workspace://SpacesStore/91919928-3709-4e2e-a829-b0be46976e42

Captured request looks like this

Image and video hosting by TinyPic

As you can see, everything looks ok. But getContent() in our webscript is empty. Why? there is no content – there are just parameters, due to application/x-www-form-urlencoded content type. So req.getParameterNames() returns String[] array in which are in our case two parameters: {{nodeRef}, {pokus}}. This is not what we want, because length ot these parameter names may be limited (2M?). So let’s use mentioned advice – change content-type:

wget –post-data=’pokus‘ –header=’Content-Type: text/xml‘ –http-user=admin –http-password=admin http://localhost:8080/alfresco/service/archivace/savemodif?nodeRef=workspace://SpacesStore/91919928-3709-4e2e-a829-b0be46976e42

And now it works! 🙂 Sniffed request looks like before, just content-type header line had changed:

Image and video hosting by TinyPic

Now is possible to send inputStream of content to Writer:

writer.putContent(req.getContent().getInputStream());

At this time req.getParameterNames() has just one parameter – {{nodeRef}} and content is really saved in data field.

Delete dialog workaround

0

Write this one, or not to write this blogpost? That is the question :-). But I would like to simply remember for some things i did, so let's do it! In my DMS I have some multimedia content saved and this content has attached description in xml file. Also I have my own space view (more info here), so I show just that multimedia content. In case of deletion that content I want to delete that xml too, so how to do that?

First we need to create an action (it's just a copy of original action ;-)) specification in web-client-config and name it – in my case "delete_doc_shm":

<action id="delete_doc_shm">
    <permissions>
        <permission allow="true">Delete</permission>
    </permissions>
    <evaluator>org.alfresco.web.action.evaluator.DeleteDocEvaluator</evaluator>
    <label-id>delete</label-id>
    <image>/images/icons/delete.gif</image>
    <action-listener>#{BrowseSessionBean.deleteFile}</action-listener>
    <params>
        <param name="id">#{actionContext.id}</param>
        <param name="ref">#{actionContext.nodeRef}</param>
    </params>
</action>

It's pretty simple, just a definition of action with Evaluator and wanted permissions. I wrote a blogpost about this a year about :-). Now I have an action, so I need to add it into some group, or view link directly. Action groups are defined in web-client-config too:

<action-group id="session_browse">
    <show-link>false</show-link>
    <style-class>inlineAction</style-class>
    .  .  .
     <action idref="delete_doc_shm" />
</action-group>

And view of this group is realised by actions component (r variable is variable defined by richListComponent, library prefix r is Alfresco's repo taglib):

.  .  .
<r:actions id="actions_menu_1" value="session_browse" context="#{r}" showLink="false" styleClass="inlineAction" />
.  .  .  

The result's like this (there's no difference in original view of that actions menu and this, but functionality – see below ;-)):

Image and video hosting by TinyPic

Now some application logic: In case of pressing our delete butto, BrowseSessionBean.deleteFile() method is called. This method just do same things that original BrowseBean.deleteFile() does, but redirects to my own dialog:

public void deleteFile(ActionEvent event) {
    .  .  .  some foo – bar stuff  .  .  .
    FacesContext fc = FacesContext.getCurrentInstance();
    NavigationHandler navigationHandler = fc.getApplication().getNavigationHandler();
    navigationHandler.handleNavigation(fc, null, "dialog:deleteVcfFile");
}

This dialog is defined in web-client-config like

<dialog
    name="deleteVcfFile"
    page="/jsp/dialog/delete.jsp"
    managed-bean="DeleteVcfContentDialog"
    icon="/images/icons/delete_large.gif"
    title-id="delete_file"
    description-id="delete_file_info" />

and points to DeleteVcfContentDialog bean:

<managed-bean>
    <description>The bean that backs up the Delete Content Dialog</description>
    <managed-bean-name>DeleteVcfContentDialog</managed-bean-name>
    <managed-bean-class>cz.shmoula.dialog.DeleteVcfContentDialog</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
        <property-name>navigator</property-name>
        <value>#{NavigationBean}</value>
    </managed-property>
    .  .  .  more properties  .  .  .
</managed-bean>

Class managing this bean extends DeleteContentDialog and has only one method – finishImpl. See following source, it's commented:

public class DeleteVcfContentDialog extends org.alfresco.web.bean.content.DeleteContentDialog {

    @Override
    protected String finishImpl(FacesContext context, String outcome)throws Exception {
        // get the content to delete
        VcfNode node = new VcfNode(this.browseBean.getDocument());
        if (node != null) {
            if (ContentModel.TYPE_MULTILINGUAL_CONTAINER.equals(node.getType())) {
                // delete the mlContainer and its translations
                getMultilingualContentService().deleteTranslationContainer(node.getNodeRef());
            } else {
                // getting attached xml node
                Node descriptionXml = node.getXmlNode();
                // and if xml exists, delete it!
                if(descriptionXml != null)
                    this.getNodeService().deleteNode(descriptionXml.getNodeRef());
                
                // delete the node
                this.getNodeService().deleteNode(node.getNodeRef());
            }
        }
        return outcome;
    }
}

As I can see, it'll be much better to implement some transactions inside this method to 'do some atomicity' 8-). VcfNode is my Node extension, I'll try to describe it in next blogpost.

So, it's pretty easy to workaround system dialogs thanks to Alfresco source code. But sometimes you can find things, which looks like there is no source in sdk 🙁 but google always helps!

Customizing Simple Search

0

Some time ago I made customized advanced search results view – no two boxes like in standard Alfresco, but three boxes – the third one for viewing special content type. Now I wanted to add this results screen as a default result of searching via the simple search box.

When I firstly saw r:simpleSearch component and no documentation, I wasn't happy. But after while I realized that it's very simple, thanks to actionListener parameter on that component:

 <r:simpleSearch id="search" actionListener="#{BrowseSessionBean.search}" />

This component creates that very known search box

Image Hosted by ImageShack.us

That action listener, search, is defined in BrowseBean, but I did my own (I copied and edited it :-)). All what this listener does is just setting Search Context and then redirects to results page – returns adequate outcome string to faces navigator.

 public void search(ActionEvent event){
        UISimpleSearch search = (UISimpleSearch)event.getComponent();
        this.navigator.setSearchContext(search.getSearchContext());
        
        FacesContext context = FacesContext.getCurrentInstance();
        context.getApplication().getNavigationHandler().handleNavigation(context, null, "browseSearchResults");
    }

 Navigation rule in faces config navigates in this case from everywhere to my own search results page thanks to this rule:

<navigation-rule>
        <from-view-id>/jsp/*</from-view-id>
        <navigation-case>
            <from-outcome>browseSearchResults</from-outcome>
            <to-view-id>/jsp/archivace/view-search-results.jsp</to-view-id>
        </navigation-case>
</navigation-rule>

That's all, view-search-results is up to you 🙂

Playing with titlebar and navigator II

0

In last blogpost I wrote about titlebar and menu in it. Also I promised some navigator hacking, which is more complex (and crazy has many javascript code) and amazing, so I decided to write whole blogpost on it. Here we are go!

Navigator jsp is pretty simple, but it's not plus for us, because whole navigator is a component – r:navigator. That should not be a problem, but this component has no documentation and what is much worse, this component's aim is just to view that simple menu with Guest home, Alfresco home, My home and Company home. Nothing less, nothing more, no editings.So what to do with that crap? Let's do some hacking! You can see my result at following picture, I added one more link:

 

Image and video hosting by TinyPic    Image and video hosting by TinyPic

 

Navigator.jsp is in /jsp/sidebar/ and at start there is just that component:

 <r:navigator id="navigator" activeArea="#{NavigationBean.toolbarLocation}" />

 This component renders ahref links inside divs, like this:

<div id="navigator">
      <div><a href="foo">bar</a></div>
      <div><a href="bar">foo</a></div>
          . . .
</div>

So I need to insert my own container with my link (both styled some way). ActionLink doesn't take 'class' attribute, so I need to inject that attribute with simple javascript:

 <a:booleanEvaluator value="#{BrowseSessionBean.isArchivaceExist}">
    <f:verbatim>
        <div id="navigatorArchivaceBox" class="sidebarButton" style="background-image: url(/alfresco/images/parts/navigator_grey_gradient_bg.gif);">
    </f:verbatim>
    <a:actionLink id="navigatorArchivaceLink" value="Archivace" href="#{BrowseSessionBean.archivaceUrl}"/>
    <f:verbatim>
            <script type="text/javascript">
                   var link = document.getElementById("navigatorArchivaceLink");
                   link.setAttribute("class","sidebarButtonLink");
            </script>
        </div>
    </f:verbatim>
</a:booleanEvaluator>

I personally hate that opening div inside verbatim tag and also that closing div, but what I can do else :-/. So this simple hack added my own link into navigator menu, it's clickable, it's styled, that link works, but that box is not selectable. After deploy this probably look like left picture a few rows higher. All that code is placed inside evaluator, because I need to have destination space accessible, to display this link in menu.

So next thing to do is to deselect all boxes and all links after 'my link' is clicked and also select my box and my link. Sounds simple, but it isn't thanks to 'My Alfresco' link. I'm doing all this stuff only when I'm at the right place (my Archivace space), so I need evaluator:

<a:booleanEvaluator value="#{BrowseSessionBean.isInsideArchivace}">
<f:verbatim>

Inside this evaluator I have simple javascript, which goes through elemnts under navigator container – links and divs – and for each set 'unselected' style:

<script type="text/javascript">
    // disabling 'selected' style at all links
    var links = document.getElementById("navigator").getElementsByTagName('a');
    for(var i=0; i<links.length; i++){
        links[i].setAttribute("class","sidebarButtonLink");
    }

    // disabling 'selected' style at all boxes
    var links = document.getElementById("navigator").getElementsByTagName('div');
    for(var i=0; i<links.length; i++){
        links[i].setAttribute("class","sidebarButton");
        links[i].setAttribute("style","background-image: url(/alfresco/images/parts/navigator_grey_gradient_bg.gif)");
    }

Now it's time to set 'selected' style just for my (selected) link. I know its ids, so I can access them directly through getElementById:

document.getElementById("navigatorArchivaceBox").setAttribute("style","background-image: url(/alfresco/images/parts/navigator_blue_gradient_bg.gif)");
document.getElementById("navigatorArchivaceLink").setAttribute("class","sidebarButtonSelectedLink");

And now all I have to do is 'just' to create evaluator – isInsideArchivace. That is not so hard – just getting current node and asking, if it has wanted aspect ;-).

public boolean getIsInsideArchivace(){
    if(this.getCurrentLocation()==null)return false;
    return this.navigator.getCurrentNode().hasAspect(cz.shmoula.ArchivaceModel.archSummary);
}

Now it's all doomed to work, like on first and second image :-). BUT – 'My Alfresco' is not an ordinary space, it's some virtual space or something, so when I left my 'Archivace' space by clicking on 'My Alfresco', My Alfresco screen is opened, but selected option is still 'Archivace'. What to do? Look at breadcrumb, because when there is node, it's ordinary space. But when there's nothing, it's probably 'My Alfresco' space (if not, it doesn't matter, because it is not my 'Archivace' space) and it's neccessarynot to display 'Archivace' as selected node. Soall that is inside that getCurrentLocation, which fragments are stolen somewhere from Alfresco sources:

 public String getCurrentLocation(){
        List<IBreadcrumbHandler> location = this.navigator.getLocation();
        String name = null;
          if (location.size() != 0){
              for (int i=0; i<location.size(); i++){
                 IBreadcrumbHandler element = location.get(i);
                 if (element instanceof IRepoBreadcrumbHandler){
                    NodeRef nodeRef = ((IRepoBreadcrumbHandler)element).getNodeRef();
                    name = Repository.getNameForNode(this.getNodeService(), nodeRef);
                 }
              }
          }
          return name;
    }

At this time it's all and it works! Ugly and hacky solution, but it works (FF3). Maybe it'll be better to write own component (eg. some tree browsing component), but now I wanted some simple and fast (and really ugly). Oh, I almost forget – this thread helped me a lot!

Playing with titlebar and navigator

0

My next quest was to change content of titlebar and navigator box. Titlebar was really easy, but navigator box is 'hell itself', because it's r:navigator component and i didn't want to create another component just for adding one more line.

Titlebar editing is very easy. Final result is on following screenshot:

Image and video hosting by TinyPic

That topmenu is created by a styled a:modeList component with some listItems. ModeList has actionListener set and after listItem is clicked, its value is sent to that listener (which is a big switch). So my first task was to bridge that listener. Original code is placed in NavigationBean and that method is named toolbarLocationChanged. I wrote my own eventListener in 'someBean' this way:

public void toolbarLocationChanged(ActionEvent event){
     UIModeList locationList = (UIModeList)event.getComponent();
     String location = locationList.getValue().toString();
     if(location.equals("archivace")){
      . . .
      }else this.navigator.processToolbarLocation(location, true);

In this piece of code is loaded 'location' – value of listItem (ie companyhome, userhome, archivace… see later) and is compared with identificator of my action.If equals, something happens (see next listing ;-)), else original processToolbarLocation routine in NavigationBean is called.

That something piece of code, which happens in that case is creation of breadcrumb and some informations in NavigationBean are changed, follows here:

List<IBreadcrumbHandler> elements = new ArrayList<IBreadcrumbHandler>(1);
Node archivace = new Node(getArchivaceRef());
elements.add(new NavigationBreadcrumbHandler(archivace.getNodeRef(), archivace.getName()));
this.navigator.setLocation(elements);
this.navigator.setCurrentNodeId(archivace.getId());

But that is not all, you have to inform registered beans that the current area has changed:

UIContextService.getInstance(FacesContext.getCurrentInstance()).areaChanged();

And because the current node is not added to the dispatch context automaticaly, it's needed to add it programaticaly (this was very old bug of Alfresco and in some cases still lasts – forum, jira):

 this.navigator.setupDispatchContext(this.navigator.getCurrentNode());

Last thing to do is to force a navigation to refresh the browse screen breadcrumb:

FacesContext context = FacesContext.getCurrentInstance();
 context.getApplication().getNavigationHandler().handleNavigation(context, null, "browse");

 That's all java coding. Now some jsp: you have to edit file titlebar.jsp in /jsp/parts/ and change actionListener in a:modeList and add another a:listItem as seen on next listing (simplified):

<a:modeList . . . actionListener="#{SomeBean.toolbarLocationChanged}">
      <a:listItem value="archivace" label="Archivace" rendered="#{SomeBean.isArchivaceExist}"/>
</a:modeList>

Ah, I see there is evaluator – isArchivaceExist. It's just an evaluator, which returns true/false – if space I want to view exists, so that listItem is viewed just in case of that space exists.

 

As I can see, it'll be better to move navigator hacks to next blogpost.

Go to Top