How to tell if my DHCP ipaddress has changed

A line of script to send you an email if your DHCP allocated IP address has changed.

Either run it from a cron or put it in /etc/rc.local

ls -rt /var/lib/dhcp/dhclient*eth0* | xargs grep fixed-address | tail -2 | awk ‘{print $3}’ | xargs echo -n | sed -e ‘s/;//g’ | awk ‘{if ($1 != $2) { print $2}}’ | mail me@example.org -E’set nonullbody’ -s “My new IP”

Posted in Linux, Ubuntu | Leave a comment

Spring Roo ConverterNotFound

In webmvc-config.xml look for

<bean class="org.wwarn.cb.web.ApplicationConversionServiceFactoryBean" id="applicationConversionService"/>

This will give you the name of the class to edit so go and find that class and add the following code:

Note that the installFormatters method is deprecated in 3.1 but as that is what Roo is currently generating I’m leaving it alone

public Converter getStudyConverter() {
        return new Converter() {
            public String convert(ChassisStudies source) {
                return source.getStudyId();
            }
        };
    }
	@Override
	protected void installFormatters(FormatterRegistry registry) {
		super.installFormatters(registry);
		// Register application converters and formatters
		registry.addConverter(getStudyConverter());
	}
Posted in java, Uncategorized | Leave a comment

MySQL performance problems

I wrote a stored procedure on MySQL that is using a cursor to update a table of about 45,000 rows.

The details what it is doing is are unimportant but the logic is very simple.

The procedure was running extremely slowly (of the order of 1/10 th second per operation) – the steps below describe what I did to make sure that the procedure ran reasonably quickly.

Taking a look in mysql.log I saw the message:

120329 10:34:48 InnoDB: ERROR: the age of the last checkpoint is 9433926,
InnoDB: which exceeds the log group capacity 9433498.
InnoDB: If you are using big BLOB or TEXT rows, you must set the
InnoDB: combined size of log files at least 10 times bigger than the
InnoDB: largest such row.

This leads me to changing my.ini to add (or change) the value of innodb_log_file_size
(Should really read the manual….)


innodb_log_file_size = 64M

You then need to shutdown the server and (re)move the existing log files(ib_logfile0 and ib_logfile1) before starting again with the new values
Official docs
Another value that looks to be recommended to change is innodb_buffer_pool_size and while I was there I changed some other values:

innodb_open_files = 512
#innodb_data_file_path=ibdata4:2G:autoextend:max:4G
innodb_buffer_pool_size = 512M
#innodb_buffer_pool_size = 4G
innodb_additional_mem_pool_size = 512M
innodb_log_file_size = 64M
innodb_log_buffer_size = 8M
innodb_thread_concurrency = 8
innodb_concurrency_tickets = 500
innodb_lock_wait_timeout=200
#Not windows
#innodb_flush_method = O_DIRECT
innodb_autoinc_lock_mode= 2
innodb_commit_concurrency=4
innodb_flush_log_at_trx_commit=2
innodb_support_xa=false
innodb_checksums=0
innodb_doublewrite=0
innodb_max_dirty_pages_pct=15
#innodb_io_capacity = 10000
#innodb_adaptive_checkpoint = 1
#innodb_write_io_threads = 8
#innodb_read_io_threads = 8

 

Other tips

innotop is useful to see what is happening

If there are (slow) temporary tables being created (EXPLAIN Using where; Using temporary; Using filesort ) then try:

mount a tmpfs system on an empty directory (you should also add this to fstab):
mount tmpfs /tmpfs -t tmpfs
and edit my.cnf to make MySQL use that directory as a temporary directory:
tmpdir = /tmpfs

Posted in mysql | Leave a comment

Alfresco CMIS host name

The problem we’ve been having is to do with our Alfresco server name when using CMIS.

We are running our Alfresco instance behind an Apache server (on a different box) and the default configuration means that when we make a call to the CMIS server all the links returned have the link to the Alfresco host which we can’t follow as it’s behind a firewall.

ApacheHost.org -> AlfrescoHost.org

e.g.  Make a call to https://ApacheHost.org/alfresco/service/cmis and all the links come back with AlfrescoHost.org as part of the URL

The solution described here Alfresco JIRA issue is as follows:

Add to the file

tomcat/shared/classes/META-INF/spring-webscripts-config-custom.xml

(You may need to create the file)

 <alfresco-config>

    <config evaluator="string-compare" condition="Server">
       <server>
          <scheme>http</scheme>
          <hostname>your-public-host</hostname>
          <port>your-port</port>
          <path>alfresco</path>
       </server>
    </config>

 </alfresco-config>

 

If you haven’t done it already then it’s probably worth adding/changing the following in alfresco-global.properties as well

alfresco.context=alfresco
alfresco.host=${localname}
alfresco.port=8080
alfresco.protocol=http

share.context=share
share.host=${localname}
share.port=8080
share.protocol=http
Posted in alfresco, cmis | Leave a comment

Solar panels – six months in

Following on from the first quarterly report I’ve moved the boundaries slightly so roughly longest day to shortest day gives us 1,125 kWh – this compares with our estimated generation of 2,300 kWh which it has to be said is a pretty good estimate so far.

