Sunday, December 23, 2012

Mixing Bootstrap with JSP

A few days ago I wrote a blog post about my first contact with Twitter's Bootstrap UI framework. What I showed was how to create a simple static web site which uses java script in order to set a content area. The article can be found here: http://ecmgeek.blogspot.de/2012/12/first-contact-with-bootstrap.html

This article will show how to mix Bootstrap with JSP pages to bring a bit more dynamic into the page. Here some requirements:

  • A simple HTML template which uses Bootstrap should be used to show a web site.
  • A site should have multiple pages. The site navigation should allow to navigate to the pages.
  • It should be possible to navigate through a page by using a simple navigation menu.
  • A page should show some HTML content.
So we can derive the following Java classes :
  • Site: A site has a name and a description. One site is associated to multiple pages.
  • Page: A page has a name, an id and a target URL. It also has exactly one SubMenu and one HTMLContent item.
  • SubMenu: A sub menu has multiple menu items.
  • SubmenuItem: A sub menu item has a  name and a target URL.
  • HTMLContent: Has just a String property to store the HTML. May be used as Base class for other Content items.

 So what can now do is to create a Site instance (which is indeed kept on the server side - and so a servlet container is required to host the application) in order to provide our site template with the required content.

package de.ecmgeek.bootstrap;

public class Demo {

    private Site site;
    
    public Demo() {
       
     site = new Site("Demo", "This is a demo page");
    
     Page home = new Page("Home",new HTMLContent("<b> Home ... </b>"));
     Submenu homeSubMenu = new Submenu();
     SubmenuItem homeItem1 = new SubmenuItem("HomeSection1","#section1");
     SubmenuItem homeItem2 = new SubmenuItem("HomeSection2","#section2");
     SubmenuItem homeItem3 = new SubmenuItem("HomeSection3","#section3");
     homeSubMenu.addItem(homeItem1);
     homeSubMenu.addItem(homeItem2);
     homeSubMenu.addItem(homeItem3);
     home.setSubmenu(homeSubMenu);
    
     Page about = new Page("About",new HTMLContent("<b> About ... </b>"));
     Submenu aboutSubMenu = new Submenu();
     SubmenuItem aboutItem1 = new SubmenuItem("AboutSection1","#section1");
     SubmenuItem aboutItem2 = new SubmenuItem("AboutSection2","#section2");
     SubmenuItem aboutItem3 = new SubmenuItem("AboutSection3","#section3");
     aboutSubMenu.addItem(aboutItem1);
     aboutSubMenu.addItem(aboutItem2);
     aboutSubMenu.addItem(aboutItem3);
     about.setSubmenu(aboutSubMenu);
    
     Page contact = new Page("Contact",new HTMLContent("<b> Contact ... </b>"));
     Submenu contactSubMenu = new Submenu();
     SubmenuItem contactItem1 = new SubmenuItem("ContactSection1","#section1");
     SubmenuItem contactItem2 = new SubmenuItem("ContactSection2","#section2");
     SubmenuItem contactItem3 = new SubmenuItem("ContactSection3","#section3");
     contactSubMenu.addItem(contactItem1);
     contactSubMenu.addItem(contactItem2);
     contactSubMenu.addItem(contactItem3);
     contact.setSubmenu(contactSubMenu);
    
     site.add(home);
     site.add(about);
     site.add(contact);
    
    }
    
    public Site getSite() {
        return site;
    };
}

The class Demo is only used for demonstration purposes. Usually we would use a persistence layer like Hibernate or JPA to read the site data from a database.

The next step is to  change our more or less static web site to use a Site which is provided from the server. For this purposes we use J(ava)S(erver)P(ages). It's just easy to use and to learn and you can access your beans directly via Scriptlets and expressions. Another, the more new school way, would be to export the site data via a RESTFul Web Service which talks J(ava)S(ript)O(bject)N(otation). The advantage of the second way is that you can use the data out of the box after you got it from the server with Java Script without the need to use something like JSP scriptlets to "inject" the whole data into your HTML. However, let's go with JSP here.

So our previous HTML page becomes now a JSP page, which looks like the following one:

