Entrades amb etiqueta liferay .

Liferay Dynamic Queries

Data de publicació 22/07/16 15:03

Liferay define una serie de procedimientos (API) para obtener datos de la base de datos. Liferay Dynamic es una de ellos. Esta forma puede añadir complejidad a las búsquedas que hagamos en la base de datos respecto a las finders que podemos encontrar en los servicios locales de las entidades.

Unos casos de uso (especificados por el equipo de Liferay) pueden ser los siguientes:

  • Hacer agregados como MAX, MIN y AVG entre otros.
  • Necesitas ciertas columnas en vez de objetos tipificados.
  • Añadir complejidad con OR/AND.
  • Otros.

Con un ejemplo se ve mejor: disponemos de una entidad Proyecto creada con el Service Builder de Liferay. Tenemos un campo fecha de creación (creation_date) y queremos encontrar los proyectos creados en un mes especificado. Necesitaremos una query que haga la siguiente acción:

  SELECT *
  FROM Proyecto
  WHERE [date_ini] <= creation_date
    AND creation_date <= [date_fin]

Al no poder realizarla con los finders de la entidad, tenemos que buscar una alternativa. Como las custom sql o las Dynamic Query. Aqui tenéis un ejemplo:

  /*
  * Creamos un objeto dynamicQuery para la clase Proyecto (que hemos generado)
  * Si la Dynamic Query fuera sobre una clase propia de Liferay (User por ejemplo) se debería
  * añadir el campo PortalClassLoaderUtil.getClassLoader()
  */

  DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(Proyecto.class);

 

Ahora podemos realizar 2 acciones:

  • Añadir criterios de búsqueda.
  • Añadir criterios de ordenación.

Criterios de búsqueda

Aqui tenemos que crear objetos con la Factoria RestrictionsFactoryUtil, que nos devuelve objetos de tipo Criterion. Esto nos sirve para construir lógicas como las siguientes:

  • ilike eq (igual)
  • gt (más grande estricto)
  • ge (más grande o igual)
  • métodos AND y OR
  • etc

Criterios de ordenación

Aqui usaremos la factoria OrderFactoryUtil, que nos devuelve objetos de tipo Order. Aqui define dos métodos:

  • Order asc(String nombrePropiedad)
  • Order desc(String nombrePropiedad)

Una vez hechos los Criterios y las Ordenaciones se añaden al objeto dynamic query así de fàcil:

  • dynamicQuery.add(criterio)
  • dynamicQuery.addOrder(order)

Finalmente, solo nos faltará ejecutar la dynamic query, pero aqui volvemos al ejemplo con todo lo aprendido:

  Criterion criterion1 = RestrictionsFactoryUtil.ge("date_ini", searchdate);
  Criterion criterion2 = RestrictionsFactoryUtil.le("date_fin", searchdate);
  Criterion criterion = RestrictionsFactoryUtil.and(criterion1, criterion2);
  dynamicQuery.add(criterion);
  List<proyecto> list = ProyectoLocalServiceUtil.dynamicQuery(dynamicQuery);

¡Espero que os sirva de ayuda!

Controlar meta tags en Liferay de forma dinámica

Data de publicació 15/07/16 13:37

Liferay permite añadir meta tags desde el apartado Site Pages > Page > SEO del panel de control, pero estos meta tags se aplican a la página sin importar lo que contenga y sus opciones son bastante limitadas.

Esta funcionalidad en según qué casos no es suficiente.

Vamos a poner un ejemplo para entender mejor lo que queremos hacer.

Tenemos un portlet que genera las fichas de todas las personas que pertenecen o han pertenecido a una empresa y Google, por defecto, nos las va a indexar todas.

Lo que queremos es mantener la ficha de todas las personas en nuestra web pero, por otro lado, no queremos que Google indexe las de las personas que se den de baja. Por tanto, lo que necesitamos es añadir (solo a esas fichas) el meta tag que le indica al robot que no indexe ese contenido.

Añadiendo el siguiente código dentro del jsp que genera la ficha de la persona podemos condicionar el meta tag para especificar cómo queremos que trate Google a esa página.

 

if (persona.dadaDeBaja()) {
    Element metaElement = portletResponse.createElement("meta");

    metaElement.setAttribute("name", "robots");
    metaElement.setAttribute("content", "noindex");

    portletResponse.addProperty(MimeResponse.MARKUP_HEAD_ELEMENT, metaElement);
}

 

Con esto, cuando la persona que queramos mostrar haya sido dada de baja, mostrará la ficha normalmente, pero dentro del <head> de la página habrá un nuevo meta tag que indicará a los buscadores que no se quiere indexar ese contenido.

 

<head>
    …
    <meta content="noindex" name="robots">
    …
</head>

 

Con este sistema se pueden manejar los elementos de la página al gusto. Nosotros hemos puesto tan sólo un ejemplo, pero hay muchas más posibilidades.

Aquí se puede encontrar más información sobre las distintas opciones que puede interpretar el robot de Google.

Espero que os sirva. Un saludo.

Liferay Security Manager con PACL

Data de publicació 20/06/16 13:46

¡Muy buenas!

 

Hace unas semanas tuvimos la necesidad de añadir una lista de control de acceso al portal o Portal Access Control List (PACL) a uno de nuestros portlets. La razón es que algunos portales Liferay pueden tener activado el Security Manager. Básicamente lo que hace es controlar todas las acciones que hacen los portlets i que no ejecuten código malicioso. El Security Manager distinguirá un código malicioso (no tiene porqué ser malicioso per se) si éste hace un acceso no registrado en la lista PACL.

 

 

Una vez sabido ésto, vamos a explicar como generar el listado para tu portlet. Primero deberemos activar tu portal en desarrollo en modo security, se ha de añadir la siguiente línea en el portal-ext.properties i reiniciar el tomcat:

 

portal.security.manager.strategy=liferay

 

Modificamos el archivo liferay-plugin-package.properties del portlet y añadimos la siguiente línea:

 

security-manager-enabled=generate

 

Compilamos y hacemos deploy en el portal, entonces podremos ver cómo se genera una carpeta a la altura del tomcat y contendrá los accesos que haga nuestro portlet:

 

[liferay.home]/pacl-policy/[servletContextName].policy

 

Para éste punto es importante, ejecutar todas las funcionalidades que tenga tu portlet para registrar todos los accesos que haga. Los accesos pueden ser des de llamadas a Local Services de Liferay, llamadas a apis externas o accesos a carpetas en el mismo servidor.

 

Una vez hecho esto, tendremos que copiar el contenido generado en el archivo .policy y añadirlo a el liferay-plugin-package.properties.

 

Para comprobar que hemos hecho bien el procedimiento, modificamos la propiedad security-manager-enabled a true en el liferay-plugin-package.properties:

 

security-manager-enabled=true

    

#Reglas generadas PACL

security-manager-get-bean-property[portal]=\
           com.liferay.portal.kernel.exception.SystemException,\
           com.liferay.portal.kernel.language.LanguageUtil,\
           com.liferay.portal.kernel.util.PrefsPropsUtil,\
           jodd.util.ReflectUtil

    security-manager-hook-language-properties-locales=\
           ca,\
           en,\
           es

#etc

 

Y, ejecutamos las funcionalidades a falta para ver si el Security Manager encuentra algo no especificado en el PACL.

 

Con esto tenemos que para tener un control de seguridad podemos activarlo en nuestro portal (recomendable si tienes portlets de terceros) y gracias a la lista PACL podremos comprobar a qué accede el portlet.


¡Un saludo!

Compartir la Capa de Servicios

Data de publicació 27/05/16 12:17

¡Buenas!

 

Hoy os explico diferentes maneras de compartir la capa de servicios que hayáis creado con el service builder entre diferentes portlets.

 

Primero, tenemos que contar que tenéis un portlet base base-portlet, el cuál habéis creado entidades y servicios con el service builder. Si quisieramos que un segundo portlet y un hook, por ejemplo, accedieran a estos servicios, replicarlos para cada plugin sería ineficiente y causante d’errores de inconsistencia en el futuro.

 

La mejor opción siempre es que todos los plugin accedan a la misma capa:


Untitled.png

 

 

Propondremos dos opciones:

 
  • Crear los diferentes portlets y hooks en un mismo plugin.

  • Compartir la capa de servicios a nivel de tomcat.

 

Crear los diferentes portlets y hooks en un mismo plugin

 

