Creación de acciones personalizadas

RECU-0051 (Recurso Manual)

Descripción

Una acción es algo que el usuario puede hacer con un contenido. Son unidades de trabajo que pueden ser configuradas en tiempo de ejecución, ejem. check-in, check-out, cortar, pegar o borrar.

El repositorio de Alfresco esta provisto de muchos tipos de acciones que se pueden utilizar. En un espacio podemos encontrar acciones en cada contenido o listadas en el combo de mas acciones. Cuando vemos las propiedades de un contenido podemos ver acciones a la derecha de la página. También se pueden invocar acciones como parte de un flujo de trabajo simple.

Pero ademas es posible añadir tipos de acciones personalizados.

Una acción es algo que el usuario puede hacer con un contenido. Son unidades de trabajo que pueden ser configuradas en tiempo de ejecución, ejem. check-in, check-out, cortar, pegar o borrar.

El repositorio de Alfresco esta provisto de muchos tipos de acciones que se pueden utilizar. En un espacio podemos encontrar acciones en cada contenido o listadas en el combo de mas acciones. Cuando vemos las propiedades de un contenido podemos ver acciones a la derecha de la página. También se pueden invocar acciones como parte de un flujo de trabajo simple.

Pero ademas es posible añadir tipos de acciones personalizados.

Como construir acciones personalizadas

Una acción en su nivel mas básico consiste en una clase Action Executer y la declaración de su bean asociado. Normalmente también necesitemos un fichero de recursos para implementar la internacionalización y para personalizar la interfaz de usuario tendremos que crear paginas JSP para la inserción de valores para los parametros y un manejador que las relacione con la acción.

Antes de empezar una buena practica es buscar código dentro de Alfresco que sea similar y que se pueda aprovechar para codificar la acción. Seguir los mismos patrones que se utiliza en los fuentes de Alfresco para implementar nuestras personalizaciones hara mas fácil de mantener y compartir nuestro código con otros o incluso contribuir al proyecto Alfresco.

Para ilustrar los pasos a seguir para la implementación se va a usar como ejemplo la construcción de una acción personalizada que aplique un aspecto personalizado taggable que permitirá añadir una propiedad llamada tags (propiedad de texto multiple) al nodo y que ademas permita introducir algún tag por defecto.

Escribir un Action Executer

El Action Executer contiene la implementación de la acción. Es donde debemos poner el código que va a hacer el trabajo. Un Action Executer debe implementar la interfaz org.alfresco.repo.action.executer.ActionExecuter:

public interface ActionExecuter
{
   /**
    * Obtener la definición de la accion
    *
    * @return  the action definition
    */
   public ActionDefinition getActionDefinition();
       
   /**
    * Ejecutar el action executer
    *
    * @param action   la accion
    * @param actionedUponNodeRef     la referencia al nodo donde se ejecuta la accion
    */
   public void execute(Action action, NodeRef actionedUponNodeRef);
}

En esta definición podemos ver dos métodos. El primero nos servirá para obtener un objeto de definición de la acción. De este objeto podremos obtener detalles como el nombre de la acción o los parámetros. El segundo proporciona el método de ejecución, tendremos que proporcionarle la instancia de la acción y el nodo sobre el que realizar el trabajo.

Esta interfaz puede ser extendida directamente pero, como para que el servicio de acciones sea consciente de que la acción debe estar disponible para el cliente es necesario hacer mas cosas que solo implementar estos dos métodos, normalmente es preferible extender la clase ActionExecuterAbstractBase que se encuentra en el mismo paquete, la cual nos proporcionas mas facilidades para la creación de la acción.

Lo primero que tenemos que hacer pues es crear una nueva clase que extienda de la anterior:

public class TagActionExecuter  extends ActionExecuterAbstractBase
{
   /**
    * @ver org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(Action, NodeRef)
    */
   @Override
   public void executeImpl(Action action, NodeRef actionedUponNodeRef)
   {
      // TODO Rellenar con la implementación de la acción
   }
   /**
    * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
    */
   @Override
   protected void addParameterDefinitions(List<ParameterDefinition> paramList)
   {
      // TODO rellenar con la definición de parametros.
   }
}

Antes de empezar la implementación de la acción necesitamos los detalles del aspecto que queremos aplicar y una instancia del servicio de nodos con la que poder hacerlo.

Como ya hemos comentado los Action Executers serán configurados mediante una bean de Spring lo que significa que obtener cualquiera de los servicios del repositorio es cuestión simplemente de añadir un metodo setter que será usado por la configuración de Spring para inyectar el servicio requerido.

public class TagActionExecuter extends ActionExecuterAbstractBase
{
   /*** El servicio de nodos  */
   private NodeService nodeService;
  
    /*** Fijar el servicio de nodos
    * @param nodeService  El servicio de nodos
    */
   public void setNodeService(NodeService nodeService)
   {
      this.nodeService = nodeService;
   }
}