A rough calculation seems to indicate that our comsumption is down by around 2kWh per day in the period September to December which seems quite good (11 to 9)

 

Reading Date Reading Incremental Value kWh End Date
26-12-11 1338.6 6 02-01-12
19-12-11 1331.3 7 26-12-11
12-12-11 1314 17 19-12-11
05-12-11 1298.1 16 12-12-11
28-11-11 1285 13 05-12-11
21-11-11 1270 15 28-11-11
14-11-11 1257.9 12 21-11-11
07-11-11 1248.6 9 14-11-11
31-10-11 1227.5 21 07-11-11
24-10-11 1200.2 27 31-10-11
17-10-11 1161 39 24-10-11
10-10-11 1118.3 43 17-10-11
03-10-11 1082.1 36 10-10-11
26-09-11 1013.1 69 03-10-11
19-09-11 972.7 40 26-09-11
12-09-11 912.7 60 19-09-11
05-09-11 874.6 38 12-09-11
29-08-11 818.8 56 05-09-11
22-08-11 760.3 58 29-08-11
15-08-11 702.1 58 22-08-11
08-08-11 639.1 63 15-08-11
01-08-11 565.9 73 08-08-11
25-07-11 491.1 75 01-08-11
18-07-11 426.6 64 25-07-11
11-07-11 350.6 76 18-07-11
04-07-11 277.9 73 11-07-11
27-06-11 214.5 63 04-07-11
20-06-11 132.7 82 27-06-11
13-06-11 53.1 80 20-06-11
10-06-11 31.2 22 13-06-11
Posted in Uncategorized | Leave a comment

XForms(Orbeon), REST, CMIS and Alfresco – part 2 Files

To keep things simple this example uses basic authentication – to see how to handle authentication in more detail see part 1 here

This is a relatively straightforward example of how to upload a file into Alfresco from Orbeon using CMIS over REST.

I’m not going to cover the details of how the REST interface works (as usual POST for create, PUT for update) or go into depth about how to use CMIS as this is covered elsewhere e.g. ECM Architect article on CMIS

Starting from the standard example of how to upload a file in Orbeon the changes to make are:

  • Instead of posting a multi-part mime message by using the method form-data-post use a normal post and encode the file using xxforms:doc-base64
  • Use the test: (echo: in 3.9) action – after this has been done the file will be uploaded to the server in a temporary directory
  • Create a second submission to post the file to Alfresco

An alternative way to encode the file would be to use the technique described here with a serialized element replacing the media element. This is simpler but less instructive.

For the second submission I use a template file containing a blank CMIS entry and fill in the appropriate fields, you can populate any aspect data fields here. I’ve either used xforms:setvalue or xslt as part of an xpl pipeline depending on what I’m doing – here I’m keeping it simple and just using setvalue.

The contents of the xforms:dispatch element are used to control the parameters to set as part of the upload submission (you’ll see that the file is sent to a test directory in this example) – there are a very limited set of parameters here but it’s easy enough to add more.

(I haven’t done any error handling although you can see what to do using the ins-cmis-rest-control instance)

Gotchas

  • According to the atom spec the contents of the atom:content are treated differently according to the value of the type attribute – it’s therefore safer to use cmisra:content instead
  • If there’s an error in Alfresco, e.g. trying to create two files with the same name, then Alfresco will return text/html not text/xml even if you ask it nicely.

It’s worth noting that if you try and POST the same file twice you will get a 500 response – if you try and upload the same file twice via Share then you get a second file with the suffix (1)

This covers the basics of how to upload a file from Orbeon to Alfresco using the CMIS REST interface.

Updating a file, using PUT or deleting are fairly straightforward after this. (Note that the metadata and file contents are different things so are updated separately.)

Enough talking – here’s the example

 <html xmlns:xforms="http://www.w3.org/2002/xforms"
    xmlns:f="http://orbeon.org/oxf/xml/formatting"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xxforms="http://orbeon.org/oxf/xml/xforms"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
    xmlns:ev="http://www.w3.org/2001/xml-events"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
    xmlns:xi="http://www.w3.org/2001/XInclude"
      xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200908/"
    xmlns:cmisra="http://docs.oasis-open.org/ns/cmis/restatom/200908/"
    xmlns:alf="http://www.alfresco.org">

<head>

<title>Spike Upload</title>