La idea es crear diferentes portlets en un mismo plugin de Liferay y tener unos servicios creados con el Service Builder. De esta manera, estos servicios seran disponibles y accesibles por cada portlet o hook dentro del plugin.

 

Compartir la capa de servicios a nivel de tomcat

 

Si la propuesta anterior no fuera posible, también podriamos exportar la capa de servicios mediante un archivo jar. Cuando ejecutas el service builder, este archivo se genera en: “your-portlet/docroot/WEB-INF/lib/your-portlet-service.jar”.

 

Este jar nos da acceso a los local service que hayas programado. Para que todos los plugin de tu instancia puedan usarlo este jar lo tienes que añadir en este directorio del servidor: “<<Liferay-tomcat>>tomcat-x.x.x/lib/ext”.

 

Recuerda, para cualquier actualización de los servicios, se tendrá que reemplazar el jar y, además, reiniciar el tomcat.


¡Espero que os haya ayudado!

Bug de regresión - Liferay 6.2 CE GA5

Data de publicació 20/05/16 11:24

Trabajando con la versión 6.2 CE GA5 hemos detectado un bug de regresión relacionado con la gestión de los idiomas, concretamente la incidencia de Liferay LPS-60313. Este bug solo afecta si se trabaja con entornos multi-idioma. En esta entrada de blog vamos a explicar los pasos que hemos seguido para resolver este problema.

Primero de todo, nos aseguramos que la incidencia detectada no esté relacionada con nuestros propios desarrollos. Se vio claramente ya que apareció al hacer el upgrade a la versión 6.2 CE GA5, aún así se revisaron los desarrollos relacionados para estar del todo seguros.

En este punto se empieza a investigar en los foros de Liferay y en el resto de sites relacionados; con esta incidencia en concreto se llegó a preguntar directamente en el foro Liferay y como casi siempre se nos orientó sobre el origen del bug.

Aquí es donde se empieza a aclarar el problema, los comentarios recibidos te permiten hacer un estudio más exhaustivo y llegamos a la incidencia reportada en el sistema de incidencias de Liferay.

Liferay utiliza JIRA para gestionar sus incidencias, con un simple vistazo tienes toda la información necesaria, con la posibilidad de profundizar en los detalles. 

El siguiente paso lógico, es buscar si existe solución por parte de Liferay para evitar realizar trabajos innecesarios. Pero revisando la incidencia JIRA se ve claramente que solo hay solución para las versiones Enterprise (6.2.X EE). En el detalle de la incidencia se explica la causa del problema, de todas formas al no existir fix para la versión CE nos obliga a crear un plugin EXT para su resolución.

Así que el último paso es la creación de un plugin EXT que modificará el CORE de Liferay para cambiar el código que provoca este problema en cuestión. Este EXT en si no es mucho, concretamente solo se modifica una línea del CORE.

Como vemos, la solución (a nivel de programación) ha sido muy sencilla, pero tiene graves implicaciones para la continuidad de la versión que se está utilizando actualmente (6.2.4 CE GA5):

  • La inclusión de un plugin EXT en el sistema. Opción no recomendada por Liferay pero sin fix no queda otra opción.
  • Tener en cuenta la existencia de este nuevo EXT en el caso de hacer futuros upgrade de la instancia de Liferay. Si es necesario su continuación en el caso que no se haya solucionado el bug o si por el contrario se tiene que eliminar.

Espero que os haya servido de ayuda.

Modificar el tiempo de sesión de Liferay

Data de publicació 29/04/16 09:30

La duración de la sesión de Liferay por defecto es de 30 minutos. Hoy os mostraremos como modificar este valor.

Podemos encontrar este valor dentro del portal.properties de Liferay.

  • portal.properties (Liferay src): /portal/portal-impl/classes
  • portal.properties (Server): /webapps/ROOT/WEB-INF/lib/portal-impl.jar
    #
    # Specify the number of minutes before a session expires. This value is
    # always overridden by the value set in web.xml.
    #
    session.timeout=30

 

Al estar como propiedad de portal podemos sobrescribirla des del portal-ext.properties o el portal-setup-wizard.properties

  • portal-ext.properties: /webapps/ROOT/WEB-INF/classes/
  • portal-setup-wizard.properties: /liferay-portal-x.x-xx-xxx/
    session.timeout=240

 

Tal y como indican en el propio portal.properties, este cambio no es suficiente, ya que prevalece el valor que se encuentra en el archivo web.xml

  • web.xml (Liferay src): /portal-web/docroot/WEB-INF/
  • web.xml (Server): /webapps/ROOT/WEB-INF/
<session-config>
    <session-timeout>30</session-timeout>
</session-config>

 

La forma recomendada de modificar este tipo de propiedades es mediante un Ext Plugin.

Al crear un nuevo Ext Plugin, se nos generarán diversos directorios y archivos. El que queremos modificar nosotros ya se encuentra en (/docroot/WEB-INF/ext-web/docroot/WEB-INF/web.xml ). Solamente es necesario editarlo y añadir únicamente el tag que queremos modificar. No es necesario como en otros casos poner todo el contenido, ya se encargará de hacer el merge con el archivo original.

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <session-config></session-config>
</web-app>

En nuestro caso eliminamos el tag <session-timeout> para que tome como válido el valor que pongamos en el portal-ext.properties, siendo más sencillo controlar dicho valor.

Una vez aplicado el EXT podremos observar que el archivo web.xml de nuestro servidor que antes contenía <session-timeot> dentro de <session-config>, ahora ya no tiene esa propiedad. Desde este momento ya podremos modificar el tiempo de sesión desde las propiedades del portal.

Recordad que para aplicar un EXT es necesario reiniciar después de hacer el deploy, y si ya existía un EXT anterior, según la situación, puede ser necesario limpiar Liferay antes de aplicar de nuevo un Ext Plugin (más información aquí).

Un saludo.

Filtros de búsqueda en sesión

Data de publicació 11/04/16 11:28

Todos nos hemos encontrado alguna vez, al programar portlets en Liferay, con la necesidad de presentar al usuario un listado de elementos y un pequeño formulario con una serie de campos para filtrar los resultados de dicho listado. Si estamos usando el search container de Liferay, nos encontraremos con que tenemos que guardar los valores de los filtros si queremos mantenerlos al clicar en algún botón del search container. Por ejemplo, si tenemos un listado de casas, y filtramos por año de construcción, si el resultado tiene varias páginas de registros, nos encontraremos que al clicar en el botón de siguiente página, habremos perdido el filtro de año de construcción.

La manera "Estándard" como se hace esto en Liferay es guardando los valores de los filtros como preferencias del portlet, de manera que siempre que vayamos al listado nos encontraremos con los filtros que teníamos puestos en la última búsqueda que hicimos. Esto es así incluso si nuestra sesión ha expirado o si la hemos cerrado. Al volver a entrar nuestras credenciales e ir al listado, veremos los filtros tal y como estaban la última vez.

Hay situaciones en las que queremos que la funcionalidad sea similar a esta, pero que al cerrar sesión, o cuando la sesión expire, los filtros se borren y aparezcan los valores por defecto de los mismos al volver a entrar al listado. Para este caso, lo que habría que hacer sería guardar los filtros en la sesión.

Para conseguir esto, tendremos que tener, en el jsp de formularios de filtro, algo similar a esto:

...
String keywordFilter = SessionParamUtil.getString(request, "keywordFilter""");
String descriptionFilter = SessionParamUtil.getString(request, "descriptionFilter""");
String commentsFilter = SessionParamUtil.getString(request, "commentsFilter""");
...

 

Aquí definimos las variables donde guardaremos los filtros y, en vez de usar la clase ParamUtil, como es habitual, usaremos SessionParamUtil, que busca el parámetro también en la sesión (no sólo en la request).

Además, en nuestro controlador tendremos que poner el código encargado de recoger los filtros de la request y de guardarlos en la sesión:

public void render(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException{
        PortletSession portletSession = renderRequest.getPortletSession();
        Enumeration<String> params = renderRequest.getParameterNames();
        while(params.hasMoreElements()){
            String param = params.nextElement();
            if(!param.endsWith("Filter")) continue;
            portletSession.setAttribute(param, renderRequest.getParameter(param), PortletSession.PORTLET_SCOPE);
        }
        super.render(renderRequest, renderResponse);
}

 

En este caso hemos modificado la función render de nuestro controlador para que guarde todos los parámetros de la request cuyo nombre acabe en "Filter".

voilà, ya tenemos nuestros filtros guardados en sesión!

 

Esperamos que os haya sido de ayuda, hasta la próxima!!

A fondo: liferay-portlet.xml

Data de publicació 02/12/15 12:57

¡Hola!

 

En esta entrada voy a explorar un poco lo que podemos llegar a hacer con el archivo 'liferay-portal.xml'. Este archivo describe mejoras del portlet JSR-286 en nuestro portal de Liferay.

La información mostrada va referida a la versión 6.2 por lo que no puedo asegurar que todo lo que especifique funcione para otras versiones de Liferay Portal.

 

Cuando generamos un nuevo portlet podemos autogenerar este archivo y ésta es su estructura base:

 

<liferay-portlet-app>

       <portlet>

              <portlet-name>my-greeting</portlet-name>

              <icon>/icon.png</icon>

              <instanciable>false</instanciable>

              <header-portlet-css>/css/main.css</header-portlet-css>

              <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>

              <css-class-wrapper>my-greeting-portlet</css-class-wrapper>

       </portlet>

       <role-mapper>

              <role-name>administrator</role-name>

              <role-link>Administrator</role-link>

       </role-mapper>

       <role-mapper>

              <role-name>guest</role-name>

  <role-link>Guest</role-link>

       </role-mapper>

       <role-mapper>

              <role-name>power-user</role-name>

  <role-link>Power User</role-link>

       </role-mapper>

       <role-mapper>

              <role-name>user</role-name>

              <role-link>User</role-link>

       </role-mapper>

</liferay-portlet-app>

 

 

Para cada portlet tenemos las siguientes etiquetas base:

  • portlet-name: Tiene el nombre especificado para el portlet. Es necesario que tenga el mismo que el especificado en el archivo portlet.xml.
  • icon: Ruta hasta la imagen icono del portlet.
  • instanciable: Indica si un portlet se puede instanciar más de una vez en una misma página.
  • header-portlet-css: Ruta hasta un archivo css. Se añadirá dentro de la etiqueta <head> de la página (debido a la parte del nombre header).*
  • footer-portlet-javascript: Ruta hasta un archivo js. Se añadirá al final de la página, justo antes de la etiqueta </body> (debido a la parte del nombre footer).*
  • css-class-wrapper: Clase que se añade en el html al renderizar el portlet, se suele usar para aplicar estilos propios al portlet.

*Pueden existir varias etiquetas de este tipo en un mismo portlet.

 

Ahora que ya tenemos lo básico, voy a especificar unas cuantas cosas que podemos realizar:

 

Añadir el portlet en el Panel de Control

 

Para conseguir esto deberemos añadir dos etiquetas más: '<control-panel-entry-category>' y '<control-panel-entry-weight>'.

 

La primera etiqueta especifica en que sección de nuestro panel de control se colocará nuestro portlet, tenemos las siguientes:

 
  • Panel de control: ‘users’, ‘sites’, ‘apps’ y ‘configuration’.
  • Administración del sitio web: ‘site_administration.pages’, ‘site_administration.content’, ‘site_administration.users’ y ‘site_administration.configuration’.
  • Mi cuenta: ‘my’.
 

La segunda etiqueta determina el peso que tendra tu portlet dentro de cada sección. A más peso más abajo estará tu portlet en la lista.

 

Hacer un cron

 

Este apartado ya lo especificamos en otra entrada de blog hecha por Sergi.

 

Además

 

A parte de lo mencionado anteriormente, este archivo también sirve para enlazar a clases que hagan una funcionalidad especifica. Por ejemplo, implementar búqueda e indexación, crear notificaciones para los usuarios y especificar friendly url del portlet entre otras cosas.

 

Para finalizar os dejo el listado con todas las etiquetas que pueden ir dentro del archivo 'liferay-portal.xml'.

 

Espero que os haya ayudado, ¡un saludo!


 

Liferay Custom SQL

Data de publicació 11/03/15 15:06

El Service Builder de Liferay te permite crear tu propio modelo de negocio y, además, te crea los servicios los cuáles usarán el modelo que has especificado en el service.xml.

Liferay también te puede generar finders para devolver datos o colecciones de datos de la persistencia. Aún así, a veces necesitamos más lógica a la hora de hacer una consulta. Especifico estas tablas a modo de ejemplo:

Empleado [ID, NOMBRE, DNI, FECHA_NACIMIENTO, DEPARTAMENTO_ID, etc]
Departamento [ID, CIUDAD, NUM_EMPLEADOS, etc]

 

No entraremos como hacer finders con el service.xml pero sí hace falta remarcar que esos finders se hacen a partir de los datos que tiene una entidad. Por ejemplo: podemos crear un finder en la entidad Empleado que devuelva todos los empleados con fecha de nacimiento y departamento indicados.

Para hacer un ejemplo, encontrar todos los empleados que tengan su departamento en una ciudad especifica ya no es posible hacerlo des de el service.xml, por lo tanto tenemos que hacer una custom sql.

Antes de empezar creando la custom sql se ha de añadir (si no esta añadido ya) un archivo default.xml en la ruta 'docroot/WEB-INF/src/custom-sql/' y, des de éste, añadiremos el archivo que vayamos a crear:

<?xml version="1.0"?>
<custom-sql>
    <sql file="custom-sql/empleado.xml" />
</custom-sql>
 

A partir de aquí, la creación son 3 pasos:

1. Especificar tu custom SQL

En el mismo directorio que se encuentra default.xml creamos un archivo empleado.xml. Tendrá la siguiente forma:

<?xml version="1.0" encoding="UTF-8"?>
<custom-sql>
    <sql id="[fully-qualified class name + method]">
        <![CDATA[
            SQL QUERY
        ]]>
    </sql>
    <sql id="[fully-qualified class name + method2]">
        <![CDATA[
            SQL QUERY 2
        ]]>
    </sql>
</custom-sql>


El "fully-qualified class name" ha de ser de la forma: "com.[proyecto].service.persistance.EntidadFinder".
La sentencia sql irá entre els tags " <![CDATA[ " y " ]]> ":

<?xml version="1.0" encoding="UTF-8"?>
<custom-sql>
    <sql id="com.proyecto.service.persistance.EmpleadoFinder.getEmpleadosPorCiudad">
        <![CDATA[           
            Select empleado.*
               From Empleado empleado
               Left outer join Departamento departamento
                  on empleado.depatamentoId=departamento.id
               where (departamento.ciudad Like ?)
        ]]>
    </sql>
</custom-sql>


Esta custom sql es como un formulario que se rellenará con cada petición. En este caso se substituirá el símbolo '?' por el nombre de una ciudad.

 

2. Método Finder

El Service Builder no crea la clase "EntidadFinderImpl.java", por lo que nos tocará a nosotros este paso. Se crea en el directorio "com/[proyecto]/service/persistance/", en nuestro caso haremos la clase "EmpleadoFinderImpl.java":

package com.proyecto.service.persistence;
 
public class EmpleadoFinderImpl extends BasePersistanceImpl<Empleado>
                                implements EmpleadoFinder {
 
}


Una vez hecho esto, tenemos que hacer la acción de 'build service'. Se creará a partir de la clase "EmpleadoFinderImpl.java", la interfaz "EmpleadoFinder.java" y la clase "EmpleadoFinderUtil.java".

Ahora podemos completar nuestra clase "EmpleadoFinderImpl.java":

package com.proyecto.service.persistence;
 
public class EmpleadoFinderImpl extends BasePersistanceImpl<Empleado> 
          implements EmpleadoFinder {
    public static String GET_EMPLEADOS_POR_CIUDAD =
                     EmpleadoFinder.class.getName() + "getEmpleadosPorCiudad";
     
    public List<Empleado> getEmpleadosPorCiudad(String ciudad, int begin, int end) 
        throws SystemException {
        Session session = null;
        try {
            session = openSession();
 
            /*Recuperas la función custom sql que has creado en el xml*/
            String sql = CustomSQLUtil.get(GET_EMPLEADOS_POR_CIUDAD);
 
            /*Creas un objeto sql a partir de la custom sql*/
            SQLQuery q = session.createSQLQuery(sql);
            q.setCacheable(false);
            /*Añades la entidad empleado que será el objeto de retorno*/
            q.addEntity("EMPLEADO", EmpleadoImpl.class);
 
            QueryPos qPos = QueryPos.getInstance(q);
            /*Añades en orden los parámetros necesarios para ejecutar la sql*/
            qPos.add(ciudad);
 
            return (List<Empleado>) QueryUtil.list(q, getDialect(), begin, end);
        catch (Exception e) {
            try {
                throw new SystemException(e);
            catch (SystemException se) {
                se.printStackTrace();
            }
        finally {
            closeSession(session);
        }
        return null;
    }
}

Volvemos a hacer el 'build service' i ya tenemos creada nuestra función custom.
 

3. Enlazar con el service

Liferay te genera los servicios para tu modelo de negocio, para acceder a tu custom sql es recomendable que tenga la misma forma de acceso que estos servicios.

En este paso tan solo tenemos que abrir el archivo LocalServiceImpl (o ServiceImpl dependiendo si los servicios son locales o remotos) y añadir una función que llame al finder:

public class EmpleadoLocalServiceImpl extends EmpleadoLocalServiceBaseImpl {
     
    public List<Empleado> getEmpleadosPorCiudad(String ciutat, int begin, int end)
             throws SystemException {
        EmpleadoFinderUtil.getEmpleadosPorCiudad(ciutat,begin,end);
    }
}


De nuevo, y por último, volvemos a hacer 'build service' para que se cree el servicio. Ahora des de tu portlet podrás llamar a "EmpleadoLocalServiceUtil.getEmpleadosPorCiudad("Barcelona",0,num);"

Un saludo y, ¡espero que os haya servido de ayuda!

Liferay - Validación CAPTCHA en formularios

Data de publicació 30/04/15 08:09

Una buena forma de validar si los formularios que tenemos en nuestro portal son rellenados por un humano o un autómata, es añadir unCompletely Automated Public Turing test to tell Computers and Humans Apart’ o lo que conocemos todos como un CAPTCHA.

 

Para eso Liferay nos facilita una librería para generar los CAPTCHA:

 

 

Básicamente tenemos que modificar nuestro jsp y la clase java donde se tratan las acciones del portlet.

 

En el jsp:

Al principio añadiremos las clases para las excepciones y, después, tan solo hace falta añadir dentro del form, que hayas hecho, las sentencias para generar el CAPTCHA:

 

<%@page import="com.liferay.portal.kernel.captcha.CaptchaTextException"%>

<%@page import="com.liferay.portal.kernel.captcha.CaptchaMaxChallengesException"%>

 

<liferay-ui:error exception="<%= CaptchaTextException.class %>" message="text-verification-failed" />

<liferay-ui:error exception="<%= CaptchaMaxChallengesException.class %>" message="maximum-number-of-captcha-attempts-exceeded" />

<portlet:actionURL  var="addElementURL" name="addElement" />

<aui:form action="<%= addElementURL %>" method="post" name="fm">

<aui:input  name="firstName" value=""/>

<aui:input  name="lastName" value=""/>

<aui:input  name="email" value=""/>

<portlet:resourceURL var="captchaURL"/>

<liferay-ui:captcha url="<%=captchaURL%>" />

<aui:button type="submit" />

</aui:form>

 

En el java:

Dentro de la función que se ejecuta por la acción se ha de validar en la parte del servidor que el Captcha se ha introducido bien:

 

public void addElement(ActionRequest actionRequest,

ActionResponse actionResponse)

throws IOException, PortletException {

String firstName=ParamUtil.getString(actionRequest,"firstName");

String lastName=ParamUtil.getString(actionRequest,"lastName");

String lastName=ParamUtil.getString(actionRequest,"email");

try{

        CaptchaUtil.check(actionRequest);
        if (!validateParameters(firstName,lastName,email)) {
            SessionErrors.add(actionRequest, "error");
        }
        else {

         System.out.println("Captcha success!");
            SessionMessages.add(actionRequest, "success");
        }
    }
    catch (CaptchaTextException cte) {

System.out.println("Captcha error!");

SessionErrors.add(actionRequest,

CaptchaTextException.class.getName());

}

}

 

Además, añadimos la función para servir el Captcha en la misma clase Java:

 

@Override

public void serveResource(ResourceRequest resourceRequest,

             ResourceResponse resourceResponse)

           throws  IOException, PortletException {

try {

 

            CaptchaUtil.serveImage(resourceRequest, resourceResponse);

      }

      catch (Exception e) {

            System.out.println("Captcha serve error!");

      }

}

 

Y así, ¡ya debería funcionar nuestro formulario!

 

Tareas Programadas en Liferay

Data de publicació 19/10/15 17:25

Para llevar a cabo un proyecto a menudo requerimos de procesos en Background. Una solución muy extendida es desarrollar estas tareas con código aislado, usando tecnologías que permiten su ejecución y programarlas mediante herramientas propias del sistema operativo. Pues bien, para proyectos Liferay no es necesario optar por este tipo de soluciones: podemos desarrollar y programar tareas en nuestros propios plugins mediante Liferay Quartz Scheduler.

 

Ventajas

Icon
  • Posibilidad de usar servicios de nuestro Back End.
  • Unificación de código (mejora en seguimiento y tener el repositorio en común entre otros).
  • Independencia de tecnologías y agentes externos.
  • Sencillez.

 

Consideraremos que ya se dispone de un portlet. El framework usado es indiferente (MVC Portlet, JSP Portlet, etc.)

 

Veamos cómo podemos programar una tarea en Liferay:

 

  1. 1. Creación de la clase:

    Dentro del portlet creamos una clase que implemente la interfaz 'com.liferay.portal.kernel.messaging.MessageListener'.
     
  2.  
  3. 2. Implementar el método 'receive()':
     

     

     
    package com.sonicon.testSchedule;
     
    import com.liferay.portal.kernel.messaging.Message;
    import com.liferay.portal.kernel.messaging.MessageListener;
    import com.liferay.portal.kernel.messaging.MessageListenerException;
    public class MessageListenerTest implements MessageListener {
        @Override
        public void receive(Message message) throws MessageListenerException {
            //Código a ejecutar aquí
        }
    }

     

    Nuestro método 'receive()' se ejecutará periódicamente en función de la configuración que definamos en el siguiente punto.

     

  4. 3. Registrar la clase en Liferay:


    Añadir el scheduler de la clase en 'liferay-portlet.xml' (tras la etiqueta '<icon>' del portlet):

     

     
    <portlet>
    ...
    <icon>/icon.png</icon>
    <scheduler-entry>
         <scheduler-event-listener-class>com.sonicon.testSchedule.MessageListenerTest</scheduler-event-listener-class>
         <trigger>
             <simple>
                 <simple-trigger-value>
                     30
                 </simple-trigger-value>
              <time-unit>second</time-unit>
           </simple>
         </trigger>
    </scheduler-entry>
    ...
    </portlet>

     

    Ya tenemos nuestra tarea programada. En este ejemplo sencillo, el scheduler se ha programado mediante un valor simple y se ejecutará cada 30 segundos. 

     

    Otra opción para el registro de la clase, es usar la etiqueta '<cron>' del siguiente modo (que sustituiría a la etiqueta '<simple>'):

     

     
    <scheduler-entry>
        <scheduler-event-listener-class>com.sonicon.testSchedule.MessageListenerTest</scheduler-event-listener-class>
        <trigger>
            <cron>
                <cron-trigger-value>0 15 10 ? * MON-FRI</cron-trigger-value>
            </cron>
        </trigger>
    </scheduler-entry>
     

Un saludo y hasta la próxima.

 

 

Más Info

Icon

 

 

 

Search Container

Data de publicació 22/01/16 13:10

Search container es una herramienta AUI de Liferay que permite paginar datos de forma sencilla. El objetivo de este post es mostrar la estructura de Search container haciendo un ejemplo sencillo para aplicarlo.

 

Primero de todo tendremos que añadir la siguiente declaración:

<%@taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>

 

Cuando queremos hacer una paginación empezamos con la etiqueta ‘liferay-ui:search-container’:

 

<liferay-ui:search-container delta="10" orderByCol="firstName" orderByType="asc" emptyResultsMessage="no-data-were-found">

</liferay-ui:search-container>

 

Los parámetros son:

  • Delta: Número de elementos por página.
  • emptyResultsMessage: Como su nombre indica, mensaje que se muestra cuando no se encuentran datos.

(Además, si queremos hacer ordenación por columnas añadiremos los siguientes parámetros)

  • orderByCol: Especifica la columna por la cuál se ordenará.
  • orderByType: Especifica el tipo de ordenación, hay disponible la ascendente (‘asc’) y descendente (‘desc’).

Etiquetas internas del Search Container

Dentro deberemos añadir las siguientes etiquetas:

liferay-ui:search-container-results

Dentro de esta etiqueta tendremos la lista de objetos, los cuáles se les hará la paginación (results) y la cantidad que hay de estos objetos (total).

 

<liferay-ui:search-container-results
        results="<%= PersonLocalServiceUtil.search(CRITERIO, searchContainer.getStart(), searchContainer.getEnd(), searchContainer.getOrderByComparator(), orderByCol,orderByType); %>"
        total="<%= PersonLocalServiceUtil.searchCount(CRITERIO); %>"
    />

 

Nota: Normalmente la paginación de objetos se muestra tras un formulario, si hay campos, por los cuáles filtrarás, los has de añadir en los parámetros de la funciones search y searchCount (que deberás crear a parte) y sustituir CRITERIO por los parámetros que necesites.
 

liferay-ui:search-container-row

En esta etiqueta tendremos la estructura de cada columna, es decir que campos se mostraran del objeto del cuál hacemos la paginación.

 

<liferay-ui:search-container-row
        className="net.sonicon.model.Person"
        keyProperty="personId"
        modelVar="person"
    >
        <liferay-ui:search-container-column-text
            name="name"
            value="<%= person.getName() %>"
        />
        <liferay-ui:search-container-column-text
            name="first-name"
            property="firstName"

            orderable="<%= true %>"

            orderableProperty="firstName"
        />
        <liferay-ui:search-container-column-text
            name="department"
            value="<%= DepartmentLocalServiceUtil

.getDepartmentByPersonID(personId).getName(); %>"

        />
    </liferay-ui:search-container-row>

Los parámetros son:

  • className: Especificas la clase del objeto que se le va ha hacer la paginación. En este caso es uno propio que hemos hecho con el 'service builder'.
  • keyProperty: Es el identificador del objecto de cada row.
  • modelVar: Es el objeto de la row.
 

Además dentro de esta etiqueta contiene N etiquetas de liferay-ui:search-container-column-text. Estas representan cada columna que se crea de la paginación.

 

En el ejemplo tenéis 3 columnas:

  • La primera: Una columna ‘Nombre’ que coje el parámetro name del objeto person.

  • La segunda: Una columna ‘Primer Nombre’ que coje el valor del parámetro especificado. Además especifica que se puede ordenar por esa columna (orderable = true) y por qué campo se ordena (orderableProperty = firstName).

  • La tercera: Una columna ‘Departamento’ que aprovecha el id del objeto person para obtener el nombre del departamento.

 

liferay-ui:search-iterator

Esta etiqueta marca donde se construirá la tabla de paginación especificada en las otras etiquetas.

 

Y con esto deberíamos tener una tabla con 3 columnas y con ordenación sobre los objetos de la clase Person.


¡Espero que os haya servido de ayuda!

 

Workflows en Liferay

Data de publicació 09/10/15 14:59

Un flujo de trabajo, o workflow, se puede describir como una automatización de una secuencia de acciones con sus estados para realizar un proceso.

 

Los flujos de trabajo para nuestro portal se pueden definir con el motor de workflow de Liferay, se llama Kaleo. El plugin kaleo-web esta disponible para las versiones EE y CE en el marketplace.

 

Estas definiciones se hacen mediante archivos ”.xml”. Puedes definir tantos flujos de trabajo como sean necesarios para tu portal. Un detalle importante es que puedes especificar roles en tus definiciones de workflow. Si estos roles no existieran en el portal, se crearían automáticamente durante el proceso de "deploy".

 

El flujo de trabajo lo podemos ver como un grafo donde los nodos son estados/tareas y las aristas son transiciones hechas (o no) por el usuario. Por ejemplo, podemos definir un workflow básico de resolución de incidencias, donde tenemos el camino verde que seria el camino esperado (CREADO - EN PROGRESO - EN TEST - RESUELTO) y, además, añadimos el camino rojo, para hacer una transición para volver del test que no ha ido bien y volver a hacer la incidencia:

 

Con Kaleo definiremos los Creado y Resuelto como estados inicial y final. Los otros nodos son tareas, que se diferencian básicamente con que están relacionadas con roles.

 

Untitled (1).png

 

Conceptos

Definición: El workflow se especifica en un archivo “.xml”. Todo la definición ha de ir dentro del tag "<workflow-definition>":

 

<workflow-definition

       xmlns="urn:liferay.com:liferay-workflow_6.2.0"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="urn:liferay.com:liferay-workflow_6.2.0 http://www.liferay.com/dtd/liferay-workflow-definition_6_2_0.xsd"

>

       <name>Test</name>

       <description>Un flujo de trabajo test.</description>

       <version>1</version>

       . . .

</workflow-definition>

 

Acción: Son scripts que se ejecutan dentro de tareas y estados. El código tiene que ir dentro de tags "<script>" y, además, se puede definir un lenguaje con el tag "<script-language>". Puedes usar Javascript, Python, Groovy y Ruby entre otros lenguajes para implementar tus acciones:

 

<actions>

       <action>

              <name>Reject</name>

              <script>

              <![CDATA[

         ...

   ]]>

               </script>

               <script-language>javascript</script-language>

               <execution-type>Reject</execution-type>

       </action>

       <notification>

        ...

       </notification>

</actions>

 

Notificaciones: Son avisos que se envían al usuario, se puede especificar cuando se envían (onAssignment, onEntry y onExit) y la forma (email, im, privatemessage). Como el código de la acción se puede especificar el lenguaje del mensaje, como freemarker en este caso. Las notificaciones se añaden dentro de las acciones:

 

<notification>

        <name>Creator Modification Notification</name>

        <template>Your submission was rejected by $(userName), please modify and resubmit.</template>

        <template-language>freemaker</template-language>

        <notification-type>email</notification-type>

        <notification-type>user-notification</notification-type>

        <execution-type>onAssignment</execution-type>

</notification>

 

Transiciones: Son los pasos de un estado/tarea a otro/a estado/tarea. En un mismo estado/tarea se pueden definir mas de una transición. El formato ha de ser el siguiente:

 

<transitions>

        <transition>

                <name>finalizar</name> <!-- Nombre transición -->

                <target>resuelto</target> <!-- Destino transición -->

        </transition>

        <transition>

                <name>repetir</name> <!-- Nombre transición -->

                <target>en-progreso</target> <!-- Destino transición -->

        </transition>

</transitions>

 

Estados: Se usa para definir como empieza y acaba el workflow. Puede contener acciones y transiciones. Se define asi:

 

<state>

       <name>creado</name>

       <metadata><![CDATA[{"xy":[36,51]}]]></metadata>

<!-- Desfinimos el estado inicial -->

       <initial>true</initial>

       <transitions>

        ...

       </transitions>

       <actions>

              <action>

               ...

              </action>

       </actions>

</state>

 

Asignaciones: Los roles se han de añadir en el tag "<assignments>":
 

<assignments>

       <roles>

              <role>

                     <role-type>organization</role-type>

                     <name>Organization Administrator</name>

              </role>

       </roles>

</assignments>

 

Tareas: Es el concepto más complejo del workflow. Son parecidas a los estados, sin embargo éstas estan relacionadas con un rol o unos roles, para especificar quién ha de completar la tarea. Como los estados, puede contener acciones y transiciones:

 

<task>

       <name>en-progreso</name>

       <metadata>

              <![CDATA[{"transitions":{"resubmit":{"bendpoints":[[303,140]]}},"xy":[328,199]}]]>

       </metadata>

       <actions>

              <action>

               ...

              </action>

              <notification>

               ...

              </notification>

       </actions>

       <assignments>

        ...

       </assignments>

       <transitions>

        ...

       </transitions>

</task>

 

Para más información sobre workflows esta la guía oficial de Liferay: https://dev.liferay.com/discover/portal/-/knowledge_base/6-2/creating-new-workflow-definitions


Espero que ayude a esclarecer el funcionamiento de esta potente herramienta que esta disponible para nuestro portal.

 

Liferay Localización-Traducción

Data de publicació 05/12/14 08:51

Liferay ofrece la posibilidad de localizar el portal incluyendo en un mismo archivo la lista de literales a traducir mediante la utilización de keys (claves). Es posible incluir keys en el propio portlet o, a modo general, desde el Hook para que afecte a todo el portal.

 

Para las traducciones se utilizan keys a modo de key=traduccion

En función del idioma en el que estemos visitando el portal, Liferay buscará la key en el Language_xx.properties que corresponda a ese idioma, donde xx es el código identificativo del idioma.

Si no encuentra la key, la buscará en el Language.properties y si tampoco se encuentra en ese archivo la buscará en las que Liferay tiene por defecto ( https://github.com/liferay/liferay-portal/tree/master/portal-impl/src/content ).

En el caso de que la key utilizada no esté especificada en ninguno de los archivos mencionados, simplemente, no realizará el cambio y mostrará el String de la key tal cual.

*En el blog anterior se puede ver cómo configurar el Hook para que detecte los archivos con las claves y donde situar dichos archivos.

 

Para utilizar éste sistema de traducción se debe incluir la key en cada Language_xx.properties de cada uno de los idiomas a los que queramos localizar el portal junto con su respectiva traducción. Una vez hecho esto, se pueden usar dichas keys dentro de nuestro código.

La forma de utilizar éstas keys puede realizarse mediante el tag de liferay liferay-ui:message o usando la clase java LanguageUtil.

En el caso de que parte del contenido a traducir sea dinámico, Liferay también permite el paso de parámetros en las keys.

 

A continuación se muestran dos ejemplos, uno simple y otro con parámetros, así como la declaración en el Language properties y las diferentes formas de utilizarlo.

 

Ejemplo (básico):

Código dentro del jsp:

Archivo con el código (mediante tags):

<liferay-ui:message key="ultimas-noticias" />

Archivo con el código (con la clase):

LanguageUtil.get(pageContext, "ultimas-noticias" );

Paso 1: Para la key “ultimas-noticias” no existe ninguna traducción por defecto y no la hemos traducido ni en el Language_es.properties ni en el Language.properties, por lo tanto, muestra la key tal cual, sin traducción.

Resultado:

01.png

Paso 2: Traducimos la key “ultimas-noticias” en el Language_es.properties.

Language_es.properties:

ultimas-noticias=Últimas noticias

Muestra “Últimas noticias” si navegamos en español, pero sigue mostrando "ultimas-noticias" si navegamos en inglés.

Resultado:

02.png 03.png

Paso 3: Traducimos la key “ultimas-noticias” en el Language.properties.

Language.properties:

ultimas-noticias=Últimas noticias

Muestra “Últimas noticias” independientemente del idioma por el que naveguemos.

Resultado:

04.png

Paso 4: Traducimos la key “ultimas-noticias” en el Language_en.properties.

Language_en.properties:

ultimas-noticias=Last news

Ahora muestra “Last news” cuando navegamos en inglés y “Últimas noticias” en el resto de idiomas.

Resultado:

02.png 05.png

 

Ejemplo (con parámetros):

Language_es.properties:

delete-profile-x=Eliminado el perfil de {0} del servicio.

Archivo con el código (mediante tags):

<liferay-ui:message key="delete-profile-x" arguments="<%= user.getName() %>" />

Archivo con el código (con la clase):

LanguageUtil.format(pageContext, "delete-profile-x" , user.getName());

Resultado:

Eliminado el perfil de Juan del servicio.

 

Nota: Hay que tener en cuenta que si sobrescribimos una traducción por defecto es probable que afectemos a literales que usa Liferay, por ejemplo, si modificamos “search” en el Hook no solo afectará a nuestros portlets, sino que también se verá afectado el texto que aparezca en el botón del buscador de Liferay.

 

Esperamos que os sea de ayuda. ¡ Hasta la próxima !

Las fases Action y Render de la ejecución de un portlet

Data de publicació 26/08/15 07:40

Muy buenas, en esta entrada explicaré las dos fases de ejecución que puede tener un portlet dentro del portal de Liferay.

 

Para aclarar el porqué Liferay está hecho así, primero debemos tener en cuenta que en una página de un portal de Liferay podemos tener incrustados diferentes portlets. Cada portlet lo podemos entender como una aplicación que hace cualquier funcionalidad y devuelve un código html que se acopla en dicha página.

 

En este sentido tendremos la fase de Render, esta fase la harán todos los portlets de la página. Incluye la funcionalidad para mostrar contenido al usuario según su estado (VIEW o EDIT entre otros).

 

Nota: Liferay permite especificar una prioridad de renderización en los portlets a través de render-weight en el archivo liferay-portlet.xml. Los portlets con un peso mayor serán renderizados antes.

 

Por otra parte, tenemos la fase Action. Esta fase incluye la acción que hace un usuario con un portlet, por lo que el portal solo procesa una acción a la vez. Una acción de un portlet puede ser cualquier petición a éste, como cambiar el estado o un cambio en la base de datos (Por ejemplo, si es un portlet publicador de noticias, añadir una noticia).

 

Podemos ver que las fases han de ser secuenciales: primero, si es necesario, se ejecuta la acción de un portlet y esta acción quizás modifica algún dato o comportamiento. Una vez hecho esto, empieza la fase de renderizar que hacen todos los portlets, incluyendo al que se le ha hecho la acción (Imágen 1).

 

Untitled.png

Imágen 1

 

Para ilustrar el funcionamiento, he añadido en una página prueba unos cuantos portlets: Camino de migas, Idiomas, Documentos y multimedia y un Visor de contenido web.

 

Cuando accedo a la página mediante la url ‘localhost:8080/web/prueba/welcome’ sólo se ejecuta la fase Render para todos los portlets que devuelve el código html para ver la página (Imágen 2).

 

image4.png

Imágen 2

 

Como usuario quiero añadir una carpeta usando el portlet de 'Documentos y multimedia', es decir, quiero realizar una acción sobre un portlet (Imágen 3).

image5.png

Imágen 3

 

Como vemos esta acción ha hecho que se cargue de nuevo la página. Me gustaría destacar dos cosas: primero, la url se ha modificado y ha añadido muchos parámetros (‘localhost:8080/web/prueba/welcome?p_p_id=....’). Así es como el servidor recoge la petición de la acción. La segunda es que si nos fijamos en los portlets siguen igual exceptuando 'Documentos y multimedia' (Imágen 4).

image6.png

Imágen 4

 

¿Qué ha pasado? Pues que la Action ha cambiado el estado del portlet para ver la vista de añadir carpeta. Así en la fase de Render los demás portlets muestran lo mismo y el de 'Documentos y multimedia' muestra un contenido diferente.

 

Sería otra acción añadir una carpeta nueva a 'Documentos y multimedia' y tendría el mismo procedimiento antes descrito.

 

Como hemos visto las url son importantes, para las fases de ejecución. A nivel de código podemos ver estas tres destacadas:

  • renderURL: sirve para mostrar un portlet usando solo la fase Render.

  • actionURL: sirve para indicar a un portlet que ha de ejecutar una acción antes de llegar a la fase Render.

  • resourceURL: sirve para obtener imágenes, XML o JSON entre otros. Es útil para hacer llamadas AJAX al servidor.

 

Fuentes:

https://dev.liferay.com/develop/tutorials/-/knowledge_base/6-1/understanding-the-two-phases-of-portlet-execution

http://www.liferay.com/es/community/forums/-/message_boards/message/43914750

 

Saludos,

Liferay Hook

Data de publicació 21/11/14 08:47

Un Hook es una herramienta que ofrece Liferay para poder, entre otras cosas, sobrescribir funcionalidades que vienen por defecto. Modificando un archivo de una ruta concreta, el archivo del Hook pasa a ser el utilizado en lugar del que utilizaría un Liferay no hookeado. La ventaja de éste sistema es que, retirando el Hook, Liferay regresa a su estado original.

Los usos más comunes son la modificación del comportamiento de ciertas funcionalidades, y la localización del sitio web (traducción a diferentes idiomas).

 

Pasos para la utilización de un Hook:

1. Crear el hook

- Creamos un nuevo Liferay Plugin Project

01.png

 

- Elegimos el nombre del proyecto, seleccionamos para que SDK se aplicará el Hook y en el tipo de plugin seleccionamos “Hook”.

02.png

Esto generará el árbol básico junto con los archivos de configuración

03.png

 

2. Configurar el hook

En el archivo "liferay-hook.xml " es donde se indican los archivos de donde se extraerán las traducciones y el directorio donde se colocarán los archivos a modificar.

<hook>
      <language-properties>content/Language_es.properties</language-properties>
      <language-properties>content/Language_en.properties</language-properties>
      <language-properties>content/Language_fr.properties</language-properties>
      <language-properties>content/Language_de.properties</language-properties>
      <language-properties>content/Language.properties</language-properties>
      <custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
<hook>

 

3. Realizar las modificaciones

3.1 Modificar funcionalidades

Lo primero es saber la ruta en la que se encuentran los archivos a modificar. Los archivos base los podemos encontrar en la carpeta ROOT dentro del liferay-portal.

En nuestro caso modificaremos el comportamiento del buscador de Liferay el cual se encuentra en "\ROOT\html\portlet\search”, para ello copiamos los archivos que nos interesa modificar.

Una vez conocemos su ruta la replicamos en nuestro hook, teniendo en cuenta que el ROOT es nuestra carpeta custom_jsps (la que definimos en el paso anterior).

- Path original: \ROOT\html\portlet\search

- Path en el hook: \custom_jsps\html\portlet\search

 Ya podemos pegar en su interior los archivos que posteriormente modificaremos. Solo es necesario copiar los archivos a modificar.

05.png

06.png

 

3.2 Añadir traducciones

A diferencia de lo que hemos hecho con el buscador, los archivos utilizados para los idiomas no machacan las traducciones por defecto (a no ser que usemos la misma key) sino que se añaden a las que ya hay.

Para añadir la localización a distintos idiomas se necesita un archivo por cada idioma que deseamos traducir. El nombre de los archivos es Language_xx.properties donde xx corresponde al código del idioma.

Estos archivos van en la ruta que hemos definido en el "liferay-hook.xml" que suele ser dentro de "WEB-INF/src/content"

07.png

Las traducciones que realicemos en estos archivos afectarán a todo el portal.

 

4.2 Aplicar los cambios

Una vez hemos realizado las modificaciones que nos interesan y/o añadido las traducciones a los idiomas deseados, solo queda realizar el build e instalarlo en nuestro Liferay.

 

En el próximo blog os explicaremos con más detalle cómo utilizar los archivos del Language para realizar la localización del portal. ¡ Nos leemos !

Liferay Model Hints

Data de publicació 14/11/14 04:36

Una vez, con el Service Builder, hayas creado tus entidades y la implementación del modelo de negocio de tu portal, es interesante especificar como se deberían presentar los datos al usuario (de las entidades creadas).

Con los Model Hints puedes especificar esta información y, además, puedes marcar el tamaño de los campos en la base de datos.

Estas modificaciones se han de realizar en el archivo 'portlet-model-hints.xmlque se encuentra en la ruta: ‘docroot/WEB-INF/src/META-INF’.

Los Model Hints te permiten configurar la librería de tags AlloyUI ‘aui’ para mostrar los campos de tu modelo de negocio.

Ejemplo de uso
Con esta entidad creada con el Service Builder (archivo ‘service.xml’):
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//ES"
      "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd">
<service-builder package-path="com.proliferay.servicebuilder">
    <author>duvidu</author>
    <namespace>PERSONAS</namespace>
    <entity name="Persona" table="PERSONA" local-service="true" remote-service="true">
        <column name="personaId" type="long" primary="true" />
        <column
name="Nombre" type="String" />
        <column
name="Apellido" type="String" />
        <column
name="DNI" type="String" />
        <column
name="fechaNacimiento" type="Date" />
        <column
name="observaciones" type="String" />
    </entity>
</service-builder>

 

Se autogenera el archivo ‘portlet-model-hints.xml’:

<?xml version="1.0"?>
<model-hints>
    <model name="
com.proliferay.servicebuilder.model.com.profilerayersona
           table="PERSONAlocal-service="trueremote-service="true">
        <column name="personaIdtype="longprimary="true" />
        <column 
name="Nombretype="String" />
        <column 
name="Apellidotype="String" />
        <column 
name="DNItype="String" />
        <column 
name="fechaNacimientotype="Date" />
        <column 
name="observacionestype="String" />
    </entity>

</model-hints>

 

Quiero limitar en un campo fecha que los usuarios solo puedan introducir fechas pasadas, como la de nacimiento. Entonces en el archivo 'portlet-model-hints.xml' marcamos para el campo ‘fechaNacimiento’ un hint llamado ‘year-range-future’ a falso:

<?xml version="1.0"?>
<model-hints>
    <model name="
com.proliferay.servicebuilder.model.com.profilerayersona
           table="PERSONAlocal-service="trueremote-service="true">
        <column name="personaIdtype="longprimary="true" />
        <column 
name="Nombretype="String" />
        <column 
name="Apellidotype="String" />
        <column 
name="DNItype="String" />
        <column 
name="fechaNacimientotype="Date" >
             <hint name="year-range-future">false</hint>
        </
column>
        <column 
name="observacionestype="String" />
    </entity>

</model-hints>

 

Para que este cambio sea efectivo se ha de hacer un Build Service.

Así, si tuviéramos un formulario para introducir fechas para el nacimiento con los tags aui, no permitiría introducir fechas futuras.

Como hemos dicho, con los Model Hints también puedes modificar el tamaño de un campo en la Base de Datos. Si fuera el caso de que necesitáramos que el campo ‘observaciones’ fuera más grande que el por defecto entonces usamos el ‘max-length’:

<?xml version="1.0"?>
<model-hints>
    <model name="
com.proliferay.servicebuilder.model.com.profilerayersona
           table="PERSONAlocal-service="trueremote-service="true">
        <column name="personaIdtype="longprimary="true" />
        <column 
name="Nombretype="String" />
        <column 
name="Apellidotype="String" />
        <column 
name="DNItype="String" />
        <column 
name="fechaNacimientotype="Date" >
             <hint
name="year-range-future">false</hint>
        </
column>
        <column 
name="observacionestype="String" >
             <hint name="max-length">400</hint>
        </
column>
    </entity>
</
model-hints>

 

Recuerda hacer el Build Service y el Deploy para que se propague el cambio a la Base da Datos y podrás ver como el campo ha cambiado.

La lista completa de Model Hints es la siguiente:

modelhints.png

¡Espero que os sirva de ayuda!

Liferay - Web Forms con campos dinámicos

Data de publicació 16/06/14 14:00

Después del artículo sobre redirección de un web form a otro, seguimos con ellos para mostrar un truco para hacer sus campos dinámicos.

Los Web Forms de Liferay son una forma sencilla de añadir formularios tipo encuesta o de recogida de datos pero están un poco limitados en cuanto a funcionalidades, como por ejemplo mostrar u ocultar campos dependiendo de las respuestas seleccionadas en otros campos.

Para añadir esta funcionalidad optamos por un camino sencillo, jugar con jQuery (Javascript) para dinamizar un poco el formulario, a continuación veremos un ejemplo sencillo.

Mostrar campos ocultos según los valores de otros campos

1) Crear el formulario con todos los campos posibles

Vamos a querer guardar y/o enviar todos los posibles campos por email, por eso debemos crear el formulario completo mediante la herramienta de configuración que presenta el web-form-portlet oficial.

2) Añadir un script en la misma página que el Web Form

Este punto puede variar mucho depende de dónde se encuentre el cuestionario, por ejemplo si está insertado en una página, dentro de un asset publisher, dentro de un portlet, etc

Por ejemplo, si está dentro de un 'Asset Publisher' deberemos acceder al editor del código del template y localizar dónde se incluye el Widget.

De todos modos se trata de localizar un sitio para poder añadir un <script></script> que pueda tener efecto sobre el formulario, es decir, que se carguen ambos sea cual sea el orden.

3) Ejemplo de código

4.1) Acceder al iframe

$("iframe[src^='http://www.example.com/en/widget/web/guest/forms/-/1_WAR_webformportlet_INSTANCE_']).on("load", function () {

4.2) Esconder uno de los campos por su nombre (se puede por ID, atributo,...)

4.3) Mostrar el campo escondido según la opción seleccionada en otro campo

 Con esta idea las opciones pueden ser muchas y se puede complicar bastante el funcionamiento de los formularios.

