He tenido que administrar varios servidores virtuales (Proxmox) que exponen diferentes servicios como SSH server, MySQL server y LDAP server, pero al que no podemos acceder directamente, lo tuve que hacer accediendo remotamente desde un cliente SSH y luego haciendo el “salto” hacia el servidor deseado. Esto es lo que se llama “tunel ssh”. Evidentemente, es un proceso muy lento sobretodo cuando necesitamos operar dichos servicios de manera rápida, crear un usuario, ver logs, clonar una VM, desplegar nuevos servicios, en fin. Esta situación se complica aún más si desde el servidor que nos sirve de puente tiene un Firewall que cierra o filtra muchos de los puertos, como es natural en un entorno productivo.

En fin, la herramienta más usada para hacer esto en Windows es Putty, además de ser un cliente SSH, permite crear túneles (port forwarding) de manera fácil.

Quizás lo más difícil es entender cómo se crean los túneles, aquí lo explicaremos y además emplearemos Tunnelier, otro cliente SSH de Btivise que además tiene unas características mejoradas en comparación a Putty.

1. Escenario donde crearemos túneles SSH

Escenario - Creando 3 túneles SSH hacia SSH server, MySQL server y LDAP server

Escenario – Creando 3 túneles SSH hacia SSH server, MySQL server y LDAP server

  • kns : representa al servidor SSH público que expone el servicio en el puerto 2121. También podéis usar el FQDN o la IP.
  • kns-lfry0, kns-db1, kns-ldap0 : representan los Hostnames o IPs privados o internos. Sólo pueden ser resueltos desde kns.

2. Creando túnel hacia SSH server

Una vez descargado e instalado Tunnelier, configurémoslo de esta forma.

  • En la pestaña “C2S” crear un tunel hacia el servidor kns-lfry0:22, tal como se indica en la figura.
Creando tunel SSH

Creando tunel SSH

  • En la pestaña “Options“, bajo “On Login” deshabilitar “Open Terminal” y “Open SFTP” (opcional, esto hace que no se abran consolas ssh ni el cliente SFTP).
  • En la pestaña Login introducir las credenciales para hacer SSH a kns bajo el puerto 2121, tal como se muestra en la figura.
Creando tunel SSH - haciendo Login

Creando tunel SSH – haciendo Login

  • Luego hacemos “Login“, con ello nos traemos el puerto 22 remoto al puerto 2122 del localhost.

En estos momentos ya hemos creado el túnel, ahora como estamos haciendo “port forwarding” hacia kns-lfry0 que tiene abierto el puerto 22 del SSH server, entonces podemos usar cualquier cliente SSH para conectarnos a dicho servidor remoto como si fuese un servicio local, pero en este caso debemos conectarnos a localhost en el puerto 2122. Podemos usar Putty o abrir nuevamente el programa Tunnelier (no añadir nuevos túneles, sólo usarlo como cliente SSH para conectarnos a localhost:2122).

Abriendo 2 sesiones de Tunnelier, el primero para crear el tunel y el segundo para hacer la conexión SSH a kns-lfry0:22 (localhost:2122)

Abriendo 2 sesiones de Tunnelier, el primero para crear el tunel y el segundo para hacer la conexión SSH a kns-lfry0:22 (localhost:2122)

3. Creando túnel hacia MySQL server

Este escenario es algo más difícil que el anterior, pero se resuelve haciendo un segundo túnel sobre el anterior, esto hace que cuando nos conectemos con un cliente MySQL, MySQL server no nos deniegue la conexión. Esto se debe a que por defecto MySQL deniega la conexión si lo hacemos desde un Host diferente al localhost. Al hacer el segundo túnel, nos conectamos directamente al MySQL server y establecemos la conexión desde el mismo host donde está instalado MySQL server.

Primer túnel ssh-mysql:

Primer túnel SSH-MySQL (1/2)

Primer túnel SSH-MySQL (1/2)

Primer túnel SSH-MySQL (2/2)

Primer túnel SSH-MySQL (2/2)

Una vez configurado el primer túnel, hacer click en Login.

Segundo túnel SSH-MySQL:

Segundo túnel SSH-MySQL (1/2)

Segundo túnel SSH-MySQL (1/2)

Segundo túnel SSH-MySQL (2/2)

Segundo túnel SSH-MySQL (2/2)

Una vez configurado el segundo túnel, hacer click en Login.

Ahora, usar cualquier cliente MySQL para conectarse al MySQL server, yo suelo usar HeidiSQL. Configurarlo de esta forma:

Usando HeidiSQL para conectarse a MySQL a través de un túnel SSH

Usando HeidiSQL para conectarse a MySQL a través de un túnel SSH

4. Creando túnel hacia LDAP server

Este escenario es similar al primer escenario donde nos conectábamos a un segundo SSH server llamado “kns-lfry0“, puerto “22“. Ahora nos conectaremos a “kns-ldap0“, puerto “389“, según la primera figura mostrada líneas arriba.

La diferencia estriba en que en el primer escenario usábamos un cliente SSH (Putty o Tunnerlier) para conectarnos al servidor, mientras que en este nuevo escenario usaremos un cliente LDAP (LDAP Browser Editor).

Otra pequeña diferencia con respecto a los anteriores escenarios es que usaremos un sólo Tunnelier para configurar todos los primeros túneles, aunque sólo vayamos a usar el relacionado a kns-ldap0:389. Esto es sólo por comodidad.

Túnel SSH-LDAP

Túnel SSH-LDAP (1/2)

Túnel SSH-LDAP (1/2)

Túnel SSH-LDAP (2/2)

Túnel SSH-LDAP (2/2)

Una vez configurado el túnel, usando LDAP Browser Editor crear una conexión hacia el túnel LDAP, es decir, localhost:2124.

LDAP Browser Editor - conectándonos al túnel SSH-LDAP

LDAP Browser Editor – conectándonos al túnel SSH-LDAP

5. Conclusiones

  • El servicio de SSH abre un el puerto 22 por defecto, muchos ports scanners o durante el proceso de pen-testing, el puerto 22 es uno de los más  atacados. Lo recomendable es cambiar el puerto, no permitir conectarse usando usuario ROOT y aplicar alguna técnica de ocultamiento de servicio como Port-Knocking. Crear túneles en este escenario no cambia nada, únicamente hay que conocer el nuevo puerto del SSH server.
  • Algunos servicios como SSH, MySQL, etc. permite incorporar mecanismos extras de seguridad basadas en IP/Hostname desde donde te conectas. Además de restringir desde dónde se establece la conexión usando Firewalling con iptables. Pues, lo recomiendo usar. En este post, MySQL tiene ese comportamiento y para acceder a él desde un cliente MySQL nos obliga crear dos túneles.
  • Es importante que el servidor SSH desde donde hacemos el salto tenga visibilidad al resto de servidores pero desde la interfaz de red interna.
  • Si quieres crear túneles desde Linux, lo puedes hacer desde línea de comando, ya que por defecto Linux tiene cliente y servidor SSH instalado o también puedes usar gSTM (Gnome SSH Tunnel Manager), es un front-end para gestionar tus túneles.
  • Y para Mac OSX, de manera similar a Linux, puedes hacer uso del cliente SSH de línea de comandos, aunque habrá que leer la ayuda del cliente ya que algunos parámetros cambian. Por otro lado, también puedes usar cualquier aplicación/front-end que te facilite crear uno o muchos túneles. Yo solía usar STM (SSH Tunnel Manager).