<xforms:model id="cmis-rest-model">
    <xxforms:variable name="alfresco-uri"
        select="xxforms:property('alfresco.uri')"
        as="xs:anyURI" />
    <xxforms:variable name="alfresco-username"
        select="xxforms:property('alfresco.username')"
        as="xs:string" />
    <xxforms:variable name="alfresco-credentials"
        select="xxforms:property('alfresco.credentials')"
        as="xs:string" />
    <xforms:instance id="ins-cmis-rest-control">
        <control>
            <status />
        </control>
    </xforms:instance>

    <xforms:instance id="ins-cmis-rest-create-file">
        <xi:include href="create-file.xml" xxi:omit-xml-base="true" />
    </xforms:instance>
    <xforms:instance id="ins-cmis-rest-create-file-result">
        <atom:entry />
    </xforms:instance>
    <xforms:instance id="ins-upload">
        <upload xmlns="">
            <summary />
            <media filename="" mediatype="" size=""
                                            xsi:type="xs:anyURI" />
            <category>term="foo"; scheme="bar"; label="baz"</category>
        </upload>
    </xforms:instance>

    <xforms:instance id="ins-upload-template">
        <upload xmlns="">
            <summary />
            <media filename="" mediatype="" size=""
                                            xsi:type="xs:anyURI" />
            <category>term="foo"; scheme="bar"; label="baz"</category>
        </upload>
    </xforms:instance>

    <xforms:submission id="sub-background-upload"
        ref="instance('ins-upload')" method="post" replace="none"
        resource="test:" validate="false" />

    <xforms:submission
                id="sub-form-data-post"
                method="post"
                action="test:"
                ref="instance('ins-upload')"
                replace="instance"
                instance="ins-post-response">

        <xforms:header>
            <xforms:name>Accept</xforms:name>
            <xforms:value>application/atom+xml</xforms:value>
        </xforms:header>

        <xforms:action ev:event="xforms-submit-done">
            <xxforms:variable name="media-entry"
                                   select="instance('ins-upload')"/>
            <xxforms:variable name="file-uri"
                             select="instance('ins-upload')/media"/>

            <xforms:dispatch target="cmis-rest-model"
                                       name="cmis-rest-upload-file">
                <xxforms:context name="fr:media-entry"
                                             select="$media-entry"/>
                <xxforms:context name="fr:media-title"
                            select="$media-entry//media/@filename"/>
                <xxforms:context name="fr:media-type"
                         select="$media-entry//atom:content/@type"/>

                <xxforms:context name="fr:file-content"    
                          select="xxforms:doc-base64($file-uri)" />

            </xforms:dispatch>
            <xforms:insert nodeset="instance('ins-upload')"
                         origin="instance('ins-upload-template')" />
            <xforms:send submission="sub-get-feed" />
        </xforms:action>
    </xforms:submission>

    <xforms:instance id="ins-post-response">
        <foo xmlns="" />
    </xforms:instance>

    <xforms:submission id="sub-get-feed" method="get"
        instance="ins-feed" serialization="none"
        resource="{$alfresco-uri}service/cmis/p/test/children"
        mediatype="application/atom+xml"
        xxforms:username="{$alfresco-username}"
        xxforms:password="{$alfresco-credentials}" replace="instance">
    </xforms:submission>

    <xforms:instance id="ins-feed">
        <atom:feed />
    </xforms:instance>

    <xforms:send ev:event="xforms-model-construct-done"
        submission="sub-get-feed" />

    <xforms:action ev:event="cmis-rest-upload-file">
        <xxforms:variable name="media-type"
                                   select="event('fr:media-type')"/>
        <xxforms:variable name="content"
                                 select="event('fr:file-content')"/>
        <xforms:delete
            nodeset="instance('ins-cmis-rest-create-file')/atom:entry/*" />
        <xforms:insert
            context="instance('ins-cmis-rest-create-file')/atom:entry"
            origin="instance('ins-cmis-rest-create-file-template')/atom:entry/*"
            at="last()" position="after" />

        <xforms:setvalue
            ref="instance('ins-cmis-rest-create-file')//atom:title"
            value="event('fr:media-title')" />

        <xforms:setvalue
            ref="instance('ins-cmis-rest-create-file')
                                     //cmisra:content/cmisra:base64"
            value="$content" />

        <xforms:setvalue
            ref="instance('ins-cmis-rest-create-file')
                                  //cmisra:content/cmisra:mediatype"
            value="$media-type" />

        <xforms:setvalue
            ref="instance('ins-cmis-rest-create-file')
//cmis:propertyString
    [@propertyDefinitionId='cmis:contentStreamMimeType']/cmis:value"
            value="$media-type" />

        <!-- You can set any values you like here
               e.g. a custom document type or aspect properties -->
        <xforms:send submission="cmis-rest-create-file-submission" />

    </xforms:action>
    <xforms:submission ref="instance('ins-cmis-rest-create-file')"
        id="cmis-rest-create-file-submission" method="post"
        action="{$alfresco-uri}service/cmis/p/test/children"
        mediatype="application/atom+xml"
        xxforms:username="{$alfresco-username}"
        xxforms:password="{$alfresco-credentials}" replace="instance"
        instance="ins-cmis-rest-create-file-result">
        <!-- This doesn't actually help for errors,
                    e.g. 2 files with the same name, as Alfresco
              always returns a 500 with Content-Type: text/html  -->
        <xforms:header>
            <xforms:name>Accept</xforms:name>
            <xforms:value>text/xml</xforms:value>
        </xforms:header>
        <xforms:action ev:event="xforms-submit-error">
            <xforms:setvalue
                ref="instance('ins-cmis-rest-control')//status"
                value="'fail'" />
            <xforms:message level="modal"
                value="concat('An error occurred while creating
                 a file in Alfresco. Please inform an administrator.
                         (error-type:',event('error-type'), ')')" />
        </xforms:action>
        <xforms:action ev:event="xforms-submit-done">
    <xforms:setvalue ref="instance('ins-cmis-rest-control')//status"
                value="'ok'" />
        </xforms:action>
    </xforms:submission>