La configuración Spring que inyectará en el Action Executer el servicio de nodos se verá mas adelante.

El siguiente paso es parametrizar el Action Executer para así permitir aplicar algunos tags por defecto. La creación de una acción se basa en una definición que puede contener parámetros así las distintas instancias de la acción pueden tener valores distintos para ellas.

En este ejemplo queremos una acción a la que podamos proporcionar el nombre del tag a aplicar por defecto. Esto se especificará añadiendo una definición de parámetro en el método addParametersDefinitions que se vio antes.

public class TagActionExecuter extends ActionExecuterAbstractBase
{
   public static final String PARAM_TAG_NAME = "param-tags";
   /*** @ver org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) */
   @Override
   protected void addParameterDefinitions(List<ParameterDefinition> paramList)
   {
      // Añadir definiciones para la lista de parametros
      paramList.add(
         new ParameterDefinitionImpl(                                         // Crea una nueva definicion de parametro para la lista
            PARAM_TAG_NAME,                                                // El nombre usado para identificar al parametro
            DataTypeDefinition.TEXT                                            // El tipo del parametro
            true,                                                                                // Indica si el parametro es obligatorio
            getParamDisplayLabel(PARAM_TAG_NAME)));      // La etiqueta que se muestra para el parametro
   }
}

Cuando el método getActionDefinition sea llamado desde la clase base, se devolverá una definición que contenga una definición correcta de los parámetros. De esto se encarga la ParametizedItemAbstractBase que está en la jerarquía de herencia.

Así por último se puede rellenar la implementación del action executer:

protected void executeImpl(Action action, NodeRef actionedUponNodeRef)
{
   if (this.nodeService.exists(actionedUponNodeRef) == true)
   {
      // Añadir aspecto si este no está ya presente en el nodo
      QName tagAspect = QName.createQName("extension.tags", "taggable");
      if (this.nodeService.hasAspect(actionedUponNodeRef, tagAspect) == false)
      {
         this.nodeService.addAspect(actionedUponNodeRef, tagAspect, null);
      }
     
      // crear la lista de tags
      String tags = (String)action.getParameterValue("tags");
      List<String> tagsList = new ArrayList<String>();
      if (tags != null && tags.length() > 0)
      {
         StringTokenizer tokenizer = new StringTokenizer(tags, ",");
         while (tokenizer.hasMoreTokens())
         {
            tagsList.add(tokenizer.nextToken().trim());
         }
      }     
      // fijar los tags a la propiedad
      QName tagsProp = QName.createQName("extension.tags", "tags");
      this.nodeService.setProperty(actionedUponNodeRef, tagsProp, (Serializable)tagsList);
   }
}

Configuración en Spring

Para hacer que la acción personalizada este disponible debemos declarar como un bean de Spring. Para ello crearemos un fichero en alfresco/extension siguiendo la nomenclatura que definimos para las extensiones de ficheros de configuración de Spring, por ejemplo add-aspect-action-context.xml, y añadiremos la definición:

# Accion personalizada para añadir aspecto
  
   <bean id="tag" class="org.alfresco.sample.TagActionExecuter" parent="action-executer">
      <property name="nodeService">
         <ref bean="nodeService" />
      </property>
   </bean>

Como se vio antes en esta configuración podemos ver como se inyecta el servicio de nodos usando el método que definimos.

Para impedir que la interfaz de usuario permita seleccionar esta acción para ser ejecutada antes de que se le halla añadido los recursos necesarios para hacerlo podemos configurar la acción para ser privada. Esto significará que se podrá usar en el repositorio aunque el web cliente no la mostrará entre los tipos de acciones disponibles.

<bean id="tag" class="org.alfresco.sample.TagActionExecuter" parent="action-executer">
      <property name="nodeService">
         <ref bean="nodeService" />
      </property>
      <property name="publicAction">
         <value>false</value>
      </property>
   </bean>

Aplicación del modelo de localización

Toda acción tiene asociado un titulo y una descripción. Además cada parámetro tiene un titulo para mostrar. Todas estas cadenas deben ser obtenidas desde ficheros de recursos I18N para asegurar que el repositorio permanece internacionalizado.

La clase base ActionExecuterAbstractBase busca, en alguno de los ficheros de recursos cargados, las entradas .title y .description.

Ademas debemos usar el método getParamDisplayLabel para obtener el mensaje id para el parámetro. Buscara el los ficheros de recursos un id con la siguiente estructura ..display-label. Este mensaje se usará cuando se cree la definición del parámetro como se vio antes.

Así crearemos un fichero tag-action-messages.properties dentro del directorio alfresco/extension y añadiremos las siguientes lineas para Internacionalizar la acción personalizada:

# Mensajes para la acción personalizada
   tag.title=Añadir aspecto a item
   tag.description=Añadirá un aspecto al item seleccionado.
   tag.param_tags.display-label =El nombre del aspecto a aplicar al item.

Este fichero debe ser registrado a través de Spring para que los mensajes puedan ser cargados así que se tendrá que añadir en nuestro fichero de extensión de Spring un nuevo bean para hacerlo, por ejemplo:

<bean id="tag-action-messages" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
   <property name="resourceBundles">
      <list>
         <value>alfresco.extension.tag-action-messages</value>
      </list>
   </property>
</bean>

Crear una página JSP

A partir de ahora se va a mostrar que es lo que se necesita personalizar en el cliente web para poder preguntar a los usuarios por los valores de los parámetros en una acción personalizada.

Aunque los asistentes para acciones han sido integrados en el nuevo Framework de Asistentes los JSPs que recogen parametros para acciones y condiciones son aun JSPs en toda regla como si no fueran mostrados como el contenedor de asistentes. Esto significa que se debe tener la estructura completa de una página en el JSP de la acción, la forma mas fácil de hacerlo es copiando una existente.

Si se coge como base la página jsp/actions/add-features.jsp solo tendremos que sustituir la lista desplegable por un campo de texto y cambiar algunas etiquetas. Así se copia esta pagina en el directorio jsp/extension y se re-nombra como tag.jsp. Una vez que se hallan hecho los cambio necesarios la pagina tendrá este aspecto:

<r:page titleId="title_action_tag">
<f:view>
  
   <%-- load a bundle of properties with I18N strings --%>
   <f:loadBundle basename="alfresco.messages.webclient" var="msg"/>
   <f:loadBundle basename="alfresco.extension.webclient" var="customMsg"/>
  
   <h:form acceptCharset="UTF-8" id="tag-action">
   ......
   <tr>
      <td><nobr><h:outputText value="#{customMsg.tags}:"/></nobr></td>
      <td width="95%">
         <h:inputText value="#{WizardManager.bean.actionProperties.tags}" size="50" maxlength="1024" />
      </td>
   </tr>
   ......

Implementación del manejador de la acción

Para integrar la accion dentro de los asistentes basados en reglas (Ejecutar accion, Crear regla....) necesitamos implementar un manejador. Esta clase, que normalmente extiende de org.alfresco.web.bean.action.handlers.BaseActionHandler, será responsable de conducir al asistente a la pagina de recogida de parámetros y guiar los parámetros entre el asistente y el repositorio.

Si la página que que recoge los parámetros requiere alguna inicialización por defecto se podrá sobrescribir el método setupUIDefaults(), en este ejemplo no se necesitará.

Se creará una clase Java llamada TagActionHandler con los siguientes metodos:

  • El método getJSPPath(), que debe devolver el camino a nuestro JSP (/jsp/extension/tag.jsp).
  • El método prepareForSave(), que coloca los tags que introduce el usuario en la propiedades del repositorio cuyo mapeo a sido pasado por parametro.
  • El método prepareForEdit(), que por el contrario coge los tags guardados en la acción y colocarlas en las propiedades mapeadas para el asistente.
  • El método generateSummary(), que se usa para generar una cadena de resumen para la acción, normalmente se incluyen los parámetros introducidos por el usuario.

El código de fichero TagActionHandler.java debe ser parecido a esto:

public class TagActionHandler extends BaseActionHandler
{
   public static final String PROP_TAGS = "tags";
  
   public String getJSPPath()
   {
      return "/jsp/extension/tag.jsp";
   }
   public void prepareForSave(Map<String, Serializable> actionProps,  Map<String, Serializable> repoProps)
   {
      repoProps.put(TagActionExecuter.PARAM_TAGS, (String)actionProps.get(PROP_TAGS));
   }
   public void prepareForEdit(Map<String, Serializable> actionProps,  Map<String, Serializable> repoProps)
   {
      actionProps.put(PROP_TAGS, (String)repoProps.get(TagActionExecuter.PARAM_TAGS));
   }
   public String generateSummary(FacesContext context, IWizardBean wizard, Map<String, Serializable> actionProps)
   {
      String tags = (String)actionProps.get(PROP_TAGS);
      if (tags == null)
      {
         tags = "";
      }
     
      return MessageFormat.format(Application.getMessage(context, "add_tags"),
            new Object[] {tags});
   }
}

Para generar el resumen usamos una nueva cadena id, add_tags. Tendremos que definirla en nuestro fichero webclient.properties personalizado así:

add_tags=Add tags ''{0}''

El último paso consiste en registrar el nuevo manejador. Esto lo haremos en el fichero de configuración web-client-config-custom.xml añadiendo un nuevo elemento config con la condición "Action Wizards":

<config evaluator="string-compare" condition="Action Wizards">
   <action-handlers>
      <handler name="tag" class="org.alfresco.sample.TagActionHandler" />
   </action-handlers>
</config>