¡Esperamos que os sea de utilidad!

Liferay. Instalación sobre SQLServer 2000

Data de publicació 16/10/14 16:49

Uno de nuestros clientes tenía el requerimiento de montar un Liferay 6.2 GA2 sobre Microsoft SQLServer 2000. Según documentación de Liferay, el portal es compatible con este SGBD, cierto, pero la instalación mediante wizard funcionará perfectamente sólo a partir de la versión 2005.

 

Básicamente los problemas encontrados son dos:

  1. Tipo de datos de columna nvarchar(max) no existe en SQLServer 2000.
  2. Esta versión tiene tamaño máximo de row de 8KB,como la versión 2005, pero esta última almacena en memoria extra los datos según el tipo de columna. Por el contrario, SQLServer 2000, sea cuál sea el tipo de datos, todo registro se almacenará en estos 8KB.
     

En nuestro caso, la base de datos se encuentra en una máquina remota. Ten en cuenta lo siguiente en caso que así sea:

  1. Instalar el Service Pack 3 para SQLServer 2000. Evitará bastantes problemas para acceder vía TCP a la base de datos.
  2. Habilitar el acceso TCP a la máquina en el SQLServer.
  3. Comprobar que el log así lo refleja (visible en el Enterprise Manager, en la sección Management)
  4. Testear el acceso remoto mediante Telnet a la IP y puerto (1433 por defecto). Debería aparecer pantalla en negro o mensaje conectado pero no el mensaje de "connection refused".

 