<!-- (0) Create an empty HTML page -->
<!DOCTYPE html>
<%@page import="de.ecmgeek.bootstrap.SubmenuItem"%>
<%@page import="de.ecmgeek.bootstrap.Page"%>
<%@page import="de.ecmgeek.bootstrap.Demo"%>
<%@page import="de.ecmgeek.bootstrap.Site"%>
<%@page import="de.ecmgeek.bootstrap.HTMLContent"%>
<html>
    <!-- (1) Some basic header info -->
    <head>
        <meta charset="utf-8">
            <title>BootstrapMeetsJSP</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="description" content="">
            <meta name="author" content="">

        <!-- (2) Import the provided CSS file -->
        <link href="css/bootstrap.min.css" rel="stylesheet" media="screen">    
    </head>
    
    <body>
        <!-- Do some JAVA initialization work -->
        <%
            //Build some demo data
            Site site = new Demo().getSite();
        %>
    
         <!-- (3) Import jQuery and Bootstrap -->
        <script src="js/jquery-latest.js"></script>
        <script src="js/bootstrap.min.js"></script>
        
        <!-- (5) At last add some JavaScript (jQuery) here to fill the page with life -->
        <script>
        
        $(document).ready(function(){
          
            
            //Inject some JAVA code into the JavaScript calls
            <%
              for (Page p : site.getPages())
              {
                      out.println("$('#"+ p.getId()  +"').click(function(){");    
                      out.println("$('div.my-content').empty();");
                      out.println("$('ul.nav-list').empty();");
                      out.println("$('div.my-content').html('"+p.getContent().getHtml()+"');");
                      
                    //Set the content and the submenu    
                    for (SubmenuItem sm : p.getSubmenu().getItems())
                    {
                        out.println("$('ul.nav-list').append('<li><a href=\"" + sm.getTarget() +"\"><i class=\"icon-chevron-right\"></i>"+ sm.getName() +"</a></li>');");
                    
                    }
                    
                    out.println("});");
              }
            %>            
               
         });      
        

        </script>

        
        <!-- (4) Bootstrap decorated HTML here -->
        <div id="container">

           <!-- A navigation header bar which is fixed to the top -->
           <div class="navbar navbar-fixed-top navbar-inverse">
                   <div class="navbar-inner">
                       <a class="brand" href="#"><%=site.getName()%></a>
                       
                       <ul id="header" class="nav">
                       
                           <%
                               for (Page p : site.getPages())
                               {
                                   out.print("<li><a href='"+p.getTarget()+"'id='"+p.getId()+"'>"+p.getName()+"</a></li>");                       
                               }
                           %>
                    
                    </ul>
                         </div>
               </div>
        
             <!-- (4.1) A simple web site header -->        
           <div class="hero-unit">
                <h1><%=site.getName()%></h1>
                <p><%=site.getDesc()%></p>
                <p>
                </p>
               </div>
          

           <!-- (4.2) A HTML grid with 12 columns, in this case we -->
             <div class="row">
            <!-- 4 columns are used for the left hand side navigation bar -->
                <div class="span4 bs-docs-sidebar">
                <ul class="nav nav-list bs-docs-sidenav">
                </ul>
            </div>
                
            <!-- (4.3) 8 columns are used for the right hand side content area -->
            <div class="span8">
                
                <!-- (4.4) The content area which can be decorated by own css -->
                <div class="my-content">
                </div>
            </div>
               </div>
            </div>    
    </body>
</html>
The result looks quite identical to the static page, but with the difference that the site data is now provided from the server to the client.



In summary the following happens:

  • Java code is used to generate the JavaScript code dependent on the site data which is provided by the server
  • The generated JavaScript code is used in order to manipulate the web site's HTML code dependent on the actions (click on the header menu item)
  • The HTML will be rendered on the client side (browser, ...)

It's easy to see that these are the basics to build a very simple W(eb)C(ontent)M(anagement) system which is based on Bootstrap. A next article will follow where I will describe how to add a database connection in order to retrieve the site data from a database. I can even imagine to create a simple Bootstrap based authoring web application which can be used to add data to this database.


Saturday, December 22, 2012

Site tracking with Piwik

I used Piwik in a previous Web Community project. So when I was asked how to add web statistics to Alfresco, my first idea was to integrate it with Piwik. So let's start to try it out:

At first a Piwik installation is required. My target test system is an OpenSuse 12.1 and so all the dependencies are available from the software repository. They are further described here: http://piwik.org/docs/requirements/ .