</xforms:model>

</head>
<body>

    <h1>Spike Upload</h1>

    <xforms:group ref="instance('ins-upload')">

        <p>

            <!-- Upload field -->
            <xforms:upload ref="media" xxforms:size="60">
                <xforms:label>Upload file: </xforms:label>
                <xforms:filename ref="@filename" />
                <xforms:mediatype ref="@mediatype" />
                <xxforms:size ref="@size" />
            </xforms:upload>

            <xforms:output value="media" appearance="xxforms:download">
                <xforms:label>Download</xforms:label>
                <xforms:filename ref="@filename" />
                <xforms:mediatype ref="@mediatype" />
            </xforms:output>

        </p>

        <p>
            <xforms:textarea ref="summary">
                <xforms:label>Summary: </xforms:label>
            </xforms:textarea>
        </p>

    </xforms:group>

    <p>
        <xforms:submit submission="sub-form-data-post">
            <xforms:label>Submit (form-data-post)</xforms:label>
        </xforms:submit>
    </p>

    <hr />

    <fr:datatable paginated="false">
        <thead>
            <tr>
                <th fr:sortable="true" fr:resizeable="true">File Name</th>
                <th fr:sortable="true" fr:resizeable="true">Published</th>
                <th fr:sortable="true" fr:resizeable="true">Author</th>
                <th>Summary</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <xforms:repeat id="rep-media"
                nodeset="instance('ins-feed')/atom:entry">
                <tr>
                    <td><xforms:output ref="atom:title" /></td>
                    <td><xforms:output ref="atom:published"
                            xxforms:format="format-dateTime(., '[Y0001]-[M01]-[D01]')" /></td>
                    <td><xforms:output ref="atom:author/atom:email" /></td>
                    <td><xforms:output ref="atom:summary" /></td>
                    <td></td>
                </tr>
            </xforms:repeat>
        </tbody>
    </fr:datatable>

</body>
</html>

The template file:

 <?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
    xmlns:cmisra="http://docs.oasis-open.org/ns/cmis/restatom/200908/"
    xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200908/"
    xmlns:alf="http://www.alfresco.org">
    <title>sample-a.doc</title>
    <summary>A sample whitepaper named Sample A</summary>
    <cmisra:content>
        <cmisra:mediatype></cmisra:mediatype>
        <cmisra:base64></cmisra:base64>
    </cmisra:content>
    <cmisra:object>
        <cmis:properties>
            <cmis:propertyId
                propertyDefinitionId="cmis:objectTypeId">
                         <cmis:value>cmis:document</cmis:value>
            </cmis:propertyId>
            <cmis:propertyString
                propertyDefinitionId="cmis:contentStreamMimeType">
                     <cmis:value>text/xml</cmis:value>
            </cmis:propertyString>
            <alf:setAspects>
                <alf:appliedAspects>P:cm:titled</alf:appliedAspects>
                <alf:appliedAspects>P:rn:renditioned</alf:appliedAspects>
                <alf:appliedAspects>P:cm:taggable</alf:appliedAspects>
                <alf:appliedAspects>P:cm:templatable</alf:appliedAspects>
                <alf:appliedAspects>P:cm:author</alf:appliedAspects>
                <alf:appliedAspects>P:app:inlineeditable</alf:appliedAspects>
                <alf:properties>
                    <cmis:propertyString
                            propertyDefinitionId="cm:author"
                            displayName="Author"
                            queryName="cm:author">
                           <cmis:value></cmis:value>
                     </cmis:propertyString>
                    <cmis:propertyString
                            propertyDefinitionId="cm:title"
                            displayName="Title"
                            queryName="cm:title">
                           <cmis:value></cmis:value>
                    </cmis:propertyString>
                </alf:properties>
            </alf:setAspects>
        </cmis:properties>
    </cmisra:object>
</entry>

 

Posted in alfresco | Leave a comment

XForms (Orbeon), CAS and CMIS (Alfresco) – Part 1 – Authentication

I thought I’d write about my experience of using the Alfresco CMIS interface as a backend to a custom XForms application.
There’s a natural fit here as the atom based syntax of CMIS fits very nicely with XForms however there are a few little wrinkles to work through.

Authentication

The first issue to decide on is how to handle the authentication of the CMIS service – this will very much depend on your application requirements and architecture. As you will see from earlier posts we are running behind CAS with both our Alfresco and Orbeon apps using CAS authentication (Orbeon via Spring Security).

Due to the application requirements I am using two different authentication strategies – a well known generic user and proxy authentication using the logged in user.

Basic Auth

This is the easiest to set up and use

The way I’ve done this is to define some configuration properties to set the user name etc and assign these to variables within the model

 

<xxforms:variable name="alfresco-uri"
     select="xxforms:property('chassis.alfresco.uri')"
     as="xs:anyURI"/>
<xxforms:variable name="alfresco-username"
     select="xxforms:property('chassis.alfresco.username')"
     as="xs:string"/>
<xxforms:variable name="alfresco-credentials"
     select="xxforms:property('chassis.alfresco.credentials')"
     as="xs:string"/>