En este punto, detallamos a continuación los pasos que necesitan revisión sobre el habitual proceso de instalación de Liferay. La mayoría de ellos pueden realizarse antes de llamar al ./startup.sh, es decir, antes de lanzar el wizard de instalación automática.

 

  1. Preparar la base de datos
  2.  
    1. Crear la base de datos sobre la que se quiera instalar Liferay.
    2. Creación manual del modelo de datos.
      1. Bajarse el script de las tablas desde aquí: http://www.liferay.com/es/downloads/liferay-portal/available-releases (apartado APPLICATION SERVER PLUGINS - DATABASE SCRIPTS)
      2. En el ZIP, localizamos el script portal-tables-sql-server.sql que vamos a modificar para luego ejecutarlo manualmente en el SQLServer (antes de que lo haga Liferay automáticamente en la instalación)
        1. Reemplazar nvarchar(max) por text. (cambio en el tipo de datos de columna en todo el script)
        2. Aún con esto, si lanzáramos en el SQLServer el script modificado, veríamos que nos lanza el siguiente aviso para unas 40 tablas:

  3.  

    The table [Table] has been created, but its maximum row size exceeds the allowed maximum of 8060 bytes. INSERT or UPDATE to this table will fail if the resulting row exceeds the size limit

Aparece el segundo problema comentado al inicio de esta entrada. Para evitar esta situación no queda otra que modificar manualmente las tablas afectadas por superar el límite de tamaño de row de 8KB. Una opción es modificar el tamaño de los nvarchar. Puede comprobarse que el script está listo cuando en su ejecución no resulte ningún warning como el anterior.
Liferay en su instalación, sólo crea las tablas si éstas no existen. Crear el modelo de datos previa instalación es perfectamente válido. Dejaremos las tablas creadas y vamos a preparar Liferay.
 

  1. Preparar Liferay

  2.  

    1. Configuramos la conexión a la BBDD en el portal-ext.properties.

    2. Añadimos en portal-ext.properties la siguiente línea:

       

      verify.processes=
       

      Sobreescribe la propiedad que controla el disparador para la verificación de la base de datos al arrancar y la inhabilita. Por defecto Liferay, la tiene activada. 
      Una de las acciones que realiza el verificador por defecto es crear una columna para cada tabla para después eliminar dicha columna. El motivo por el que inhabilitamos la verificación es por que justamente la columna que añade Liferay es del tipo nvarchar(max), el tipo de datos incompatible. Lo ideal sería montar un verificador a medida para SQLServer 2000 pero se muestra en esta entrada de blog una solución que no implique recompilar Liferay
      Si lanzáramos el ./startup.sh antes de realizar esta acción, nos econtraríamos con el siguiente error en el log:

       

      15:33:05,507 INFO [localhost-startStop-1][VerifySQLServer:107] Updating AnnouncementsEntry.content to use nvarchar(max)
      15:33:05,509 ERROR [localhost-startStop-1][VerifySQLServer:95] java.sql.SQLException: Line 1: Incorrect syntax near 'max'.
       