End.

El beneficio principal de hacer SOA sobre hacer Integración es la capacidad de gobierno que tenemos sobre los servicios, es decir, la capacidad de control que alcanzamos al aplicar los principios SOA.

En la integración o construcción de aplicaciones muchas veces tenemos que integrar o consumir servicios externos “no gobernados”, hacerlo sin control es siempre un riesgo, por ejemplo, si el contrato (WSDL) cambia, nuestra aplicación integrada se quedaría al margen de los cambios y sin soporte.

Gobierno de los servicio de Alfresco con WSO2

Gobierno de los servicio de Alfresco con WSO2

En esta post aprenderemos cómo registrar servicios externos en WSO2 Registry, concretamente aquellos servicios SOAP de Alfresco ECM, y luego consumirlos usando el mismo WSDL (contrato) desde nuestras aplicaciones de negocio usando WSO2 ESB.

I. Estrategia:

Para iniciar con el Gobierno SOA es necesario gestionar los servicios que vamos a consumir, eso implica registrar los servicios en WSO2 Registry, una vez realizado esto alcanzaríamos otros beneficios como: capacidad de medir, ofrecer otra interficie SOA, controlar la QoS, mejorar la Seguridad, gestionar versiones, encadenar servicios, etc.

Por estos motivos, WSO2 Registry es una herramienta fundamental para hacer Gobierno SOA y a partir de este sencillo principio SOA se abren infinitos escenarios de aplicación y de negocio.

En este post sólo registraremos un servicio de Alfresco ECM en WSO2 Registry, pero podría ser posible extender de manera rápida y fácil al resto de servicios SOAP, incluso al resto de servicios REST de Alfresco, además aprovechar la capacidad integradora de WSO2 ESB para “jugar” un poco con los servicios de Alfresco llegando incluso a brindar una nueva capa “gobernada” de servicios de gestión documental, es decir, un API completo de gestión documental.

II. Objetivos:

  1. Saber identificar Aplicación externa y servicios externos (endpoints, WSDLs,..) a usar (integrarlos) en nuestra aplicación interna.
  2. Aprender a registrar servicios externos en WSO2 Registry.
  3. Aprender a crear EndPoint interno en WSO2 ESB.
  4. Aprender a crear Proxy para dicho servicio manteniendo mismo “contrato” y usando nuevo EndPoint desde WSO2 ESB.
  5. Saber consumir el nuevo servicio recientemente registrado con WSO2 TryIt y con soapUI.

III. Requerimientos:

Emplearemos WSO2 Stratos Live, para ello debemos crearenos una cuenta y nos asegurarmos de que WSO2 ESB y WSO2 Registry estén habilitados.

IV. Pasos:

1. Identificar Aplicación externa y sus servicios externos (Endpoints, WSDLs,..)

En este caso usaremos Alfresco ECM como aquella aplicación que queremos integrar a la nuestra interna pero consumiendo únicamente sus servicios. Alfresco brinda una serie de servicios de gestión documental, esto son:

  • Servicios SOAP ad-hoc
  • Servicios basados en CMIS
WSDLs de Alfresco ECM

WSDLs de Alfresco ECM

  • Servicios REST ad-hoc
Servicios REST de Alfresco ECM

Servicios REST de Alfresco ECM

2. Desde WSO2 Registry, añadir servicios a partir de WSDLs.

En este caso usaremos únicamente el servicio de autenticación de alfresco (SOAP), a modo de ejemplo, aunque dejamos también el servicio de Repositorio:

Registrando los WSDL en WSO2 Registry

Registrando los WSDL en WSO2 Registry

En WSO2 Registry aparecerán 2 servicios registrados (uno por cada WSDL).

Servicios registrados

Servicios registrados

3. En WSO2 ESB, crear un nuevo “Address Endpoint” y guardarlo en el Governance Registry.

Creando EndPoints

Creando EndPoints

Crear “Address Endpoint” con los sgtes datos:

Nombre Endpoint: “kns.alfr.authn.ep0”
Valor: “http://www.konosys.es:8014/alfresco/api/AuthenticationService” (verlo en el WSDL, como se muestra en la figura de abajo).
Key: “gov:/ kns.alfr.authn.ep0”
Obteniendo EndPoint desde el WSDL

Obteniendo EndPoint desde el WSDL

Durante el proceso de creación del Endpoint hacer click en el botón “Save As”, te solicitará dónde guardar dicha entrada. Elegir Governance Registry con el nombre de la entrada por defecto (mismo nombre del endpoint).

Asignando EndPoint a servicio

Asignando EndPoint a servicio

Finalmente, si todo ha ido bien, se mostrarán los Dynamic Endpoints, verificar que nuestro endpoint esté en la lista.

Dynamic EndPoint creado

Dynamic EndPoint creado

4. Desde WSO2 ESB, crear Proxy para dicho servicio manteniendo mismo WSDL y usando nuevo Endpoint.

Nombre Proxy: “kns.alfr.authn.ep0”
WSDL: seleccionarlo desde Governance Registry.
Endpoint: seleccionarlo desde Governance Registry.
WSO2 ESB - Creando un Custom Proxy

WSO2 ESB – Creando un Custom Proxy

Luego, asignarle un nombre al proxy y seleccionar el WSDL alojado en el Registry.

WSO2 ESB - Seleccionando el WSDL desde el registro

WSO2 ESB – Seleccionando el WSDL desde el registro

Luego, hacer click sobre el botón “Next” para continuar con la configuración del Proxy. En la nueva pantalla tendremos la oportunidad de seleccionar el Endpoint que habiamos creado inicialmente.

WSO2 ESB - Seleccionando el EndPoint desde el Registry

WSO2 ESB – Seleccionando el EndPoint desde el Registry

Haga click en “Next” y en el último paso hacer click sobre “Finish”.

WSO2 ESB - Finalizando la creación del Proxy

WSO2 ESB – Finalizando la creación del Proxy

Si hemos llegado a este punto, ya habremos creado un Proxy con el que ya podemos invocar el servicio desde cualquier aplicación cliente SOAP. En este caso para probarlo usaremos el servicio TryIt de WSO2, para ello hacer click sobre “Try this service”.

WSO2 ESB - Listo para probar el Proxy

WSO2 ESB – Listo para probar el Proxy

5. Probando el Servicio con WSO2 TryIt

WSO2 viene con una herramienta para probar servicios, se llama WSO2 TryIt. En este ejemplo usaremos esta herramienta, aunque también explicamos qué hay que hacer para emplear soapUI.

WSO2 TryIt - Probando Servicios

WSO2 TryIt – Probando Servicios

6. Probando el Servicio con soapUI.

En este caso asegurarse de que estemos usando el Endpoint correcto, para ello podéis copiar el Endpoint que se muestra en WSO2 TryIt, en nuestro caso sólo son válidos las implementaciones de SOAP 1.2 y SOAP 1.1, estas son:

Probando Servicio con soapUI

Probando Servicio con soapUI

V. Observaciones

  • Si se muestra este error, es probable que hayas configurado incorrectamente el Proxy. Para solucionarlo prueba revisando la configuración del Proxy, concretamente el Endpoint. Una forma rápida de probarlo sería reemplazar el Endpoint que se muestra en TryIt por el real (no pasamos por WSO2 ESB), concretamente usar:
WSO2 TryIt - Error

WSO2 TryIt – Error

  • Durante el desarrollo de este blog hicimos unos cambios en nuestra web de demos, reemplazamos el puerto 8014 por 80, asi que si algo no funciona, intenta cambiar los puertos.

VI. Recursos usados:

Fin.

Espero que esto os haya servido.

I would like to share a tip, frequently we need modify protected files (i.e. in an Ubuntu box) from WinSCP remotely, but sometimes that is impossible.

If you have a non-privileged user and want to edit a root protected file of an Ubuntu box but with WinSCP, you can do it with a few changes.

Requirements:

1. Server:

- Ubuntu 11.04
- Non-privileged user: chilcano

2. Client:

- Any S.O. but with WinSCP 4.3.6 (http://winscp.net)

Steps:

1. In the server side add this line to /etc/sudoers

chilcano@sso1:/$ sudo nano /etc/sudoers

and

[...]
#### added
chilcano ALL=NOPASSWD: /usr/lib/openssh/sftp-server

2. In the client side open WinSCP and do the following

Check SFTP protocol in WinSCP

Check SFTP protocol in WinSCP

and

Add customized command to sftp-server in Ubuntu

Add customized command to sftp-server in Ubuntu

3. Test it, open a protected file and modify. You could do changes on it.

Open as ROOT from WinSCP

Open as ROOT from WinSCP

End.

I hope this help you.

Some days ago I had to deploy a Liferay Portal in a hosting provider where Proxmox Virtual Environment (PVE) was necessary to use.

Proxmox Virtual Environment is a Debian distro with pre-installed packages ready to use when you want to create virtualization platform for running Virtual Appliances and Virtual Machines based on OpenVZ and KVM.

But, What is difference between tradicional hosting and hosting based on Proxmox VE?, really does not exist differences because with Proxmox VE you can virtualize all (network, server, vlan, etc.).

Then, I did create a virtualized network with 2 servers based on Proxmox, the first server runs a Linux Debian with Apache HTTP server as web-proxy, the second server will be a private server with Liferay Portal.

The architecture final is as follow:

Architecture - Liferay in an environment virtualized with Proxmox

Architecture - Liferay in an environment virtualized with Proxmox

1. Installation first server with Proxmox / Apache2 HTTP Server (In Host)

When installing Proxmox in your hosting provider you will have as principal public server a first server with Debian Linux,
its initial configuration is as follow:

root@kns ~ # nano /etc/network/interfaces

### Hetzner Online AG - installimage
# Loopback device:
auto lo
iface lo inet loopback

# device: eth0
auto  eth0
iface eth0 inet static
  address   176.9.30.36
  broadcast 176.9.30.63
  netmask   255.255.255.224
  gateway   176.9.30.33
  pointopoint 176.9.30.33
  post-up mii-tool -F 100baseTx-FD eth0

# default route to access subnet
up route add -net 176.9.30.32 netmask 255.255.255.224 gw 176.9.30.33 eth0
root@kns ~ # more /etc/resolv.conf

search
nameserver 213.133.100.100
nameserver 213.133.98.98
nameserver 213.133.99.99

After of installing Proxmox, you have to create virtual network with a private IP range, in this case I will use 10.10.10.x.

1.1. Creating Virtual Network with Proxmox

root@kns ~ # nano /etc/network/interfaces

# Loopback device:
auto lo
iface lo inet loopback

# device: eth0
auto  eth0
iface eth0 inet static
  address   176.9.30.36
  broadcast 176.9.30.63
  netmask   255.255.255.224
  gateway   176.9.30.33
  pointopoint 176.9.30.33
  post-up mii-tool -F 100baseTx-FD eth0
  post-up echo 1 > /proc/sys/net/ipv4/conf/eth0/proxy_arp

#### my new virtual network
auto vmbr0
iface vmbr0 inet static
    address  10.10.10.1
    netmask  255.255.255.0
    bridge_ports none
    bridge_stp off
    bridge_fd 0
    post-up echo 1 > /proc/sys/net/ipv4/ip_forward
    post-up iptables -t nat -A POSTROUTING -s '10.10.10.0/24' -o eth0 -j MASQUERADE
    post-down iptables -t nat -D POSTROUTING -s '10.10.10.0/24' -o eth0 -j MASQUERADE
root@kns ~ # nano /etc/sysctl.d/10-no-icmp-redirects.conf

#### my bridge to VM
net.ipv4.conf.all.send_redirects = 0
root@kns ~ # nano /etc/sysctl.conf

### Hetzner Online AG installimage
# sysctl config
net.ipv4.conf.all.rp_filter=1
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.ip_forward=1

2. Installation of second server with Liferay 6.0.6 CE (a Guest VM)

2.1. Install your prefered Linux distro in your new VM

With Proxmox VE is easy and quickly to create VM from ISO image.
You can change VM parameters as RAM, HD, type of Network interface, MAC, etc. all from Proxmox VE web interface, or if you know Proxmox’s commands you could do from SSH terminal.

2.2. Configure eth0 in Guest

After of creating your VM with Proxmox, you will need to asign private IP or customize your VM, in this case you will need a VNC terminal to apply these changes.
With Proxmox you can get a shell/terminal to Guest VM without connection to virtualized network.

Then, from Proxmox VE (interface web) go to your recently VM created and open a VNC terminal, then here runs following commands:

[root@pisco ~]# nano /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE="eth0"
NM_CONTROLLED="yes"
ONBOOT=yes
HWADDR=3E:DB:61:48:63:88
TYPE=Ethernet
BOOTPROTO=none
IPADDR=10.10.10.12
PREFIX=24
GATEWAY=10.10.10.1
DNS1=213.133.99.99
DEFROUTE=yes
IPV4_FAILURE_FATAL=yes
IPV6INIT=no
NAME="System eth0"
UUID=5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03
[root@pisco ~]# more /etc/resolv.conf

search local
nameserver 213.133.99.99

…add gateway

[root@pisco ~]# ip route add 176.9.30.36 dev eth0

..add default path

[root@pisco ~]# ip route add default via 176.9.30.36

2.3. Re-start VM and test configuration

[root@pisco ~]# ping www.google.com

PING www.l.google.com (74.125.39.103) 56(84) bytes of data.
64 bytes from fx-in-f103.1e100.net (74.125.39.103): icmp_seq=1 ttl=55 time=6.77 ms
64 bytes from fx-in-f103.1e100.net (74.125.39.103): icmp_seq=2 ttl=53 time=6.81 ms

2.4. Install Liferay, then move Liferay to non-root context

Liferay will be running with this URL: http://10.10.10.12:8080/kns

3. Move Proxmox Virtual Environment 1.9 (proxmox’s website) from 80 port to 8080 port.

Next blog post.

4. Configure first server as Reverse Web Proxy

Next blog post.

5. Test installation

From Internet you can call this URL: http://myliferay.intix.info/kns, http://176.9.30.36/kns or http://${YOUR-PUBLIC-IP}/kns

Bye.

References

Some days ago I saw a post on the LinkedIn’s Alfresco forums asking ‘Which front end framework is Generally used with Alfresco?’, for this reason I started to research and test them and discovered with joy that the trend is to use lightweight frameworks capable of creating rich interfaces quickly. Well here I put the review: Because I started to carefully review and discovered with joy that the trend is to use lightweight frameworks capable of creating rich interfaces quickly. Well here I put the review:

I. What is a client for Alfresco ECM ?

A client for Alfresco is any webapp or desktop app that consumes the Alfresco’s services, this client app can be created with Alfresco’s standard framework or with third party frameworks.

Several endusers want always customize traditional and existing Alfresco’s web clients, because these do not fit its requeriments.
Then, they quickly seek to modify existing Alfresco’s webclient and if this is not possible, then seek a tool (framework) to build a desktop or client from scratch.

How to client applications can be connected to Alfresco ECM

How to client applications can be connected to Alfresco ECM

What are the main reasons why you need to modify Alfresco’s webclients or create new ones?
Well, there are several reasons:

  • The technology stack in the company is not Java.
  • Is very difficult to make changes. Although there is much online information available on how to make customizations to Alfresco Explorer and Alfresco Share.
  • Make changes in existing webapp are not supported.
  • UI (look&feel) is not aligned to corporate graphical style.
  • Alfresco’s content must be delivered for any devices: iPad, Android tablet, PC, etc.
  • … or simply, everybody has different requirements.

Well, this post will review existing initiatives, RIA, RDA (Rich Desktop App) and RAD frameworks for developing clients for Alfresco ECM.

II. List of Clients

1. Alfresco Explorer

Alfresco Explorer is the clasic webapp bundled with Alfresco ECM, It is the principal web application to do document management, this offers some functionalities of system administratios.
Big changes or personalizations on this webapp must be done by XML file (Spring) configuring.

* Web: http://www.alfresco.com
* License: Enterprise Edition is open source with commercial support, Community Edition is LGPL 2 with some exception.
* Technology:
Alfresco Explorer based in JavaServerFace.

2. Alfresco Share

* Provides a rich web-based collaboration environment for managing documents, wiki content, blogs and more.
* Is based on Alfresco Surf API (now is a Spring project).

* Web: http://www.alfresco.com
* License: Idem to Alfresco Explorer
* Technology:
Alfresco Share based in SpringSurf/Webscripts.

3. Opsoro

“opsoro is an alternative web client for the Alfresco Enterprise Content Management Plattform. It’s goal is to provide an easier user interface with a richer user experience by leveraging the latest web technologies.”

* Version: ExtJS-version is 0.1beta
* Web: http://www.opsoro.org
* License: GPL
* Architecture: http://www.opsoro.org/architecture
* Technology:
- Alfresco server side: Opsoro does uses standard Alfresco Web Scripts.
- Web server side: Ext JS 2.0
* Features:
Basic document management features as:
- Folder, category and tag cloud browser
- My Alfresco portal with draggable portlets
- Document tagging
- Embedded preview (crop content & image preview)
- Inline view (supports text, pdf and images)

4. FlexSpaces (in browser, portlet and Air)

“Flex RIA client for Alfresco ECM”

* Version: build 04052011 from 2011/05/10
* Web: http://code.google.com/p/flexspaces
* License: GNU Lesser GPL
* Architecture: see my diagram above
* Technology: Flex, own RESTful WebScripts, Air.
* Features:
- Supports document management, search, workflow and wcm.

5. CMISSpaces (in browser, portlet and Air)

· “Flex RIA client for CMIS content management APIs.”
· Based on FlexSpaces

* Version: Build 18 from 2011/05/23
* Web: http://code.google.com/p/cmisspaces
* License: GNU Lesser GPL
* Architecture: see my diagram above
* Technology: Flex, BlazeDS, CMIS, Air.
* Features:
- Doc management, except:
· cut,copy,paste, advanced search not implemented, properties is read-only
- No workflow actions.

6. ifresco

“OpenSource Client for Alfresco
With the ifresco client we offer a powerful OpenSource Web-Client for Alfresco which has all features which are important and neccessary for the daily work with the DMS/ECM system and which can be operated easily and intuitiv.
ifresco is Web-browser based – but offers consumer-like experience of MS-Windows applications with sortable lists, context menus, drag and drop.
A plug-in interface allows to add new functions and features to the client without changing the base product.”

* Version: 0.2 beta from 2011/05
* Web: http://code.google.com/p/ifresco-client
* License: GNU GPL v3
* Architecture: http://www.ifresco.at/de/products/client/overview.html
* Technology:

* PHP5+
* Symfony 1.4 for MVC Framework
* Doctrine for database ORM
* Alfresco Integration based on Standard Restful Services & Webservice API
* No additional webscripts must be installed on the server!
* Server Side Converter for OFFICE2SWF Conversion (jodconvert & swftools)
* MySQL to save – administrative data, user sessions and favorites
* jQuery for various JavaScript functions and drag & drop
* ExtJS for the user interface with plug-ins

* Features:
* All the DMS/ECM functionalities.
* A plug-in interface allows to add new functions and features to the client without changing the base product.
* No workflow actions.
* Provides a mature and complete PHP Lib based on Alfresco PHP API (http://code.google.com/p/ifresco-php-library).

7. Liferay Document Library by CMIS

* Web: http://www.liferay.com
* License: GNU General Public License version 3.0 (GPLv3) for Community Edition
* Architecture: see my diagram above
* Technology:
Up to version 6.0.6 was possible to use Liferay Document Library to connect to any CMIS repository through a Liferay Hook, but was little usable in real scenarios, now in version 6.1b3 we can connect to any CMIS repository and allow synchronization between Alfresco repository and Liferay.
I think this is a huge step forward demonstrating the strength of CMIS interface to any type of application in front-end side.

* Features:
- Connect to any CMIS repository
- Still remaining to be developed all the features of CMIS 1.0
- It is very usable in real scenarios.

8. DoCASU

“The goal of DoCASU is to provide to the Alfresco Community a Custom Alfresco UI with a strong focus on User eXperience. This will be less confusing for average end users and will permit a broader acceptance of the solution by a larger group of users.”

* Version: 1.6.3 from 2010/01/08
* Web: http://docasu.sourceforge.net
* License: GNU GPL
* Architecture: http://docasu.sourceforge.net/productInfo.html
* Technology: ExtJS, own WebScripts (REST) and WS.
* Features:
DoCASU provides the functionality that average end users need to easily work with Alfresco:
- Folder Actions (View, create, delete, rename Folder, Create HTML or Text File, Upload File, Paste all)
- File Actions (View, update, delete, checkout, checkin, copy File, Download File, Add to favorites, Mail Link to File, Copy File Path/URL to clipboard)
- Simple and Advanced Search (Node type, Creation date, Modification date, Look in current folder, all folders)
- No workflow actions

9. ExtAlf

“This is not an Opsoro or FlexSpaces alternative, rather than a developer toolkit or a simple SDK what supports embedding Alfresco backend functionality into Web2.0/AJAX portals and frontends.”

* Web: http://louise.hu/poet/?p=753
* License: ExtAlf is part of a bigger project what is closed-source yet.
* Architecture: see diagram above
* Technology: ExtJS and own WebScripts
* Features:
- Doc management.
- More info here: http://louise.hu/poet/?p=927

10. CMIS Explorer

http://blogs.citytechinc.com/sjohnson/?p=60
http://code.google.com/p/cmis-explorer

11. Alfresco Office Plugin

“Implemented on a mini-browser windows, all actions available through REST API (HTML response for UI, others JSON for responses to update UI and data retrieval”.
Core services accessible through plugin:
- Document management
- Workflow
- Search

12. SCAr

http://wiki.rivetlogic.org/display/SCAr/Home

13. AWPr

“AWPr (Alfresco Web Script Portlet rivet) is a JSR-286 portlet that can be used to expose remote Alfresco Web scripts, including those that need user authentication.”

* Web: http://wiki.rivetlogic.org/display/AWPr/About+AWPr
* License: GNU Affero General Public License.
* Version:

14. Alfresco Web Script Portlet

“A webscript portlet for Liferay Portal, supporting multiple modes of authentication, a simple AJAX proxy and jQuery support.”

* Web: http://forge.alfresco.com/projects/liferaywsp
* License: MIT/X Consortium License
* Version: 1.0 of May/31/2010

15. WeWeBu OpenWorkdesk

“WeWebU OpenWorkdesk is an application suite (not just a CMIS browser!) for Enterprise Content Management (ECM) with an intuitive Web 2.0 front-end.”

* Web: http://openworkdesk.org (http://sourceforge.net/projects/owd)
* License: GNU General Public License version 3.0 (GPLv3) for Community Edition
* Version: 3.2.0.0 sprint6
* Architecture:

Integration using CMIS standard and custom framework (WeWebU OpenECM-Framework) more info here:
http://www.wewebu.de/fileadmin/user_upload/software/OECM_Architektur_EN.gif

* Technology:
-Java, CMIS and ExtJS 3.2.0 for UI in frontend

* Features:
The Community Edition version contains standard functionalities related to document manangement and supports all CMIS-enabled ECM systems:
- checkin, checkout, create, delete, search, …
Extra functionalities as workflow interactions, etc. are not availables in Community Edition.
For more detailed information about editions, here is a short comparison matrix:
http://www.openworkdesk.org/system/files/u4/20100623_OS-Editionen-Matrix_All.jpg

For an updated roadmap, you can see here:
http://www.openworkdesk.org/roadmap

III. Meta-frameworks

1. Jibe Framework (AlfExt)
http://code.google.com/p/jibeframework

2. Spring Surf
http://www.springsurf.org

3. Apache Chemistry (CMIS)
http://chemistry.apache.org

4. Spring Surf CMIS Application Browser
“Spring Surf provides a scriptable approach to building web applications. OpenCMIS provides a Java client library for accessing any CMIS compliant content repository. By combining the two, we create a development platform for building CMIS enabled web applications.”

http://blogs.alfresco.com/wp/cmis/2010/03/17/spring-surf-and-opencmis-integration
http://blogs.alfresco.com/wp/cmis/2010/06/14/spring-surf-and-opencmis-integration-part-2

5. Struts2 CMIS Explorer
“Struts2CmisExplorer is a web-based CMIS explorer. It uses Struts2 for the user interface.
If you have documents stored in your ECM repository and want to expose them (or some of them) to your clients/employees through an Intranet (or Internet/portal) web application…”
http://code.google.com/p/struts2cmisexplorer

6. Drupal + CMIS
http://drupal.org/project/cmis

7. Joomla + CMIS
http://blog.joomlatools.eu/2008/12/joomla-meet-alfresco.html

8. Drupal Open Atrium + CMIS
http://ecmarchitect.com/archives/2010/03/22/1141

9. ifresco PHP Lib
“ifresco PHP Library extends the Alfresco PHP Library, many RESTFul Services (which are used by Share) are implemented. Zoho Upload is implemented in here too.”

* Web: http://code.google.com/p/ifresco-php-library
* License: GNU GPL v3
* Technology:
- PHP5+
- Alfresco Integration based on Standard Restful Services & Webservice API
- No additional webscripts must be installed on the server!
- Parts of the Administration Service
- Category Management
- Lucene Search for Limited Result Query’s

IV. Conclusions

  • This review concludes that the trend is to use existing products (ifresco, Drupal, OpenAtrium, Liferay, ….) enabling rapid integration with Alfresco services.
  • The second option is to create RIA applications from scratch, and quickly, taking advantage of existing metaframeworks, we can see in doCASU, ExtAlf, AWPr, FlexSpace, etc.., The fact that they have started as a proof of concept and then have become standalone products confirms this trend.
  • You can download a resume table with all features of each product reviewed from here.

I think that we are reaching the maturity level in Liferay and Alfresco, because we can create applications on top of them of fastly and easy way.

Alfresco ECM has functionalities exposed as a RESTful API, as know as Alfresco Webscripts, built on the basis of Spring Surf.
Liferay Portal has Liferay IDE based on Eclipse where we can create from scratch different types of Portlets. Also Liferay allows to include external libraries as jQuery, ExtJS, Vaadin, etc. that allows to develop highly customized portlets.

Right now, when several people ask me how to integrate Alfresco into Liferay, after I ask them what does mean when you said *integrate*?. Well I say that implies several thing as:

Integration mean:

  1. User and roles, SSO ?
  2. Include Alfresco Explorer or Share as a Portlet?
  3. Include Alfresco Explorer inside iFrame Portlet?
  4. Call any Alfresco’s functionality from a Portlet?

Well, everything is possible to do, but to create applications from scratch following point 5 was very difficult, but now I think is the quickest way to do it, also the best from an architectural point of view.

This post explain how to do a portlet calling to Alfresco’s Webscripts (REST URIs) via ajax using jQuery. I also give some recommendations.

Ajax Portlet calls Alfresco Webscripts

Ajax Portlet calls Alfresco Webscripts

Requeriments

  1. Liferay IDE version 1.3.1 as IDE
  2. Liferay Portal version 6.0.6 installed into IDE
  3. Liferay SDK version 6.0.6 installed into IDE
  4. Alfresco ECM version 3.4d CE installed
  5. Identify and verify Alfresco Webscripts:
    • Login and get Ticket: http://${ALFRESCO}/alfresco/service/api/login?u=${USR}&pw=${PWD}
    • Folder browser: http://${ALFRESCO}/alfresco/service/sample/folder${INITIAL_FOLDER}
  6. jQuery version 1.6.3 added to new portlet

Process

1. From Liferay IDE create a new Liferay Project that implement GenericPortlet as follow:

Liferay IDE - creating new Liferay Project (1/6)

Liferay IDE - creating new Liferay Project (1/6)

Liferay IDE - creating new Liferay Project (2/6)

Liferay IDE - creating new Liferay Project (2/6)

Liferay IDE - creating new Liferay Project (3/6)

Liferay IDE - creating new Liferay Project (3/6)

Liferay IDE - creating new Liferay Project (4/6)

Liferay IDE - creating new Liferay Project (4/6)

Liferay IDE - creating new Liferay Project (5/6)

Liferay IDE - creating new Liferay Project (5/6)

Liferay IDE - creating new Liferay Project (6/6)

Liferay IDE - creating new Liferay Project (6/6)

2. The structure of Project in Liferay IDE will be as follow:

Liferay IDE - folder structure of new project

Liferay IDE - folder structure of new project

3. Add code in view.jsp to call serverResource method and to do ajax call to Alfresco. Also, in view.jsp you will add JavaScript code (jQuery) for parsing HTML/XML ajax responses.

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<portlet:defineObjects />

This is the <b>Ajax Alfresco Folder Browser</b> portlet in View mode.
<hr/>

<%
// "http://192.168.56.101:8080";
String strUrlAlfIP = renderRequest.getAttribute("alfServer").toString(); 
// "/alfresco/service/api/login?u=admin&pw=admin";
String strUrlAlfLogin = renderRequest.getAttribute("alfTicketSvc").toString() + "?" + renderRequest.getAttribute("alfTicketSvcParams");
// "/alfresco/service/sample/folder/Company%20Home";
String strUrlAlfDir = renderRequest.getAttribute("alfWebscriptBrowserURL").toString(); 
%>
<portlet:resourceURL var="resourceUrlAlfIP" id="<%=strUrlAlfIP%>" escapeXml="false" />
<portlet:resourceURL var="resource1AlfLogin" id="<%=strUrlAlfIP.concat(strUrlAlfLogin.trim()).trim()%>" escapeXml="false" />
<portlet:resourceURL var="resource2AlfFolderBrowser" id="<%=strUrlAlfIP.concat(strUrlAlfDir.trim()).trim()%>" escapeXml="false" />

<script type="text/javascript">
jQuery(document).ready(function() {
		
	var urlAlfIP = "<%=strUrlAlfIP%>";	
	var currentAlfTicket = jQuery("#<portlet:namespace/>alfrescoticket").text();			   
	$("#<portlet:namespace/>buttonAlfLoginAndTicket").click(function(){		
		if (currentAlfTicket == "") {
			jQuery("#<portlet:namespace/>loading").html("<img src='<%=request.getContextPath()%>/images/2-1.gif' border='0'> loading ...");
			jQuery("#<portlet:namespace/>errormsg").html("");
			jQuery.get( '<%=renderResponse.encodeURL(resource1AlfLogin)%>',
					function (data1, textStatus1, jqXHR1) {
							jQuery("#<portlet:namespace/>alfrescoticket").html(data1.getElementsByTagName("ticket")[0].childNodes[0].nodeValue);
							jQuery.get( '<%=renderResponse.encodeURL(resource2AlfFolderBrowser)%>', 
								'alf_ticket=' + data1.getElementsByTagName("ticket")[0].childNodes[0].nodeValue, 
								function (data2, textStatus2, jqXHR2) {
									jQuery("#<portlet:namespace/>loading").html("");	
									jQuery("#<portlet:namespace/>alfrescowscontent").html("<b><font color='blue'>Folder:</font></b> " + $(data2).filter("title").text() + "<br><table>");							
									var i=1;
									$(data2).find("a").each( 
										function() {								
											$("#<portlet:namespace/>alfrescowscontent").append( "<tr><td>" +  i++  + "&nbsp;&gt;&nbsp;</td><td><a href='" + $(this).attr("href") + "' id='IdLinkAlfPath'>" + $(this).text() + "</a></td></tr>");
										}
									);
									jQuery("#<portlet:namespace/>alfrescowscontent").append("</table><hr/>");
									jQuery("#<portlet:namespace/>errormsg").append("<font color='green'>* textStatus2: " + textStatus2  + "</font><br/>");
									jQuery("#<portlet:namespace/>errormsg").append("<font color='green'>* jqXHR2: " + jqXHR2.status  + "</font><br/>");								
									jQuery("#<portlet:namespace/>loading").html("");
								},
								'html'
							).error(function() { //alert("2nd ajax error"); 
												}); // 2nd end-jquery-get	
							jQuery("#<portlet:namespace/>errormsg").append("<font color='green'>* textStatus1: " + textStatus1  + "</font><br/>");
							jQuery("#<portlet:namespace/>errormsg").append("<font color='green'>* jqXHR1: " + jqXHR1.status  + "</font><br/>");	
					} // end-function-data 
			).error(function() { //alert("1st ajax error"); 
								}); // 1st end-jquery-get
		} // end-if
	}); // end-click-button
	
	$('a#IdLinkAlfPath').live('click', function(event) {
		jQuery("#<portlet:namespace/>loading").html("<img src='<%=request.getContextPath()%>/images/2-1.gif' border='0'> loading ...");
		var urlAlfGeneric = "" + "<%=renderResponse.encodeURL(resourceUrlAlfIP)%>";
		urlAlfGeneric = urlAlfGeneric.replace(encodeURIComponent(urlAlfIP), encodeURIComponent(urlAlfIP + $(this).attr("href")));
		
 		jQuery.get( urlAlfGeneric, 
 			'alf_ticket=' + jQuery("#<portlet:namespace/>alfrescoticket").text(), 
 			function (data3, textStatus3, jqXHR3) {
 				jQuery("#<portlet:namespace/>loading").html("");	
 				jQuery("#<portlet:namespace/>alfrescowscontent").html("<b><font color='blue'>Folder:</font></b> " + $(data3).filter("title").text() + "<br><table>");	
 				var i=1;
 				$(data3).find("a").each( function() {								
 					$("#<portlet:namespace/>alfrescowscontent").append( "<tr><td>" +  i++  + "&nbsp;&gt;&nbsp;</td><td><a href='" + $(this).attr("href") + "' id='IdLinkAlfPath'>" + $(this).text() + "</a></td></tr>");
 				});
 				jQuery("<portlet:namespace/>alfrescowscontent").append("</table>");
 				
				jQuery("#<portlet:namespace/>errormsg").append("<font color='green'>* textStatus3: " + textStatus3  + "</font><br/>");
				jQuery("#<portlet:namespace/>errormsg").append("<font color='green'>* jqXHR3: " + jqXHR3.status  + "</font><br/>");				
 								
 				jQuery("#<portlet:namespace/>loading").html("");
 			}, 
 			'html'
 		).error(function() { //alert("3rd ajax error"); 
 							}); // 3rd end jQuery.get		
		return false; 	// works, does not propagate
    });
    $('a#IdLinkAlfPath').trigger('click');
    
    // jquery error management
	jQuery("#<portlet:namespace/>errormsg").ajaxError(
		function (event, jqXHR, ajaxSettings, thrownError) {
			jQuery("#<portlet:namespace/>errormsg").html("");
			jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* Status Code jqXHR: " + jqXHR.status  + "</font><br/>");
			jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* Status Text jqXHR: " + jqXHR.statusText  + "</font><br/>");
			jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* URL: " + ajaxSettings.url  + "</font><br/>");
			jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* thrownError: " + jqXHR.statusText  + "</font><br/>");
			
			// intix: does not work HTTP_STATUS_CODE in 6.0.6 CE
            // http://issues.liferay.com/browse/LPS-13039
 			// for this reason, bellow messages will not be show
			if(jqXHR.status == 0) {
				// a status of 0 indicates a failure to connect to alfresco
				jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* Message: Unable to reach the Alfresco server, check your network connection</font>");
			}else if(jqXHR.status == 403) {
				// a 403 indicates that the login via the alfresco ticket service has failed.
				// display the "access denied" div
				jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* Message: An authentication error has occurred loading content from Alfresco, check login params</font>");
			}else if(jqXHR.status == 500) {
				// we shouldn't see many 500 errors from Alfrsco services if they 
				// have been properly configured.  
				jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* Message: A server error has occurred loading content from Alfresco</font>");
			}else {
				// report timeouts to the user
				jQuery("#<portlet:namespace/>errormsg").append("<font color='red'>* Message: Request to Alfresco server has timed out</font>");
			}
			jQuery("#<portlet:namespace/>loading").html("");
		}
	); //end-ajax-error 

	// toggles the slickbox on clicking the noted link  
	$('#alf-error-slick-toggle').click(function() {
		$('#<portlet:namespace/>errormsg').toggle(400);
		return false;
	});

	// toggles the slickbox on clicking the noted link  
	$('#alf-content-slick-toggle').click(function() {
		$('#<portlet:namespace/>alfrescowscontent').toggle(400);
		return false;
	});	
});
</script>

<input type="button" id="<portlet:namespace/>buttonAlfLoginAndTicket" value="Login Alfresco and get Ticket">
<hr>

<!-- div to contain ticket retrieved from Alfresco Login web script -->
<div id="<portlet:namespace/>alfrescoticket"></div>

<!-- Div to hold loading image -->
<div id="<portlet:namespace/>loading"><img src="<%=request.getContextPath()%>/images/2-1.gif" border="0"> ...click on above button to start or change params in portlet menu preferences!</div>

<br/>
<!-- Div to hold error messages -->
<a href="#" id="alf-error-slick-toggle">[+] Toggle error console</a>
<div id="<portlet:namespace/>errormsg" class="div_bg_white" > :)  </div>

<!-- Div to hold logs -->
<div id="<portlet:namespace/>logs"></div>

<br/>
<!-- Div to hold alfresco content -->
<a href="#" id="alf-content-slick-toggle">[+] Toggle Alfresco content</a>
<div class="div_bg_white" id="<portlet:namespace/>alfrescowscontent">:)</div>

4. AjaxAlfrescoFolderBrowser.java extends GenericPortlet, in the serverResource method manages ajax calls and returns ResourceResponse to be parsed in view.jsp

package info.intix.lfry.samples;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;

/**
 * Portlet implementation class AjaxAlfrescoFolderBrowser
 */
public class AjaxAlfrescoFolderBrowser extends GenericPortlet {

    public void init() {
        editJSP = getInitParameter("edit-jsp");
        helpJSP = getInitParameter("help-jsp");
        viewJSP = getInitParameter("view-jsp");
    }

	/**
	 * intix: Changes are persisted when the store method is called. 
	 * The store method can only be invoked within the scope of a processAction call. 
	 * Changes that are not persisted are discarded when the processAction or render method ends.
	 */
    public void processAction(
            ActionRequest actionRequest, ActionResponse actionResponse)
        throws IOException, PortletException {

        //super.processAction(actionRequest, actionResponse);
		PortletPreferences prefs = actionRequest.getPreferences();		
		prefs.setValue("ticketUrl", actionRequest.getParameter("ticketUrl"));
		prefs.setValue("alfServer", actionRequest.getParameter("alfServer"));
		prefs.setValue("alfTicketSvc", actionRequest.getParameter("alfTicketSvc"));
		prefs.setValue("alfTicketSvcParams", actionRequest.getParameter("alfTicketSvcParams"));
		prefs.setValue("alfWebscriptBrowserURL", actionRequest.getParameter("alfWebscriptBrowserURL"));
		prefs.setValue("alfWebscriptBrowserURLParams", actionRequest.getParameter("alfWebscriptBrowserURLParams"));
		prefs.setValue("jQuery", actionRequest.getParameter("jQuery"));	
		prefs.store();
		actionResponse.setPortletMode(PortletMode.EDIT);
    }
    
    /**
     * intix:
     */
    public void doEdit(
            RenderRequest renderRequest, RenderResponse renderResponse)
        throws IOException, PortletException {
		if (renderRequest.getPreferences() == null) {
			//super.doEdit(renderRequest, renderResponse);
		} else {
			// get editable preferences
			PortletPreferences prefs = renderRequest.getPreferences();
			
			// intix: these values will override options in portlet.xml
			renderRequest.setAttribute("alfServer", (prefs.getValue("alfServer", "")));
			renderRequest.setAttribute("alfTicketSvc", (prefs.getValue("alfTicketSvc", "")));
			renderRequest.setAttribute("alfTicketSvcParams", (prefs.getValue("alfTicketSvcParams", "")));			
			renderRequest.setAttribute("alfWebscriptBrowserURL", (prefs.getValue("alfWebscriptBrowserURL", "")));
			renderRequest.setAttribute("alfWebscriptBrowserURLParams", (prefs.getValue("alfWebscriptBrowserURLParams", "")));		
			renderRequest.setAttribute("jQuery", (prefs.getValue("jQuery", "")));		
			include(editJSP, renderRequest, renderResponse);
		}
    }
    
    public void doHelp(
            RenderRequest renderRequest, RenderResponse renderResponse)
        throws IOException, PortletException {
        
        include(helpJSP, renderRequest, renderResponse);
    }
    
    /**
     * intix:
     */
    public void doView(
            RenderRequest renderRequest, RenderResponse renderResponse)
        throws IOException, PortletException {
        
		try {
			// get portlet prefs
			PortletPreferences prefs = renderRequest.getPreferences();	
			
			String alfServer = prefs.getValue("alfServer", "");
			String alfTicketSvc = prefs.getValue("alfTicketSvc", "");
			String alfTicketSvcParams = prefs.getValue("alfTicketSvcParams", "");		
			String alfWebscriptBrowserURL= prefs.getValue("alfWebscriptBrowserURL", "");
			String alfWebscriptBrowserURLParams = prefs.getValue("alfWebscriptBrowserURLParams", "");			
			String jQuery = prefs.getValue("jQuery", "");			
			String ticketUrl = alfServer + alfTicketSvc + "?" + alfTicketSvcParams;
			
			renderRequest.setAttribute("ticketUrl", ticketUrl);			
			renderRequest.setAttribute("alfServer", alfServer);
			renderRequest.setAttribute("alfTicketSvc", alfTicketSvc);
			renderRequest.setAttribute("alfTicketSvcParams", alfTicketSvcParams);		
			renderRequest.setAttribute("alfWebscriptBrowserURL", alfWebscriptBrowserURL);
			renderRequest.setAttribute("alfWebscriptBrowserURLParams", alfWebscriptBrowserURLParams);
			renderRequest.setAttribute("jQuery", jQuery);
		}catch(Exception ex) {
			_log.error(ex);
		}     	
        include(viewJSP, renderRequest, renderResponse);
    }

    protected void include(
            String path, RenderRequest renderRequest,
            RenderResponse renderResponse)
        throws IOException, PortletException {

        PortletRequestDispatcher portletRequestDispatcher =
            getPortletContext().getRequestDispatcher(path);

        if (portletRequestDispatcher == null) {
            _log.error(path + " is not a valid include");
        }
        else {
            portletRequestDispatcher.include(renderRequest, renderResponse);
        }
    }

    /**
     * intix: serveResource does HTTP and Ajax call behind of Liferay
     */
    public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
    	response.setContentType("text/xml");
		String strAlfTicket= request.getParameter("alf_ticket");	
        String strQueryString = "";
        if (strAlfTicket != null) {
			// intix: if alf_ticket exists, then user was authenticate with alfresco
			Map<String, String[]> mapParameters = request.getParameterMap();
			for (Entry<String, String[]> entryParameter : mapParameters.entrySet()) {
			    System.out.println(">> Key = " + entryParameter.getKey() + ", Value = " + entryParameter.getValue()[0]);		    
			    strQueryString = strQueryString + entryParameter.getKey() + "=" + entryParameter.getValue()[0] + "&";
			}
		} else {
			// intix: ticket is null
			String strUser = request.getParameter("u");
			String strPw = request.getParameter("pw");
			strQueryString = "u=" + strUser + "&pw=" + strPw;
		}
		String requestUrl = request.getResourceID();    
    	BufferedInputStream web2ProxyBuffer = null;
        BufferedOutputStream proxy2ClientBuffer = null;
        HttpURLConnection con;
        URL url = null;
        try {
            int oneByte = 0;
            String methodName;
            if (strAlfTicket != null) {
            	url = new URL(requestUrl + "?" + strQueryString);
            } else {
            	url = new URL(requestUrl);
            }
            con = (HttpURLConnection) url.openConnection();
            methodName = request.getMethod();
            System.out.println(">> methodName: " + methodName);
            
            con.setRequestMethod(methodName);
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setFollowRedirects(false);
            con.setUseCaches(true);
            con.connect();

            // does not work in 6.0.6 CE
            // http://issues.liferay.com/browse/LPS-13039
            int httpRespCode = con.getResponseCode();
            response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(httpRespCode));
            System.out.println(">> HTTP_STATUS_CODE: " + httpRespCode);
                      
            if(methodName.equals("POST")) {
                BufferedInputStream clientToProxyBuf = new BufferedInputStream(request.getPortletInputStream());
                BufferedOutputStream proxyToWebBuf     = new BufferedOutputStream(con.getOutputStream());
                while ((oneByte = clientToProxyBuf.read()) != -1) {
                    proxyToWebBuf.write(oneByte);
                }
                proxyToWebBuf.flush();
                proxyToWebBuf.close();
                clientToProxyBuf.close();
            }

            for( Iterator i = con.getHeaderFields().entrySet().iterator() ; i.hasNext() ;)  {
                Map.Entry mapEntry = (Map.Entry)i.next();
                if(mapEntry.getKey()!=null) {
                    //response.setHeader(mapEntry.getKey().toString(), ((List)mapEntry.getValue()).get(0).toString());
                    System.out.println(">> HEADER > " + mapEntry.getKey().toString() + "\t" + ((List)mapEntry.getValue()).get(0).toString());
                }
            }
            
            InputStream in = con.getInputStream();
            web2ProxyBuffer = new BufferedInputStream(in);
            proxy2ClientBuffer = new BufferedOutputStream(response.getPortletOutputStream());

 			byte [] byteArray = new byte[1024]; // intix: any array size is valid
			int intByteRead = web2ProxyBuffer.read(byteArray);
			while (intByteRead > 0) {
				// intix: print response-html/xml, must be the first line after while loop
				System.out.println(new String(byteArray, 0, intByteRead));
				proxy2ClientBuffer.write(byteArray, 0, intByteRead);
				intByteRead = web2ProxyBuffer.read(byteArray);			
			}                 
            proxy2ClientBuffer.flush();
            proxy2ClientBuffer.close();
            web2ProxyBuffer.close();
            con.disconnect();
        } catch(Exception e) {
            e.getMessage();
        } finally {
        	//
        }		
	}         
    
    protected String editJSP;
    protected String helpJSP;
    protected String viewJSP;

    private static Log _log = LogFactoryUtil.getLog(AjaxAlfrescoFolderBrowser.class);

}