<xxforms:variable name="is-send-alfresco"
     select="xxforms:property('chassis.alfresco.send')"
     as="xs:boolean"/>

Retrieving information is then via a straightforward submission using xxforms:username and xxforms:password (obviously you need to set the path of the action appropriately)

<xforms:submission id="cmis-rest-get-file-record" method="get"
  action="{$alfresco-uri}service/cmis/p/User Homes/TestUser/children"
  mediatype="application/atom+xml"
  xxforms:username="{$alfresco-username}"
  xxforms:password="{$alfresco-credentials}"
  replace="instance"
  instance="ins-cmis-rest-create-file"
  if="$is-send-alfresco"
  serialization="none"/>

This uses basic auth which will work with an out of box Alfresco but does have the limitations of basic auth i.e. it’s not very secure unless you use https

Proxy Auth

First thing to note in following on is that this has to be done using https as otherwise CAS won’t like it.

The complication here is that the requests to the Alfresco server are being sent from Orbeon not directly from the user so although the user is logged into both Orbeon and Alfresco the CMIS requests will not be authenticated.

The way I’ve chosen to do this is to implement a servlet filter to obtain an Alfresco authentication ticket which can then be passed through to Orbeon as part of the request and appended to the CMIS request.

You will see here that the code uses HttpClient 3.1 (because I’m still using Orbeon 3.8) but it should be fairly trivial to upgrade.

I also use JNDI to retrieve the name of the Alfresco service e.g. <Environment name=”alfrescoApp” type=”java.lang.String” value=”https://alfresco/alfresco”/> – you may want to do this differently.

package org.aelfric.demo.security;

import java.io.IOException;

import javax.naming.NamingException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author iwright
 *
 */
public class AlfrescoCASFilter implements Filter {

  private Log log = LogFactory.getLog(this.getClass());

  public static final String ALFRESCO_TICKET = "alf_ticket";

  public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                       throws IOException, ServletException {
    if (request instanceof HttpServletRequest &&
                        response instanceof HttpServletResponse) {
      doHttpFilter((HttpServletRequest)request,
                        (HttpServletResponse)response, chain);
    } else {
      throw
        new ServletException("only HTTP request and responses are
                                         supported by this filter");
    }
  }

  @Override
  public void doHttpFilter(HttpServletRequest request,
    	           HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    log.debug("request inbound");

    HttpClient client = new HttpClient();
    String ticket = null;
    try {
	ticket = AlfrescoCASFilter.getAlfrescoTicket(request, client);
    } catch (NamingException e) {
	log.error("Need to set JNDI variable alfrescoApp if using Alfresco", e);
    }
    if (ticket != null) {
	request.setAttribute(ALFRESCO_TICKET, ticket);
    }
    log.debug("alfresco ticket:" + ticket);
    chain.doFilter(request, response);

    log.debug("response outbound");
  }

  public static String getAlfrescoTicket(HttpServletRequest req,
                                                 HttpClient client)
            throws UnsupportedEncodingException, IOException, HttpException,
            ServletException, NamingException {

    HttpSession httpSess = req.getSession(true);

    // Get CAS information
    Assertion assertion = (Assertion) httpSess
                .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
    if (assertion == null) {
       return "";
    }
    String username = assertion.getPrincipal().getName();
        // Read out the ticket id
    String ticket = null;
    String alfrescoWebAppURL = LookupJNDI.<String> getEnvEntry(ALFRESCO_WEBAPP_URL_CONFIG);
    if (alfrescoWebAppURL == null) {
       return (null);
    }
    String proxyticket = assertion.getPrincipal().getProxyTicketFor(
                alfrescoWebAppURL);

    if (proxyticket == null) {
      return (null);
    }
    String casLoginUrl = alfrescoWebAppURL + "/service/api/logincas?u="
                + URLEncoder.encode(username, "UTF-8") + "&t="
                + URLEncoder.encode(proxyticket, "UTF-8");

    GetMethod method = new GetMethod(casLoginUrl);
    method.setRequestHeader("cookie", req.getHeader("cookie"));
    int statusCode = client.executeMethod(method);
    // Read back the ticket
    if (statusCode == 200) {
      InputStream is = method.getResponseBodyAsStream();
      // do something with the input stream
      BufferedReader in = new BufferedReader(new InputStreamReader(is));
      String line;
      String responseText = "";
      while ((line = in.readLine()) != null) {
        responseText += line;
      }
      in.close();
      ticket = responseText;

   } else {
      if (log.isDebugEnabled()) {
        log.debug("Authentication failed, received response code: "
                        + statusCode);
      }
   }
   method.releaseConnection();
   return ticket;
  }
}

The next step is to configure the servlet filter in your web.xml for any URLs where you want to proxy authenticate.