Ya lo tenemos todo listo para lanzar la instalación.

 

Hasta el día de hoy, tras la instalación correcta de Liferay, únicamente se han detectado problemas con las inserciones de datos básicos que Liferay realiza para tablas de configuración, como por ejemplo, tipos de documento de la Document Library (DLFileEntryType), que es una de las tablas que deben modificarse los tipos de datos en el script. El tipo "Basic Document" no existía en el Back Office.

 

Esperamos que si alguna vez os encontráis con tener de realizar una instalación en este entorno, esta entrada os sirva para ahorraros algún que otro dolor de cabeza.

 

Hasta la próxima.

Liferay - Mostrar otro Web Form al enviar el primero

Data de publicació 28/05/14 12:51

Liferay incluye un portlet para hacer formularios de una forma muy sencilla y con bastante personalización, son los Web Forms.

Una de las opciones que permite es redireccionar a una URL cualquiera una vez enviado el formulario con éxito. Con esta idea de base, se puede enviar a páginas de resultado (success) personalizadas, enviar a áreas privadas, a otros recursos que antes no estaban disponibles o como veremos a continuación, encadenar un formulario diferente.

Nuestro objetivo entonces será poder redirigir de un formulario a otro que queremos mostrar únicamente si se ha completado el primero, y queremos utilizar el mismo espacio destinado al primero.