5. When you have successfully deployed the portlet, open a browser, login, then add the new portlet to any page. Then you see the following:

Ajax Portlet calling Alfresco Webscripts

Ajax Portlet calling Alfresco Webscripts

Ajax Portlet calling Alfresco Webscript - view mode

Ajax Portlet calling Alfresco Webscript - view mode

Ajax Portlet calling Alfresco Webscripts - edit mode

Ajax Portlet calling Alfresco Webscripts - edit mode

Conclussions

  1. In the JSR-286 specifications (Portlet 2.0) now is possible to use serveResource() method andto request data. I use it as a servlet-proxy to do ajax calls to Alfresco.
  2. Exists a issue in Liferay 6.0.6 when setting ResourceResponse.HTTP_STATUS_CODE in the Portlet response (http://issues.liferay.com/browse/LPS-13039), this implies I have to manage HTTP_STATUS_CODE by parsing the Ajax HTML/XML responses.
  3. I have Liferay and Alfresco in different VMs (different IP and Ports) and I never had cross-domain issues thanks to Point #1 (serveResource nad portlet:resourceURL), but if you run into it is recommended that you use Apache HTTP server as a reverse-proxy.

You can download entire project (source code) and compiled from here:

  1. Source code (Liferay IDE project): AjaxAlfrescoFolderBrowser-portlet.zip
  2. Compiled: AjaxAlfrescoFolderBrowser-portlet.war

End.

Debugging is a task very important when building software, this allows software development with high level of quality (without bugs) because “debugging” enable you repeat cycles of “to do and to test” several time.

When developing Liferay portlets, Liferay Portal server will contain the portlets in a PC different of the PC of developer.

For this reason, debugging is an important task when working in distributed project teams with a single and/or centralized development server.

Then, remote debugging means you could debug an application or portlet on server from a different computer.

This post discusses how to use the Eclipse IDE for remote debugging on Liferay.

Versions used

  • Liferay Portal bundled with Tomcat: liferay-portal-tomcat-6.0.6-20110225.zip
  • Liferay IDE 1.2

Remote Debugging configuration

1. In your Liferay server side, if you have installed Tomcat bundle, to add this line into $TOMCAT_HOME/bin/setenv.bat or setenv.sh before you set any JAVA_OPTS:

In windows:

set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n

In Mac OSX the file will be as follow:

## intix - remote debugging
JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8 -Duser.timezone=GMT -Xmx1024m -XX:MaxPermSize=256m"

2. Restart Tomcat server. You should see in log (catalina.out) a message showing that port 8000 is opened and ready to use.

[...]
Listening for transport dt_socket at address: 8000
Sep 8, 2011 9:55:02 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: .:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
Sep 8, 2011 9:55:02 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Sep 8, 2011 9:55:02 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 456 ms
[...]

Note:
* If you have Liferay Portal configured as server into your Liferay IDE, do not launch Tomcat from this IDE. It is possible that remote debugging port is not opened.
* For a successfully debugging, run Tomcat from a shell as follow:

chilcano$ sh ./startup.sh ; tail -f ../logs/catalina.out

3. In your PC of development, to open Eclipse, Liferay IDE or Liferay Studio, in top menu to do click on “Run > Debug Configurations…”, will appear a dialog setup window where you have to create a new “Remote Java Application”.
Follow the image bellow:

The Debugging Configuration window

The Debugging Configuration window

4. Under “Remote Java Application” to add a entry, add values of hostname and port of remote server.
Follow the image bellow:

Debug Configuration - set hostname and port of remote server

Debug Configuration - set hostname and port of remote server

5. In “Debug Configurations…” dialog window, click “Source” and to add source code of your project to be debugged. At end, start debugging by click on “Debug” button.

Debug Configuration - attach source code and start debugging

Debug Configuration - attach source code and start debugging

Your IDE will ask you if you want to change to “Debug perspective”. You accept it.

6. In your IDE, put some “breakpoints” on your code.

7. Open your browser and go to your webapp’s URL, if you put breakpoints are in appropriate places, then Eclipse will show the line of code being executed.

You have nice debug on Liferay!.

Bye.

References:

Follow

Get every new post delivered to your Inbox.

Join 459 other followers