 <filter-mapping>
        <filter-name>AlfrescoCASFilter</filter-name>
        <url-pattern>/study/*</url-pattern>
    </filter-mapping>

Once you’ve done this then the request will contain an attribute with the Alfresco ticket.

Now for the Orbeon part.

For information I hold cmis-rest as a separate model but I’m trying to simplify the examples by leaving that out.

When the model is contructed the requested attribute is held in a control instance.

<xforms:instance id="ins-cmis-rest-control">
        <control>
            <ticketAuth/>
        </control>
</xforms:instance>

<xforms:instance id="ins-cmis-rest">
        <request/>
</xforms:instance>

<!-- Note that this has to be done as part of the
                              xforms-model-construct-done stage -->
<xforms:action ev:event="cmis-rest-get-ticket">
   <xforms:insert
      nodeset="instance('ins-cmis-rest')"
      origin="xxforms:get-request-attribute('alf_ticket')"/>
   <xforms:setvalue ref="instance('ins-cmis-rest-control')//ticketAuth"
                value="instance('ins-cmis-rest')"/>
</xforms:action>

<xforms:action ev:event="xforms-model-construct-done">
      <xforms:dispatch name="cmis-rest-get-ticket"/>
</xforms:action>

So now when the submission is sent the Alfresco ticket can be appended to the URL

So the equivalent submission but with proxy authentication is:

 <xforms:submission id="cmis-rest-get-file-record" method="get"
       action="
{$alfresco-uri}service/cmis/p/User Homes/
           TestUser/children?alf_ticket={instance('ins-cmis-rest')}"
mediatype="application/atom+xml" replace="instance"
instance="ins-cmis-rest-create-file" serialization="none"/>
Posted in alfresco, cas, cmis, orbeon | 1 Comment

XForms select using an XML Schema (XSD)

As I’m using schema based validation for my XForms (using Orbeon) I thought it would be a nice idea to generate the select options from the xsd (as well as saving some work!).

As an advanced feature we can also select the value for a second select based on the value of the first.

This is a collection of ideas from different places on the Orbeon wiki and mailing list together with my own thoughts to bring it together.

First the set up:

Load the schema into an instance in this case it’s called study-info-resources – note that this needs to be in a directory where it is not protected by any security – not because of this code but so it can be used for validation.

Next we set up a resources file – this is going to be used for the labels – as well as nice labels we also get multi language support. This resources file will be used to define the relationship between the first and second selects.

 <xforms:instance id="study-info-resources"
        src="../../../../../../insecure/schema/study.xsd"/>
    <!--
    In your XForms model, load resources.xml in an instance.
    Make sure to make that instance read-only and cacheable: this
    way the instance will only be stored once in memory
   (it will be shared by all the users) and using a more efficient
   (because read-only) representation in memory.

    xxforms:readonly="true" xxforms:cache="true"
    -->
    <xforms:instance id="all-resources"
        src="/apps/common/resources.xml"/>
    <!--
    NOTE: Here we point to a local file with the oxf: protocol.
    This is usually yields more performance than using http:,
    because oxf: will reach a local file on disk.
    However, in the online version, we use the http:,
    because we want to load the resource from an online server!

    Define an instance used to store the current language,
       e.g. en or fr.
    -->
 <xforms:instance id="language"><language>en</language></xforms:instance>
 <xforms:instance id="iterator"><key></key></xforms:instance>
    <!--
    Define a variable ($resources), which points to <resources>
       for the current language.
    You will use this variable as a shortcut in your view
       to point to specific resources.
        -->
<xxforms:variable name="resources"
select="instance('all-resources')/resource[@xml:lang = instance('language')]"/>

Next write the schema elements

 <xs:simpleType name="tradeName">
     <xs:restriction base="xs:string">
       <xs:enumeration value="" />
       <xs:enumeration value="fred" />
       <xs:enumeration value="bert" />
       <xs:enumeration value="Other" />
     </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name="manufacturer">
   <xs:restriction base="xs:string">
     <xs:enumeration value="" />
     <xs:enumeration value="acme" />
     <xs:enumeration value="ernie" />
     <xs:enumeration value="Other" />
  </xs:restriction>
 </xs:simpleType>

Now we set up the resources to get our labels

 <?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <resource xml:lang="en">
        <tradeName>Trade Name</tradeName>
        <manufacturer>Manufacturer</manufacturer>
        <tradeNames>
                    <tradeName ref="" />
                    <tradeName ref="fred">Frederick</tradeName>
                    <tradeName ref="bert">Albert</tradeName>
                    <tradeName ref="Other">Unlisted</tradeName>
        </tradeNames>
        <manufacturers>            
            <manufacturer ref="" tradeName=""></manufacturer>
            <manufacturer ref="fred" tradeName="fred">acme</manufacturer>
            <manufacturer ref="bert" tradeName="bert">ernie</manufacturer>
            <manufacturer ref="Other" tradeName="Other">Other</manufacturer>
        </manufacturers>
    </resource>
</resources>

Now to write our control – as you can see the itemset is populated by looking at the enumeration values from the schema. Looking up the labels from the resources is slightly trickier (of course you can just use the value from the enumeration as the label and not do this look up)

The xforms-value-changed action is only used for the next part so you can usually leave it out.

<xforms:select1 ref="tradeName" appearance="minimal">
    <xforms:label class="fixed-width" model="mod-study-info"
                                        ref="$resources/tradeName"/>

    <xforms:itemset model="mod-study-info"
        nodeset="instance('study-info-resources')//xs:simpleType[@name='tradeName']/xs:restriction/xs:enumeration">
         <xforms:label ref="for $currentItemName in @value
             return $resources//tradeName[@ref = $currentItemName]"/>
        <xforms:value ref="@value"/>
    </xforms:itemset>
    <xforms:action ev:event="xforms-value-changed">

      <xforms:insert ref="instance('binding-control')//rebuild"
                                               value="something"/>
      <!--  the rebuild is what should make it work however
                           it's actually the previous statement....
      <xforms:rebuild model="mod-study-dashboard" />
      -->
 </xforms:action>
</xforms:select1>

For the final part of this example we are going to create a second select control which is populated based on the value of the first.

Here you can see that if we know the trade name (it’s not ‘Other’) then we can populate the manufacturer based on the mappings held in the resources file. If we don’t know then trade name then this functions just like a normal select box – the only thing necessary to set this up is to create a binding.

The trick to making this work as expected (in Orbeon 3.8) is to use the action specified above – it seems that the insert triggers a model rebuild (if the xforms:rebuild is called then it appears to calculate the binding based on the value of the select prior to the change rather than after the change)

 <xforms:bind nodeset="manufacturer[not(../tradeName = 'Other')]"
        calculate="
            for $currentItemName in ../tradeName
             return
               xxforms:instance('all-resources')//manufacturer[@tradeName = $currentItemName]/@ref
         " />

 

<xforms:select1 ref="manufacturer" appearance="minimal">  
   <xforms:label class="fixed-width" model="mod-study-info"
                                   ref="$resources/manufacturer"/>
   <xforms:itemset model="mod-study-info"
       nodeset="instance('study-info-resources')//xs:simpleType[@name='manufacturer']/xs:restriction/xs:enumeration">
      <xforms:label ref="for $currentItemName in @value
            return $resources//manufacturer[@ref = $currentItemName]"/>
      <xforms:value ref="@value"/>
   </xforms:itemset>
</xforms:select1>

The binding control instance is just a bit bucket used to trigger the rebuild

  <xforms:instance id="binding-control">
        <bc xmlns="">
            <rebuild/>
        </bc>
    </xforms:instance>
Posted in orbeon, xml | Leave a comment

Share CAS logout

I’ve finally had the time to work out how to configure Alfresco Share to successfully log out when using CAS to log in.

If you log out of just CAS you will still be logged into Share and vice versa so you need to log out of both – and just for good measure log out of Alfresco at the same time.

This follows on from Martin Bergljung’s blog on configuring for CAS

(Note this doesn’t do anything about single sign out as that’s a different problem)

The way I’ve done this is to override the logoutController by defined a new java class and referencing it in custom-slingshot-application-context.xml

 <!-- Override Logout Controller - to expire Alfresco tickets -->
   <bean id="logoutController">
      <property name="cacheSeconds" value="-1" />
      <property name="useExpiresHeader"><value>true</value></property>
      <property name="useCacheControlHeader"><value>true</value></property>
      <property name="connectorService" ref="connector.service" />
      <!-- if blank assumes the same as Share -->

      <property name="casHost"><value>https://alfresco</value></property>
      <property name="casPath"><value>sso/logout</value></property>

   </bean>

You’ll see here that I’ve defined a couple to properties to indicate where the CAS log out page lies – this is where the user will be redirected when they log out. This fits quite closely with the method of configuring the CAS server in the web.xml but you could use an alternative approach such as properties.

The java code is very closely based on org.alfresco.web.site.servlet.SlingshotLogoutController however there is one significant difference in that it doesn’t call handleRequestInternal in LogoutController.
This is because LogoutController.handleRequestInternal ends up redirecting to request.getContextPath whereas we want to go to our CAS logout page.

package org.wwarn.cms.authentication.servlet;

import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.UserFactory;
import org.springframework.extensions.surf.mvc.LogoutController;
import org.springframework.extensions.surf.site.AuthenticationUtil;
import org.springframework.extensions.surf.support.AlfrescoUserFactory;
import org.springframework.extensions.webscripts.connector.AlfrescoAuthenticator;
import org.springframework.extensions.webscripts.connector.Connector;
import org.springframework.extensions.webscripts.connector.ConnectorContext;
import org.springframework.extensions.webscripts.connector.ConnectorService;
import org.springframework.extensions.webscripts.connector.HttpMethod;
import org.springframework.extensions.webscripts.connector.Response;
import org.springframework.web.servlet.ModelAndView;

/**
 * CAS specific override of the Share specific override of the SpringSurf
 * dologout controller.
 * <p>
 * The implementation ensures Alfresco tickets are removed if appropriate and as
 * it can't delegates to the SpringSurf implementation for framework cleanup
 * does that clean up and then sends a logout to the CAS host
 *
 * @see org.alfresco.web.site.servlet.SlingshotLogoutController
 *
 * @author Ian Wright
 */
public class CASSlingshotLogoutController extends LogoutController {
    private static Log logger = LogFactory
            .getLog(CASSlingshotLogoutController.class);
    private ConnectorService connectorService;
    private String casHost;
    private String casPath;