So after you installed an Apache, all required PHP modules and a MySQL database (BTW: This is something which I do not like regarding Piwik. The only supported database is MySQL. I would like to see at least Postgres support. ) you can begin to install your Piwik instance. Therefore you can follow the following instructions: http://piwik.org/docs/installation/ .

The most interesting part may be the database setup:

mysql> CREATE USER 'piwik'@'localhost' IDENTIFIED BY '${Your pwd here}';
mysql> CREATE DATABASE piwikdb;
mysql> GRANT ALL PRIVILEGES ON piwikdb.* TO 'piwik'@'localhost' WITH GRANT OPTION;


Then unzip the latest Piwik zip to your Apache2 web server and open the URL 'http://localhost/piwik'. For OpenSuse the htdocs folder is located at '/srv/www'.

If calling Piwik the first time it will prompt you to perform the following file permission changes:

chown -R www-data:www-data /srv/www/htdocs/piwik
chmod -R 0777 /srv/www/htdocs/piwik/tmp
chmod -R 0777 /srv/www/htdocs/piwik/tmp/templates_c/
chmod -R 0777 /srv/www/htdocs/piwik/tmp/cache/
chmod -R 0777 /srv/www/htdocs/piwik/tmp/assets/
chmod -R 0777 /srv/www/htdocs/piwik/tmp/tcpdf/

It's maybe required to create the user, group or directories above. You should also enable a temp. write access to '/srv/www/htdocs/piwik/config/'.

An error occurs that your php installation need zlib-suppot. You can find 'php5-zlib' in the OpenSuse software repository. Do not forget to restart Apache after installing this extension.

The next steps are quite easy. Just follow the Installation Wizard by entering your database connection details.

Also part of the installation is the generation of the tracking code. The installer says: "Here is the JavaScript Tracking code to include on all your pages, just before the </body> tag"

<!-- Piwik --> 

<script type="text/javascript">

var pkBaseURL = (("https:" == document.location.protocol) ? "https://localhost/piwik/" : "http://localhost/piwik/");

document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));

</script><script type="text/javascript">

try {

var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 1);

piwikTracker.trackPageView();

piwikTracker.enableLinkTracking();

} catch( err ) {}

</script><noscript><p><img 
src="http://localhost/piwik/piwik.php?idsite=1" style="border:0" alt="" 
/></p></noscript>

<!-- End Piwik Tracking Code -->

Important is the 'idsite' property. The value '1' is the id of the site which I configured as 'http://localhost:8080/share'. OK, Piwik is now up and running. Now let's investigate how to customize Alfresco in order to use it.

So to enable global tracking we can search a header or footer element which is used by every page of Alfresco. So let's check which site web scripts are available and let's see if we can put our snippet to one of the freemarker templates.

One good candidate seems to be '${WEBAPPS}/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/header'.  So I just placed the script above after the first other '<script>' block.

A test showed the following result:


Piwik can do a lot more. But for now this shows exactly what we required. It answers the question which site was accessed how often.







Thursday, December 20, 2012

First contact with Bootstrap

Insprired by Thomas Glaser and Jan Pfitzner, I thought it could be a good idea to get in contact with Twitter's Bootstrap framework. So here a simple skeleton page which uses Bootstrap components to reflect a simple web site. jQuery is used to interact with menu and the content area.

Here the result:



And finally the code:

<!-- (0) Create an empty HTML page -->
<!DOCTYPE html>
<html>
    <!-- (1) Some basic header info -->
    <head>
        <meta charset="utf-8">
            <title>Basic web site</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="description" content="">
            <meta name="author" content="">

        <!-- (2) Import the provided CSS file -->
        <link href="css/bootstrap.min.css" rel="stylesheet" media="screen">   
    </head>
   
    <body>
         <!-- (3) Import jQuery and Bootstrap -->
        <script src="js/jquery-latest.js"></script>
        <script src="js/bootstrap.min.js"></script>
       
        <!-- (5) At last add some JavaScript (jQuery) here to fill the page with life -->
        <script>
        $(document).ready(function(){
         
           $("#header-home").click(function() {
            $('div.my-content').html('<p>Home ...</p>');
            });

          $("#header-contact").click(function() {
            $('div.my-content').html('<p>Contact ...</p>');
            });


          $("#header-about").click(function() {
            $('div.my-content').html('<p>About ...</p>');
            });

          $("#header-contact").click(function() {
            $('div.my-content').html('<p>Contact ...</p>');
            });

         });     
            </script>

       
        <!-- (4) Bootstrap decorated HTML here -->
        <div id="container">

           <!-- A navigation header bar which is fixed to the top -->
           <div class="navbar navbar-fixed-top navbar-inverse">
                   <div class="navbar-inner">
                       <a class="brand" href="#"> Basic web site</a>
                       <ul id="header" class="nav">
                           <li class="active"><a href="#" id="header-home">Home</a></li>
                        <li><a href="#about" id="header-about">About</a></li>
                        <li><a href="#contact" id="header-contact">Contact</a></li>
                    </ul>
                         </div>
               </div>
       
             <!-- (4.1) A simple web site header -->       
           <div class="hero-unit">
                <h1>Basic web site</h1>
                <p>This site explains the Twitter Bootstrap a little bit.</p>
                <p>
                </p>
               </div>
         

           <!-- (4.2) A HTML grid with 12 columns, in this case we -->
             <div class="row">
            <!-- 4 columns are used for the left hand side navigation bar -->
                <div class="span4 bs-docs-sidebar">
                <ul class="nav nav-list bs-docs-sidenav">
                     <li><a href="#Marker1"><i class="icon-chevron-right"></i> Marker 1</a></li>
                     <li><a href="#Marker2"><i class="icon-chevron-right"></i> Marker 2</a></li>
                     <li><a href="#Marker2"><i class="icon-chevron-right"></i> Marker 3</a></li>
                </ul>
            </div>
               
            <!-- (4.3) 8 columns are used for the right hand side content area -->
            <div class="span8">
               
                <!-- (4.4) The content area which can be decorated by own css -->
                <div class="my-content">
                      <p> Home ...</p>
                </div>
            </div>
               </div>
            </div>   
    </body>
</html>

Friday, December 14, 2012

About the anatomy of ArchiveLink

Preamble

I just played arround with ArchiveLink, and so I decided to read the specification to inform myself how it internally works. What you can use in order to archive your SAP documents (invoices, ...) is an ArchiveLink speaking HTTP Content Server. So here a short summary of what I understood from the ArchiveLink specification so far.

Terms

We want to store documents inside a content repository. I guess it is not required to explain this term further. ArchiveLink knows the term 'component', whereby a component represents a content unit on a administrative level. Several component types are used (for instance 'data'),  Components are summarized via a document header. So here a scenario:

  • One Content Repository contains multiple document headers
  • One document header references multiple components
  • One component contains one content unit
A document header (id, status, date, ...) and a component (content type, status, ...) has some adminstrative meta data attached.

So an archive needs to reflect that terms somehow. The most intuitive way seems to reflect it by using folders. So the content repository contains a folder of the type header which contains component folders those are containing documents to keep the content. Important is that ArchiveLink does by design not transfer any SAP business object related meta data. Only administrative meta data is transfered to the archive.

The protocol

HTTP is used to exchange date on a lower level. So we are speaking about a kind of RESTFul service access. The format is 'http://${server}:${port}/${service name}/${command}?${command parameters}. If we take security into account (more about it later) and take a spot on the get command by retrieving the data component then the URL has the following format:

http://${host}:${port}/${service name}?get&contRep=${repo id}&docId=${doc id}&compId=data&accessMode=r&authId=${user id}&expiration=${time}?secKey=${base64 encoded security key}.

The following commands are available:

  • get: get a content unit
  • info: get info about a document
  • docGet: get the whole content
  • create: Create a new document
  • update: Modify an existing document
  • append: Append data to a content unit
  • search: Search by using a pattern inside a content unit (full text search)
  • attrSearch: Search for a specific attribute value
  • mCreate: Create new documents
  • serverInfo: Retrieve information about the content server
  • putCert: Transfer the client certificate
The resonse is a little bit old scool (the specification document is from 2001) and so does not return JSON, but Multipart Form Data.

Here an example response from the publically available specification document:

HTTP/1.1 200 (OK)
Server: Microsoft-IIS/4.0
Date: Wed, 04 Nov 1998 07:41:03 GMT
Content-Type: multipart/form-data; boundary=A495ukjfasdfddrg4hztzu...
...some more header informations...
Content-Length: 32413
X-dateC: 1998-10-07
X-timeC: 07:55:57
X-dateM: 1998-10-07
X-timeM: 07:55:57
X-contRep: K1
X-numComps: 2
X-docId: ID
X-docStatus: online
X-pVersion: 0045
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla12319981147528895
Content-Type: application/x-alf; charset=
Content-Length: 2591
X-compId: descr
X-Content-Length: 2591
X-compDateC: 1998-10-07
X-compTimeC: 07:55:57
X-compDateM: 1998-10-07
SAP AG SAP ArchiveLink (BC-SRV-ARL)
docGet
April 2001 265
X-compTimeM: 07:55:57
X-compStatus: online
X-pVersion: 0045
...component data ...
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla12319981147528895
Content-Type: application/x-alf; charset=
Content-Length: 29313
X-compId: data
X-Content-Length: 29213
X-compDateC: 1998-10-07
X-compTimeC: 07:55:57
X-compDateM: 1998-10-07
X-compTimeM: 07:55:57
X-compStatus: online
X-compStatus: online
X-pVersion: 0045
...component data ...
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla12319981147528895--
Beim docGet-Kommando auf ein leeres Dokument steht im Response-Body beispielsweise:
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla1231999102562159269
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla1231999102562159269--
Summary

This is the furst article of several ones those are focusing on ArchiveLink. What we should have learned is the following: An ArchiveLink archive can be realized by using a HTTP Content Server. The Content Server then could speak with a simple File System or even better an DMS. The content server provides several commands and functions to store and access content items. There are several kinds of content items, wrapped by components. A specific URL pattern is used on side of the HTTP Content Server. The response of the HTTP Content Server has the Multipart Form Data format.


Monday, December 3, 2012

How to setup an Alfresco Maven project in a few steps

 Install the tools and libraries
  1.  Install Maven (E.G. use 'sudo apt-get install maven2')
  2. Install Subversion (E.G. use 'sudo apt-get install subversion')
  3. Install the Subversion Java bindinds (E.G. use 'sudo apt-get install  libsvn-java)
 Install Eclipse and the required plug-ins (optional)
  1. Install Eclipse (you will need it as the I(ntegrated) D(evelopment) E(nvironment)
  2. Install the Eclipse Maven plug-in (The download site is 'http://download.eclipse.org/technology/m2e/releases'
  3. Install the Subversion plug-in (The easisest is to enter subversion in the Eclipse Market Place)
  4. In Eclipse create a new workspace
Create the project for the repository extensions
  1.  Change the directory to the Eclipse workspace
  2. Then run the following command
mvn archetype:generate -DarchetypeGroupId=org.alfresco -DarchetypeArtifactId=maven-alfresco-amp-archetype \
-DarchetypeVersion=3.9.1 -DgroupId=${Your domain here, E.G. de.ecg} -DartifactId=${Your project name -repo here, E.G. my-repo} -Dversion=1.0-SNAPSHOT \
-DarchetypeRepository=https://artifacts.alfresco.com/nexus/content/repositories/releases -DinteractiveMode=false
 
 The above command contains 2 place holders. One specifies your domain the other one should name the project. For example the project 'my-repo' will be created.

Create the project for the Share extensions
  1.  Change the directory to the Eclipse workspace
  2. Then run the following command

mvn archetype:generate -DarchetypeGroupId=org.alfresco.maven -DarchetypeArtifactId=maven-alfresco-share-archetype \
-DarchetypeVersion=3.9.1 -DgroupId=${Your domain here} -DartifactId=${Your project name -share here} -Dversion=1.0-SNAPSHOT \
-DarchetypeRepository=https://artifacts.alfresco.com/nexus/content/repositories/releases -DinteractiveMode=false
 
Create the Eclipse projects
  1. Open Eclipse
  2. Click on 'Import -> Maven -> Existing Maven Project into Workspace'
  3. Navigate into the folder of the repo project and confirm
  4. The download of the several dependent artifacts / libraries to your local Maven cache may take a while
  5. Do the same for the Share project
A really cool and easy way to get an Alfresco project set up quite simpler than with ANT script. In the pom.xml you can see that the referenced Alfresco version is 4.0.2b which seems to be an older Community Edition release. An open question is how to setup a similar project for the Alfresco Enterprise Edition.