Para conseguir la dirección de forma correcta se debe poner la URL única del segundo formulario en el campo Redirect URL on Success del primero.

Pero, ¿como podemos obtener la URL única del formulario? Única ya que queremos la URL propia del componente, no la de la página con el theme, layout, etc.

1) Como incrustar un Web Form en un Web Content, Asset Publisher, etc?

1.1) Crear el Web Form en una página diferente a la que lo incluiremos, puede ser una página oculta (hidden). Comprobar que esta página tenga los mismos permisos (Config->Permissions) que la página donde queremos incluir el componente.

1.2) Activar "Permitir compartir el componente" y Guardar como se ve en la siguiente imagen.

1.3) Entonces, dependiendo del tipo de contenedor que queramos, incluir el código del widget del Web Form como código fuente. Esto se puede hacer desde el editor de un template, des de la opción source de un contenido web, etc

<script src="http://intranet.server.org/html/js/liferay/widget.js" type="text/javascript"></script><script type="text/javascript">
    Liferay.Widget({ url: 'http://intranet.server.org/en/widget/group/public/forms/-/1_WAR_webformportlet_INSTANCE_ySrPL3uYb3bt', height: '580px'});
</script>

2) ¿Cómo redirigir de un Web Form a otro?

2.1) Creamos el segundo formulario en la misma página que el primero [1.1]