    /**
     * @param connectorService
     *            the ConnectorService to set
     */
    public void setConnectorService(ConnectorService connectorService) {
        this.connectorService = connectorService;
    }

    @Override
    public ModelAndView handleRequestInternal(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        try {
            HttpSession session = request.getSession(false);
            if (session != null) {
                // retrieve the current user ID from the session
                String userId = (String) session
                        .getAttribute(UserFactory.SESSION_ATTRIBUTE_KEY_USER_ID);

                if (userId != null) {
                    // get the ticket from the Alfresco connector
                    Connector connector = connectorService.getConnector(
                            AlfrescoUserFactory.ALFRESCO_ENDPOINT_ID, userId,
                            session);
                    String ticket = connector.getConnectorSession()
                            .getParameter(
                                    AlfrescoAuthenticator.CS_PARAM_ALF_TICKET);

                    if (ticket != null) {
                        // if we found a ticket, then expire it via REST API -
                        // not all auth will have a ticket i.e. SSO
                        Response res = connector.call("/api/login/ticket/"
                                + ticket, new ConnectorContext(
                                HttpMethod.DELETE));
                        if (logger.isDebugEnabled())
                            logger.debug("Expired ticket: " + ticket
                                    + " user: " + userId + " - status: "
                                    + res.getStatus().getCode());
                    }
                }
            }
        } finally {
            AuthenticationUtil.logout(request, response);
            String target = request.getContextPath();
            if (casHost != null && casHost.length() > 0) {
                target = casHost;
            } else {
                URL reconstructedURL = new URL(request.getScheme(),
                        request.getServerName(),
                        request.getServerPort(),
                        "");
                target = reconstructedURL.toExternalForm();
            }
            if (casPath != null && casPath.length() > 0) {
                target += '/' + casPath;
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Logout to:" + target);
            }
            response.sendRedirect(target);
        }

        return null;
    }

    /**
     * @param casHostValue
     *            the casHost to set - defaults to the same as Share
     */
    public void setCasHost(String casHostValue) {
        casHost = casHostValue;
    }

    /**
     * @param casPathValue
     *            the location of the CAS logout servlet
     */
    public void setCasPath(String casPathValue) {
        casPath = casPathValue;
    }
}

 

Posted in alfresco, cas | Leave a comment

Solar panels – the first quarter

So we’ve had our solar panels installed for three months and inspired by my friend SomeBeans I thought I’d write a blog post to comment on the experience.

We had our panels installed by Joju (a link to us as a case study here) in early June – just about when the sunny weather stopped and we started the least sunny summer for 18 years. Our estimated generation is 2,300 kWh each year so a fraction over 900 kWh for what should be the sunniest months of the year is slightly disappointing – hopefully better weather would lead to another 10% or so but then when do we ever have a sunny summer….

Because of the shape and configuration of our roof we ended up with installing some high end Sanyo panels which gives us a total installed capacity of 2.88 kWh – basically these panels are a bit smaller so we could fit more on the back roof and if we wanted to put some cheaper ones on the other then we would need two inverters which would be more expensive anyway.
(The highest number I’ve spotted being generated is 2,798W)

Our roof faces roughly SW which means that the panels don’t start working properly until after 10 in the morning but then keep going after that.

The installation cost about £13,000 and we’ve just had a cheque for about £400 based on 900kWh. Of course that doesn’t take into account what is saved in consumption
(For ease of comparison we pay roughly 20p per kWh for first 225kWh per quarter followed by 10p – interestingly we get paid 3.1p for each kWh for the export tariff)
The estimate is half used so on that basis it’s a further £45 – I suspect it’s more than that however as we are making an effort to do as much as possible when it’s sunny.
All this is tax free.

If we assume an interest rate of 3% over 20 years then our £13,000 would turn into £23,479.45 (that makes no allowance for tax)

Reading Date Reading Incremental Value kWh End Date End Reading
05-09-11 874.6 38 12-09-11 913
29-08-11 818.8 56 05-09-11 875
22-08-11 760.3 58 29-08-11 819
15-08-11 702.1 58 22-08-11 760
08-08-11 639.1 63 15-08-11 702
01-08-11 565.9 73 08-08-11 639
25-07-11 491.1 75 01-08-11 566
18-07-11 426.6 64 25-07-11 491
11-07-11 350.6 76 18-07-11 427
04-07-11 277.9 73 11-07-11 351
27-06-11 214.5 63 04-07-11 278
20-06-11 132.7 82 27-06-11 214
13-06-11 53.1 80 20-06-11 133
10-06-11 31.2 22 13-06-11 53

Changes to our consumption

It’s quite hard to tell because we had a faulty meter last year but saving a third seems like a good guess.

As we don’t have a smart meter (even though it’s less than a year old) the energy company assumes that we consume 50% of our generated power and export the other 50% – this means that we were paid for 450 kWh. What this assumption means is that when we’re generating anything we use is effectively free!

Bill period Number of days Total Daily Average
22/07/11 – 10/09/11 51 343 7
06/06/11 – 21/07/11 46 275 6
22/04/11 – 05/06/11* 45 407 9
31/01/11 – 21/04/11 81 916 11

* We were on holiday for a week during this period

Posted in Uncategorized | 1 Comment