2.2) Repetimos el punto [1.2] para el segundo formulario

2.3) Cogemos la URL del widged del punto anterior que será parecida a la siguiente:

http://intranet.server.org/en/widget/group/public/forms/-/1_WAR_webformportlet_INSTANCE_r14q3YPOB6f4

2.4) Ponemos la URL anterior en el campo Redirect URL on Success del primer formulario

Con esto ya tenemos la dirección entre Web Forms de Liferay.

 
¡Esperamos que os sea de utilidad!
 
Icon

¿Cómo añadir el mensaje de éxito en el envío del primer formulario cuando se carga el segundo?

 

Poner el mensaje de éxito que se quiere mostrar como descripción (campo description) del segundo formulario.
Incluir el siguiente código (modificando la url del src) a continuación del script que incluye el primer formulario [1.3].
 
var srcURL = 'http://intranet.server.org/$langcode/widget/web/guest/form/-/1_WAR_webformportlet_INSTANCE_r14q3YPOB6f4';
$("iframe[src^="+srcURL+"]").on("load", function () {
    var iframeObj = $(this);
    if(iframeObj.contents().find(".description").html()!='') {
        iframeObj.contents().find(".description").addClass("portlet-msg-success");
    }
});

El código es jQuery, pero podría traducirse a javascript u otros frameworks con un código parecido.

 
— 20 Items per Page
S'estan mostrant 1 - 20 de 24 resultats.

Bloggers recents Bloggers recents

Oscar Rodríguez
Apunts: 9
Estrelles: 2
Data: 28/09/16
David Berruezo
Apunts: 14
Estrelles: 1
Data: 22/07/16
Javi Martín
Apunts: 2
Estrelles: 1
Data: 20/05/16
Javier Torres
Apunts: 5
Estrelles: 3
Data: 11/04/16
Sergi Mingueza
Apunts: 4
Estrelles: 1
Data: 19/10/15
Matilde Gallardo
Apunts: 1
Estrelles: 0
Data: 26/02/15
Adrià Vilà
Apunts: 4
Estrelles: 4
Data: 31/08/14
Elena Ruiz
Apunts: 1
Estrelles: 2
Data: 13/03/14