tekuento.nettekuento.netLiferay 6.2: El portlet Asset Publisher y el navegador de categoríasJesus Salinashttp://www.tekuento.net/inicio/-/blogs/liferay-6-2-el-portlet-asset-publisher-y-el-navegador-de-categorias2014-10-13T10:03:23Z2014-10-12T12:16:43Z<p>
Como ya hemos comentado en alguna otra ocasión, el portlet <a href="https://www.liferay.com/es/documentation/liferay-portal/6.1/user-guide/-/ai/lp-6-1-ugen03-using-the-asset-publisher-portlet-0" target="_blank">Asset Publisher </a>de <a href="http://www.liferay.com" target="_blank">Liferay Portal</a> es uno de los más completos que nos ofrece, recomendamos desde aquí su conocimiento profundo, dado que nos permite resolver muchos problemas de los que se plantean a la hora de crear un portal.</p>
<p>
Hoy vamos comentar cómo se relaciona este portlet con el de <a href="https://www.liferay.com/es/community/wiki/-/wiki/Main/Categories+Navigation+Portlet" target="_blank">Navegación por categorías</a> cuando conviven en la misma página. La siguiente imagen describe la situación que queremos analizar.</p>
<p>
<img alt="Asset publisher y navegacion" src="http://www.tekuento.net/documents/11022/29417/img02.jpg/1cb6cd47-101c-440a-9b33-a763d3094dea?t=1413134533366" style="width: 840px; height: 377px;" /></p>
<p>
Por defecto, y sin ningún tipo de desarrollo adicional, el portlet <strong>Asset publisher</strong> trabaja de forma combinada con el navegador de categoría. Cuando el usuario hace click sobre una categoría, éste muestra sólo los contenidos que tengan asociada dicha categoría. Se muestra a continuación un ejemplo de lo anteriormente expuesto:</p>
<p>
<img alt="Interacción con navegación" src="http://www.tekuento.net/documents/11022/29417/img03.jpg/b2bdadee-aabd-4638-90f6-c86f0389cd00?t=1413134533779" style="width: 840px; height: 287px;" /></p>
<p>
Como se puede ver en la imagen, el <strong>asset publisher </strong>muestra la categoría seleccionada, permitiendo al usuario desactivarla haciendo click en la cruz. La opción <strong>Mostrar descripciones de metadatos</strong> (contenidos relacionados con o contenidos con categoría …) que aparece en las preferencias configura si mostramos o no esta información.</p>
<p>
<img alt="Mostrar descripciones de metadatos" src="http://www.tekuento.net/documents/11022/29417/img05.jpg/f613b68c-b462-4092-ac65-4d9f855c6244?t=1413194327996" style="width: 562px; height: 450px;" /></p>
<p>
<br />
Es importante tenerla activa cuando queremos que al hacer click sobre la categoría aparezca en pantalla.</p>
<p>
Desde el punto de vista programático el portlet Asset Publisher mediante su fichero <strong>init.jsp</strong> recupera el parámetro <strong>showMetadataDescriptions</strong> de las preferencias del portlet:<br />
<br />
<code>boolean showMetadataDescriptions = </code></p>
<p>
<code>GetterUtil.getBoolean(portletPreferences.getValue("showMetadataDescriptions", null), true);</code><br />
<br />
Y después el fichero <strong>view.jsp</strong>:<br />
<br />
<code> <c:if test="<%= showMetadataDescriptions %>"><br />
<liferay-ui:categorization-filter<br />
assetType="content"<br />
portletURL="<%= portletURL %>"<br />
/><br />
</c:if></code><br />
<br />
La etiqueta <strong>categorization-filter</strong> que se encuentra en la <strong>librería liferay-ui </strong>se encarga de mostrar la categoría seleccionada.</p>
<p>
Resumiendo, debéis conocer las diferentes propiedades que nos ofrece este portlet, entre las que se encuentra esta opción <strong>"Mostrar descripciones de metadatos".</strong> Esperamos que os haya sido de utilidad.</p>Jesus Salinas2014-10-12T12:16:43ZLa certificación ACE y el modelo de contenidos en Alfresco (1)Jesus Salinashttp://www.tekuento.net/inicio/-/blogs/la-certificacion-ace-y-el-modelo-de-contenidos-en-alfresco-1-2014-09-30T16:35:00Z2014-09-30T16:25:15Z<p>
Como ya sabéis, <a href="http://university.alfresco.com/ACE" target="_blank">Alfresco Certified Engineer</a> es una de las certificaciones que podemos obtener para <a href="http://alfresco.org" target="_blank">Alfresco ECM</a>. Sus <a href="http://university.alfresco.com/static/documents/ACEblueprint002.pdf" target="_blank">blueprints</a> nos ofrecen información general sobre este examen.</p>
<p>
Básicamente tenemos 60 minutos para contestar 60 preguntas sobre áreas de conocimiento, tales como:</p>
<ul>
<li>
Núcleo de la arquitectura.</li>
<li>
Personalización del repositorio.</li>
<li>
Webscripts.</li>
<li>
Personalización UI.</li>
<li>
API de Alfresco.</li>
</ul>
<p>
Uno de los temas tratados en <strong>Personalización del repositorio</strong> es <strong>Content modeling</strong>, lo que habitualmente llamamos <strong>Contenidos en Alfresco ECM</strong>. Si queréis profundizar en este tema podeís acceder a <a href="http://www.slideshare.net/EmatizTecnologia/contenidos-personalizados-en-alfresco-ecm" target="_blank">Contenidos Personalizados en Alfresco ECM</a>.<br />
<br />
Aquí tenéis alguna de las preguntas que aparecieron sobre este tema en el examen <a href="http://university.alfresco.com/ACE" target="_blank">Alfresco Certified Engineer</a>:<br />
<br />
<strong>Select the two items that may appear within a custom content model<br />
<br />
A. Localization strings.<br />
B. Policy behaviors for a content type.<br />
C. Property sheet definitions.<br />
D. References to other namespaces<br />
E. The model's namespace</strong><br />
<br />
Obligatoriamente deben aparecer referencias a otros espacios de nombres y el espacio de nombres del modelo, por lo tanto, D y E sería la respuesta correcta.<br />
<br />
<strong>Select the two elements that will indicate that a specific property is to be indexed in the background.<br />
<br />
A. <index enabled='true'><stored>false</stored></index><br />
B. <index enabled='true'><background>true</background></index><br />
C. <index enabled="true"><atomic>true</atomic><index><br />
D. <index enabled="true"><atomic>false</atomic></index><br />
E. <index enabled=’’true'’ backqround='’true'’ stored='’false’’/></strong><br />
<br />
Por defecto, los contenidos se indexan atómicamente en background, no se almacenan en el índice y se dividen en distintos tokens antes de ser indexados (esta es la política definida en cm:content que puede ser sobreescrita).<br />
<br />
<code> <property name="cm:content"><br />
<type>d:content</type><br />
<mandatory>false</mandatory><br />
<index enabled="true"><br />
<atomic>false</atomic><br />
<stored>false</stored><br />
<tokenised>true</tokenised><br />
</index><br />
</property></code><br />
<br />
Las respuestas sintácticamente correctas son A, D, las demás sintácticamente no son válidas<br />
<br />
<strong>Select the element that will allow any node to be included as part of a peer association<br />
<br />
A. <target><class>ANY</class></target><br />
B. <target><class>sys:base</class></target><br />
C. <target><type>ANY</type></target><br />
D. <target><type>sys:base</type></target></strong><br />
<br />
Si queremos que cualquier nodo forme parte de una asociación deberemos usar sys:base, para más <a href="https://wiki.alfresco.com/wiki/Data_Dictionary_Guide" target="_blank">información</a>,<br />
<br />
<code> <type name="cm:cmobject"><br />
<title>Object</title><br />
<parent>sys:base</parent><br />
<properties><br />
<property name="cm:name"><br />
<type>d:text</type><br />
</property><br />
</properties><br />
<mandatory-aspects><br />
<aspect>cm:auditable</aspect><br />
</mandatory-aspects><br />
</type></code><br />
<br />
por lo tanto, la respuesta correcta es B.<br />
<br />
En próximos post, incluiremos otras preguntas que pueden seros de ayuda para preparar la certificación.</p>Jesus Salinas2014-09-30T16:25:15ZDescarga de documentos mediante webscripts en Alfresco ECMJesus Salinashttp://www.tekuento.net/inicio/-/blogs/descarga-de-documentos-mediante-webscripts-en-alfresco-ecm2014-09-30T14:05:00Z2014-09-30T13:40:34Z<p>
<a href="http://www.alfresco.org"><strong>Alfresco ECM</strong></a> nos ofrece infinidad de posibilidades a la hora de llevar a cabo integraciones con terceros. Los <strong>webscripts</strong> son una de las estrategias más utilizadas. En este artículo analizaremos de forma simple cómo generar urls la descarga de documentos almacenados en Alfresco ECM que podemos utilizar en cualquier aplicación.</p>
<h1>
Introducción</h1>
<p>
Supongamos que queremos diseñar un <strong>webscript en Alfresco</strong> que realice una búsqueda y genere una <strong>respuesta HTML</strong> con información de los documentos, entre otras cosas, <strong>su url de descarga</strong>. El controlador en función de una serie de criterios recupera un conjunto de nodos:<br />
<br />
<strong>Ejemplo.get.js</strong><br />
<br />
<code> ...<br />
var <strong>resultadoBusqueda</strong> = search.luceneSearch( … );<br />
...</code><br />
<br />
Posteriormente, estos nodos son cargados en la variable model para que la plantilla ftl del webscript pueda utilizar esa información.<br />
<br />
<code> model.res = resultadoBusqueda;</code><br />
<br />
Una vez tenemos todos los nodos, queremos generar una respuesta en formato HTML en una plantilla <strong>Ejemplo.get.html.ftl</strong> con información de cada documento y su url de descarga.<br />
<br />
<code><h1>Resultado de la búsqueda</h1><br />
<br />
<#if res?has_content><br />
<br />
<table><br />
<thead><br />
<tr><br />
<th>Nombre del documento</th><br />
…<br />
</tr><br />
</thead><br />
<tbody><br />
…<br />
<br />
<#else><br />
<h1>No hay documentos que mostrar</h1><br />
</#if></code><br />
<br />
Como se puede ver, se utiliza la directiva <strong>if</strong> de <a href="http://freemarker.org/docs/ref_directive_if.html" target="_blank">Freemarker</a>.</p>
<h1>
Iteración sobre los documentos</h1>
<p>
Primero, debemos aprender a iterar sobre los elementos de una lista de nodos, para ello utilizaremos la directiva <strong><#list></strong>:</p>
<p>
<code><#list res as item><br />
<tr><br />
<td>${item.properties["cm:name"]}</td><br />
<td>${item.properties["sample:info1"]}</td><br />
...<br />
<br />
</tr><br />
</#list></code></p>
<p>
La variable <strong>item</strong> identifica a cada documento dentro de cada iteración. El mapa <strong>properties</strong> nos permite recuperar los valores de las diferentes propiedades de un nodo, para profundizar en estos conceptos la <a href="http://wiki.alfresco.com/wiki/Template_Guide" target="_blank">wiki de Alfresco puede ser un buen recurso</a>, por ejemplo, <strong>${item.properties["cm:name"]} </strong>es el nombre del documento.</p>
<h1>
Construcción url de descarga</h1>
<p>
Para construir la url de descarga de cada documento tendremos que manejar los siguientes conceptos:</p>
<ol>
<li>
<strong>Url de descarga</strong> para cada documento: existe una propiedad asociado a cada nodo llamada <strong>downloadUrl</strong> que nos permite recuperar la url de descarga.</li>
<li>
<strong>Url del host </strong>donde se encuentra desplegado Alfresco: existe un <strong>objeto implícito</strong> llamado <strong>url</strong> que nos permite acceder, entre otras cosas, a la propiedad <strong>server</strong>, de tal forma que, la variable <strong>url.server</strong> nos ofrece la url del servidor.</li>
<li>
<strong>Ticket de autenticación</strong>: la url de descarga de un documento está protegida, es decir, debemos estar autenticados para descargar el documento. Para ello utilizaremos el concepto de ticket (<a href="https://wiki.alfresco.com/wiki/Web_Scripts" target="_blank">Pre-Authorized Calls</a>) mediante el objeto implícito session y su propiedad ticket.</li>
</ol>
<p>
La url de descarga, utilizando los conceptos anteriormente comentados es:<br />
<br />
<strong> <span style="font-size:14px;"><code> ${url.server}/alfresco${item.downloadUrl}?ticket=${session.ticket}</code></span></strong><br />
<br />
Para finalizar, la plantilla sería algo tal que así:<br />
<br />
<code><h1>Resultado de la búsqueda</h1><br />
<br />
<#if res?has_content><br />
<br />
<table><br />
<thead><br />
<tr><br />
<th>Nombre del documento</th><br />
<th>Nuevo propietario</th><br />
<th>Fecha de cambio de titularidad</th><br />
<th>Descarga</th><br />
</tr><br />
</thead><br />
<tbody><br />
<#list res as item><br />
<tr><br />
<td>${item.properties["cm:name"]}</td><br />
<td>${item.properties["sample:info1"]}</td><br />
<td>${item.properties["sample:fechaInfo"]?date}</td><br />
<td><a href="${url.server}/alfresco${item.downloadUrl}?ticket=${session.ticket}" download><span>Descarga</span></a></td><br />
</tr><br />
</#list><br />
<tbody><br />
</table><br />
<#else><br />
<h1>No hay documentos que mostrar</h1><br />
</#if></code></p>
<h1>
Conclusión</h1>
<p>
En este artículo, hemos querido demostrar <strong>cómo generar</strong> en un webscript fragmentos HTML donde aparecen urls que nos permiten descargar de documentos almacenados en Alfresco ECM de una forma muy simple.</p>Jesus Salinas2014-09-30T13:40:34ZMarketplace de Liferay 6.1: Problemas instalando pluginsJesus Salinashttp://www.tekuento.net/inicio/-/blogs/marketplace-de-liferay-6-1-problemas-instalando-plugins2014-01-27T10:47:01Z2014-01-27T08:10:35Z<p>
<a href="http://www.liferay.com"><strong>Liferay Portal</strong></a> nos ha permitido históricamente <strong>instalar plugins</strong> sobre nuestras instancias siguiendo diferentes estrategias. A partir de la versión 6 aparece el <strong>Marketplace</strong> como herramienta de instalación centralizada. Este artículo pretende proporcionar una solución sencilla cuando el desarrollador se encuentra con problemas en el proceso de instalación.</p>
<h1>
Introducción</h1>
<p>
En ocasiones nos encontramos con problemas cuando intentamos instalar plugins desde el <strong>Marketplace de Liferay</strong>, la aplicación se queda intentando instalar ese componente de forma indefinida, sin éxito. No genera ningún mensaje de error indicándonos cuál es el problema, "<a href="https://support.liferay.com/browse/LPS-40457?page=com.googlecode.jira-suite-utilities:transitions-summary-tabpanel"><strong>Marketplace: no warning/error message when trying to download a plugin > 3MB</strong></a>".</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22635/marketplace00.jpg/58abb381-ae47-4dc5-b24e-5b794c73686d?t=1390812146738" style="width: 700px; height: 270px;" /></p>
<p>
Parece que este problema se encuentra estrechamente relacionado con el tamaño máximo de fichero, es decir, el parámetro <strong>dl.file.max.size</strong>. Es por eso que estos problemas de instalación se producen sólo cuando instalamos plugins de gran tamaño. De cualquier forma, hemos estado <strong>jugando con este parámetro</strong> sin éxito. Para resolver este problema, hemos optado por la instalación del plugin <strong>manual</strong>. Se describe a continuación dicho proceso.</p>
<h1>
Acceder a nuestro perfil de liferay.com</h1>
<p>
Se accede a la web de <a href="http://www.liferay.com">Liferay</a> y se procede a autenticarse dentro de la plataforma. Evidentemente, si el usuario no tiene cuenta debe darse de alta en el sistema:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22635/articulo00.jpg/3f08328f-47c3-4989-98d9-c72db067fcb8?t=1390818095894" style="width: 700px; height: 450px;" /></p>
<p>
Una vez que el usuario se ha autenticado debe acceder a su perfil. La siguiente figura muestra cómo hacerlo:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22635/articulo01.jpg/acb950e3-c1fd-4dcf-a342-704b2030e34f?t=1390818096351" style="width: 700px; height: 334px;" /></p>
<p>
El <strong>perfil de usuario</strong>, entre otras cosas, nos ofrece un acceso al <strong>App Manager</strong>, como su nombre indica es el gestor de aplicaciones que nos proporciona acceso a los diferentes plugins que tendremos a nuestra disposición.</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22635/articulo02.jpg/73ae1a9e-7ce6-4443-a939-b00f9b1b6c07?t=1390818096792" style="width: 700px; height: 318px;" /></p>
<h1>
Descargar la aplicación que queremos instalar en nuestro equipo</h1>
<p>
El proceso de descarga es muy simple, se selecciona la aplicación que deseamos instalar y la versión adecuada. Por ejemplo, en la siguiente figura se selecciona el plugin para la versión 6.1 CE GA2:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22635/articulo03.jpg/68081adb-4707-4691-bf36-154019febefe?t=1390818097224" style="width: 700px; height: 458px;" /></p>
<p>
Al hacer click sobre el <strong>botón App</strong> que se muestra en la figura anterior se desencadena el proceso de descarga. La extensión del fichero es <strong>lpkg</strong>.</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22635/articulo04.jpg/214d9c77-058a-476b-9b0f-0a05e924ed25?t=1390818097692" style="width: 700px; height: 310px;" /></p>
<p>
</p>
<h1>
Instalar componente en la instancia de Liferay</h1>
<p>
Una vez que el fichero ha sido descargado en el equipo local, para terminar el proceso, <strong>sólo</strong> tenemos que copiarlo en la carpeta deploy de la instalación de liferay con la que estamos trabajando. Automáticamente, se desencadena el proceso de instalación.</p>Jesus Salinas2014-01-27T08:10:35ZBarra de navegación siempre visible en Liferay 6.1 con AlloyJesus Salinashttp://www.tekuento.net/inicio/-/blogs/barra-de-navegacion-siempre-visible-en-liferay-6-1-con-alloy2014-01-13T12:11:14Z2014-01-13T09:24:41Z<p>
En este artículo pasamos a comentar cuáles son los pasos que debe seguir un desarrollador de temas en <a href="http://www.liferay.com"><strong>Liferay Portal</strong></a> para conseguir que la barra de navegación siempre esté visible, aún cuando el usuario hace scroll.</p>
<h1>
Introducción</h1>
<p>
Diseñar una web haciendo que su barra de navegación principal se encuentre siempre accesible es una práctica muy habitual. Existen infinidad de ejemplos, mostramos a continuación uno para que no haya confusión alguna.</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22206/img03.jpg/f1c6e941-bd46-4d49-867e-3b53d1e1a5bc?t=1389606836859" style="width: 800px; height: 341px;" /></p>
<p>
Cuando el usuario hace scroll, <strong>DEBE</strong> seguir teniendo acceso a la barra de navegación:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22206/img04.jpg/eea45884-a5c7-4405-834b-15dab33e1dcb?t=1389606837453" style="width: 800px; height: 310px;" /></p>
<h1>
Solución</h1>
<p>
<a href="http://www.liferay.com/es/web/jorge.ferrer">Jorge Ferrer</a> habla sobre una funcionalidad muy parecida en su artículo <a href="http://www.liferay.com/es/web/jorge.ferrer/blog/-/blogs/4897250"><strong>Feedback for Liferay 6: Global unified breadcrumb</strong></a>. En resumen, el <strong>tema Classic</strong> de Liferay Portal implementa unas Migas de Pan siempre visibles mediante la siguiente funcionalidad javascript:<br />
<br />
<code>AUI().ready('liferay-hudcrumbs', function(A) {<br />
<br />
var siteBreadcrumbs = A.one('.site-breadcrumbs');<br />
<br />
if (siteBreadcrumbs) {<br />
siteBreadcrumbs.plug(A.Hudcrumbs);<br />
}<br />
}<br />
);</code><br />
<br />
Entendiendo este concepto y como bien se indica en el artículo <a href="http://www.liferay.com/es/web/ryan.schuhler/blog/-/blogs/easy-sticky-nav-using-alloy-">Easy Sticky-nav using alloy</a>, podemos adaptarlo al problema que queremos resolver. El desarrollador debe modificar el tema (<a href="http://www.liferay.com/es/documentation/liferay-portal/6.1/development/-/ai/creating-liferay-them-7">liferay theme</a>) responsable del <strong>Look & Feel</strong> del portal añadiendo las siguientes líneas de el fichero main.js:<br />
<br />
<code>AUI().ready('liferay-hudcrumbs', function(A) {<br />
<br />
// Se recupera el elemento id="navigation"<br />
var navigation = A.one('#navigation');<br />
<br />
if (navigation) {<br />
navigation.plug(A.Hudcrumbs);<br />
}<br />
}<br />
);</code></p>
<p>
Como podéis ver, hemos adaptado el código JS para que el elemento HTML visible sea la barra de navegación:<br />
<br />
<code><nav class="$nav_css_class" id="navigation"><br />
<br />
…<br />
<br />
</nav></code></p>
<p>
Al añadir este código JavaScript, nuestro portal cambia su comportamiento, automáticamente, cuando el usuario hace scroll al final de la página HTML aparece una clonación de la barra, algo tal que así:<br />
<br />
<code>...<br />
<nav class="sort-pages modify-pages <strong>lfr-hudcrumbs</strong>" ...><br />
<ul><br />
<li class="selected"> ... </li><br />
<li> ... </li><br />
<li> ... </li><br />
<li>… <li><br />
</ul><br />
</nav></code></p>
<p>
Siguiendo el ejemplo que se estamos utilizando, el resultado sería algo tal que así:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22206/img06.jpg/4c164e91-58bb-4132-9f63-5f2ee9368c30?t=1389613765356" style="width: 718px; height: 417px;" /></p>
<p>
Evidentemente, esto tal que así, no nos sirve para mucho. A partir de aquí, debemos utilizar CSS para aplicar estilos sobre la nueva barra de navegación. El portal inserta la barra de navegación clonada con un <strong>SELECTOR CSS</strong> de tipo class llamado <strong>lfr-hudcrumbs</strong>. Este será el selector que nos permita controlar dónde queremos que aparezca y con qué apariencia. Por ejemplo, si añadimos el siguiente código al fichero custom.css del tema con el que trabaja el desarrollador obtendremos cambios significativos en la barra clonada:</p>
<p>
<code>nav.lfr-hudcrumbs{<br />
<br />
position: fixed;<br />
z-index: 440;<br />
margin-bottom: 5px;<br />
margin-left: 0;<br />
margin-right: 0;<br />
margin-top: 0;<br />
<br />
width: 530px !important;<br />
left: 52.2% !important;<br />
<br />
-moz-border-bottom-colors: none;<br />
-moz-border-left-colors: none;<br />
-moz-border-right-colors: none;<br />
-moz-border-top-colors: none;<br />
background-color: #FFFFFF;<br />
border-bottom-color: #0061A1;<br />
border-bottom-left-radius: 5px;<br />
border-bottom-right-radius: 5px;<br />
border-bottom-style: solid;<br />
border-bottom-width: 1px;<br />
border-image-outset: 0 0 0 0;<br />
border-image-repeat: stretch stretch;<br />
border-image-slice: 100% 100% 100% 100%;<br />
border-image-source: none;<br />
border-image-width: 1 1 1 1;<br />
border-top-color: #0061A1;<br />
border-top-left-radius: 5px;<br />
border-top-right-radius: 5px;<br />
border-top-style: solid;<br />
border-top-width: 1px;<br />
box-shadow: 0 0 4px #0061A1;<br />
<br />
}<br />
<br />
nav.lfr-hudcrumbs ul{<br />
list-style-type: none;<br />
margin: 0px !important; <br />
<br />
}<br />
<br />
nav.lfr-hudcrumbs ul li{<br />
float: left;<br />
padding-bottom: 29px;<br />
padding-top: 29px;<br />
padding-left: 0px;<br />
padding-right: 0px;<br />
}<br />
<br />
nav.lfr-hudcrumbs ul li:selected{<br />
<br />
border-bottom-color: #0061A1;<br />
border-bottom-style: solid;<br />
border-bottom-width: 3px;<br />
}<br />
<br />
nav.lfr-hudcrumbs ul li:hover {<br />
border-bottom-color: #dc002e;<br />
border-bottom-style: solid;<br />
border-bottom-width: 3px;<br />
}<br />
<br />
nav.lfr-hudcrumbs ul li:hover a{<br />
color: #dc002e;<br />
}<br />
<br />
nav.lfr-hudcrumbs ul li a{<br />
color: #0061A1;<br />
font-size: 16px;<br />
font-weight: 400;<br />
text-decoration: none;<br />
text-transform: lowercase;<br />
margin-left: 20px;<br />
margin-right: 20px;<br />
padding-left: 2px;<br />
padding-right: 2px;<br />
}</code></p>
<p>
La siguiente figura ilustra los cambios:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22206/img04.jpg/eea45884-a5c7-4405-834b-15dab33e1dcb?t=1389606837000" style="width: 800px; height: 310px;" /></p>
<p>
Una vez entendido el procedimiento, la aplicación sobre cualquiera de nuestros temas de Liferay es prácticamente inmediato y muy útil. Esperamos que el artículo sea de vuestro agrado.</p>
<p>
</p>Jesus Salinas2014-01-13T09:24:41ZAsset publisher en Liferay 6.1, abriendo contenidos en otra páginaJesus Salinashttp://www.tekuento.net/inicio/-/blogs/asset-publisher-en-liferay-6-1-abriendo-contenidos-en-otra-pagina2014-01-13T08:21:42Z2014-01-08T09:35:56Z<p>
Se describe el procedimiento para, trabajando con el portlet <strong>Asset Publisher,</strong> poder abrir los contenidos seleccionados por el usuario en otra página. Esta es un práctica muy habitual en cualquier web y este portlet es la mejor estrategia para gestionarla. Podemos encontrar referencias a esta duda en la comunidad de Liferay, por ejemplo, en el siguiente <a href="https://www.liferay.com/es/community/forums/-/message_boards/message/12981689">enlace</a>.</p>
<h1>
Introducción</h1>
<p>
La generación de <strong>contenidos web</strong> es uno de los elementos más importantes dentro del concepto de portal, dado que permite, a los gestores del portal y a los usuarios que acceden a él, dotar de contenidos a la herramienta. <strong>Liferay Portal</strong> ofrece una serie de funcionalidades que permite generar, revisar y mantener contenidos web de una forma cómoda, flexible y avanzada:</p>
<ol>
<li>
Editor de contenidos web: WYSWYG.</li>
<li>
Portlets auxiliares para el manejo de contenidos.</li>
<li>
Integración con flujos de trabajo.</li>
<li>
...</li>
</ol>
<p>
Uno de los portlet más útil es el <strong>Asset Publisher</strong> o Publicador de contenidos, nos ofrece infinidad de posibilidades para manejar todo tipo de contenidos: contenidos web, documentos, etc. Podéis encontrar información básica sobre su uso en el siguiente <a href="https://www.liferay.com/es/documentation/liferay-portal/6.2/user-guide/-/ai/using-the-asset-publisher-liferay-portal-6-2-user-guide-06-en">enlace</a>.</p>
<h1>
Abriendo contenidos en otra página</h1>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/5.jpg/e51dfd5d-14d1-4cbd-b840-8a9c2d269f92?t=1389179335981" style="width: 878px; height: 507px;" /></p>
<p>
Pero, por defecto, el contenido se muestra en la misma página, tal que así:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/7.jpg/b5e07818-bca2-469b-825f-e2266ecd2bbc?t=1389179619657" style="width: 876px; height: 355px;" /></p>
<p>
</p>
<p>
Si queremos modificar ese comportamiento por defecto, debemos seguir los pasos que se describen a continuación:</p>
<h2>
Paso 1: Navegando a las preferencias del portlet se configura el modo en el que se muestra el contenido.</h2>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/9.jpg/5a2a2269-5cad-4ae3-a6e4-47d25780418e?t=1389180150471" style="width: 789px; height: 287px;" /></p>
<p>
</p>
<h2>
Paso 2: Se crea la nueva página donde se desea mostrar el contenido añadiendo un portlet de tipo Asset Publisher.</h2>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/11.jpg/4423a21b-96d4-48d1-a9fd-4a11dd29b2b6?t=1389180826085" style="width: 548px; height: 344px;" /></p>
<h2>
Paso 3: Se añade el portlet Asset Publisher y se configura para que sea el visualizador por defecto.</h2>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/13.jpg/08828a6b-af56-4089-b0d5-68904e774621?t=1389181268855" style="width: 772px; height: 231px;" /></p>
<h2>
Paso 4: Cuando se crean los contenidos se define la página de visualización por defecto de la siguiente forma.</h2>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/15.jpg/6cb96a72-f5c0-4853-b5f5-9b2094b63c0d?t=1389181556557" style="width: 800px; height: 332px;" /></p>
<p>
Haciendo click en la opción Página de Visualización:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/22039/16.jpg/e17bf5cf-c480-490a-bc27-8ad53e01960a?t=1389181940996" style="width: 800px; height: 304px;" /></p>
<p>
Una vez hecho esto, todas las vistas de detalle de los contenidos manejados desde el primer Asset Publisher serán visualizados en otra página.</p>
<h1>
Conclusión</h1>
<p>
Esta funcionalidad es muy simple y suele utilizarse en infinidad de aplicaciones. La página principal de la <a href="http://www.autismodarata.org">Asociación Autismo Dárata</a> muestra esta funcionalidad en la gestión de últimas noticias y eventos.</p>Jesus Salinas2014-01-08T09:35:56ZLiferay 6.1, validación en formularios desarrollados con Alloy UIJesus Salinashttp://www.tekuento.net/inicio/-/blogs/liferay-6-1-validacion-en-formularios-desarrollados-con-alloy-ui2013-10-06T22:45:21Z2013-10-06T22:26:03Z<h1>
Introducción</h1>
<p>
La biblioteca de etiquetas JSP aui nos permite, entre otras cosas, desarrollar formularios HTML de una forma rápida y elegante. Para manejar esta biblioteca tendremos que añadir en nuestras páginas JSP la siguiente línea:</p>
<pre style="margin-left: 40px;">
<code><%@ taglib uri="http://alloy.liferay.com/tld/aui" prefix="aui"%></code>
</pre>
<p>
</p>
<p>
De esa forma podemos crear formularios:</p>
<pre>
<code><%@ taglib uri="http://alloy.liferay.com/tld/aui" prefix="aui"%>
...
<aui:form action="<%= editArticleActionURL %>" enctype="multipart/form-data" method="post" name="fm1">
...
</aui:form>
</code></pre>
<p>
</p>
<p>
Esta biblioteca nos permite trabajar con formularios, botones, campos de texto, etc. Uno de los procesos más importantes cuando trabajamos con formularios es la posibilidad de definir validaciones sobre los campos de dicho formulario. La biblioteca Alloy UI nos ofrece una etiqueta JSP que nos permite resolver este proceso.</p>
<h1>
Validación de formularios, aui:validator</h1>
<p>
La etiqueta <strong>validator</strong> nos permite desencadenar procesos de validación sobre los campos de un formulario:<br />
</p>
<pre style="margin-left: 40px;">
<code>...
<aui:input bean="<%= article %>" model="<%= JournalArticle.class %>" label="name" name="title" >
<aui:validator name=”required” />
</aui:input>
...
</code></pre>
<p>
</p>
<p>
El atributo <strong>name</strong> determina qué validación debemos realizar y el atributo <strong>errorMessage</strong>, como su nombre indica, el mensaje de error a generar si la validación no es correcta. El atributo name puede tomar los siguientes valores:</p>
<ol>
<li>
alpha o alphanum: sólo caracteres o alfanumérico.</li>
<li>
date: sólo acepta fechas.</li>
<li>
digits o numbers: sólo acepta números positivos o sólo acepta números.</li>
<li>
email.</li>
<li>
min, minLenght, max, maxLenght.</li>
<li>
range, rangeLenght.</li>
<li>
required, url.</li>
<li>
custom: se pueden definir validaciones personalizadas.</li>
</ol>
<p>
Y además se puede definir una cadena de validadores sobre el mismo campo:</p>
<pre style="margin-left: 40px;">
<code><aui:input bean="<%= article %>" model="<%= JournalArticle.class %>" label="name" name="title" >
<aui:validator name=”required” />
<aui:validator name=”numbers” />
</aui:input>
</code></pre>
<h1>
Conclusión</h1>
<p>
Se recomienda el uso de este tipo de validación en nuestros formularios, aunque, por supuesto, sin olvidar los procesos de validación en servidor.</p>Jesus Salinas2013-10-06T22:26:03ZLiferay 6.1, trabajando con categorías y etiquetas programáticamenteJesus Salinashttp://www.tekuento.net/inicio/-/blogs/liferay-6-1-trabajando-con-categorias-y-etiquetas-programaticamente2013-10-06T20:01:45Z2013-10-06T11:28:40Z<h1>
Introducción</h1>
<p>
La gestión de <strong>categorías y etiquetas</strong> es un proceso muy habitual dentro de <strong>Liferay Portal</strong>. Se pueden gestionar desde el panel de control y mediante portlets de Liferay.</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/20125/img20.png/80eec876-ed90-4f43-9627-2af300b66711?t=1381061081492" style="width: 600px; height: 151px;" /></p>
<p>
Pero cuando necesitamos manejar este concepto desde nuestros propios desarrollos, <strong>¿qué debemos hacer?</strong> Pues debemos tener en cuenta los siguientes elementos.</p>
<h1>
Selección de categorías desde la interface web</h1>
<p>
<strong>Liferay Portal</strong> nos ofrece la etiqueta JSP <strong>aui:input</strong> para ofrecer al usuario un componente para seleccionar las categorías disponibles:</p>
<p style="margin-left: 40px;">
<code><aui:input classPK="<%= assetClassPK %>"<br />
model="<%= DLFileEntry.class %>"<br />
name="categories"<br />
type="assetCategories"<br />
fieldParam="categories"<br />
field="categories" /></code></p>
<p>
Como se puede ver, la propiedad <strong>type</strong> debe ser igual a <strong>assetCategories</strong>, la propiedad model debe identificar la clase asociada a la entidad sobre la que queremos definir categorías y classPK debe ser el identificador de la entidad.</p>
<p>
</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/20125/categorias03.png/514e2b1b-2b12-4a5f-8727-b1a953ec450a?t=1381062416610" style="width: 490px; height: 154px;" /></p>
<h1>
Selección de etiquetas desde la interface web</h1>
<p>
La etiqueta <strong>aui:input</strong> nos permite también trabajar con el selector de etiquetas. El atributo type debe ser igual a <strong>assetTags</strong>:</p>
<p>
<code><aui:input classPK="<%= assetClassPK %>"<br />
model="<%= DLFileEntry.class %>"<br />
name="tags"<br />
type="assetTags" /></code></p>
<p>
</p>
<p style="text-align: center;">
<code><img alt="" src="http://www.tekuento.net/documents/11022/20125/categorias04.png/156e7ea3-eb10-42f4-836d-6409a602d144?t=1381062647131" style="width: 683px; height: 186px;" /></code></p>
<h1>
Dentro de un formulario</h1>
<p>
Incluir estos componentes dentro de un formulario es tan simple como:</p>
<p style="margin-left: 40px;">
<code><aui:form action="<%= mostrarAccion %>" method="post" name="fm"><br />
...</code><br />
<code><aui:fieldset></code><code> </code></p>
<p style="margin-left: 40px;">
<code><aui:input classPK="<%= assetClassPK %>" model="<%= DLFileEntry.class %>" </code></p>
<p style="margin-left: 40px;">
<code>name="categories" type="assetCategories" fieldParam="categories" field="categories" /><br />
<aui:input classPK="<%= assetClassPK %>" model="<%= DLFileEntry.class %>" name="tags" type="assetTags" /><br />
</aui:fieldset> <br />
...<br />
</aui:form></code></p>
<h1>
Recuperación de categoría y etiquetas</h1>
<p>
La recuperación de las <strong>categorías y etiquetas</strong> seleccionadas por un usuario dentro del código de nuestros portlets, OBLIGATORIAMENTE, deben hacerse mediante la clase <strong>ServiceContext</strong> de Liferay Portal.</p>
<pre style="margin-left: 40px;">
<cite><code>@ProcessAction(name="getInformation")
public void getInformation(ActionRequest actionRequest, ActionResponse actionResponse){
try {
ServiceContext context =
ServiceContextFactory.getInstance(actionRequest);
long [] categories = context.getAssetCategoryIds();
String [] tags = context.getAssetTagNames();
} catch (PortalException e) {
e.printStackTrace();
} catch (SystemException e) {
e.printStackTrace();
}
...</code></cite></pre>
<p>
Como se puede ver en el ejemplo, los métodos <strong>getAssetCategoryIds()</strong> y <strong>getAssetTagNames()</strong> nos permiten recuperar las categorías y etiquetas seleccionadas por el usuario a través de la interfaz web.</p>
<h1>
Conclusión</h1>
<p>
Como habéis podido comprobar, la gestión de categorías y etiquetas dentro de nuestros portlets es muy simple. Os recomendamos su uso.</p>
<p>
</p>Jesus Salinas2013-10-06T11:28:40ZLa cache en Liferay 6.1, IntroducciónJesus Salinashttp://www.tekuento.net/inicio/-/blogs/la-cache-en-liferay-6-1-introduccion2013-10-06T11:23:41Z2013-09-20T09:37:59Z<h1>
Introducción</h1>
<p>
La cache es un aspecto muy importante para cualquier sistema que quiere alcanzar prestaciones adecuadas.<strong> <a href="http://liferay.com">Liferay Portal</a></strong> ofrece una integración con diferentes frameworks de cache. Por defecto, se cachean registros, contenido, etc.<strong> </strong></p>
<p>
<a href="http://ehcache.org/"><strong>Ehcache</strong></a> es un framework de cache distribuido muy potente, es un proyecto <em>OpenSource</em> desarrollada en Java. <strong>Liferay Portal</strong>, por defecto, ofrece integración con este framework. La configuración usa una cache en instancias locales. Esto significa que si se está usando un entorno clusterizado, cada nodo tendrá su propia caché. La cache en <strong>Liferay Portal</strong> trabaja sobre los objetos de negocio mediante tres niveles diferentes, todos ellos, por defecto, implementados mediante esta solución:</p>
<ol>
<li>
El primer nivel es el de entidad, <strong>entity level</strong>.</li>
<li>
El segundo es el de <strong>finder</strong>.</li>
<li>
El tercero es el de <strong>Hibernate</strong>.</li>
</ol>
<p>
Estos niveles de cache pueden estar activos o no. La configuración de este elemento, como cualquier otro en Liferay Portal, se define gracias a su fichero de propiedades:</p>
<p>
<code>...<br />
value.object.entity.cache.enabled=true<br />
value.object.finder.cache.enabled=true<br />
...<br />
</code></p>
<h1>
Cache del sistema</h1>
<p>
El framework <strong>EHCache</strong> utiliza un componente llamado <em>Cache Manager</em>, encargado de controlar el ciclo de vida de los objetos cacheados. Un mismo sistema puede trabajar con varios gestores de caché diferentes y, éstos, pueden configurarse mediante ficheros xml. Liferay Portal trabaja con varios Cache Managers diferentes.</p>
<ul>
<li>
<strong>Single-VM CacheManager</strong>: usado para cachear contenidos que no requiren replicación en entornos clusterizados. Son contenidos estáticos. Un ejemplo de este tipo de contenido pueden ser las plantillas de Velocity asociadas a los contenidos web. El fichero de configuración que parametriza el comportamiento de este manager se define mediante la propiedad <strong>ehcache.single.vm.config.location</strong>. Si queremos modificar el comportamiento de este gestor de cache, debemos redefinir en el fichero <strong>portal-ext.properties</strong> la propiedad apuntado a otro fichero.</li>
</ul>
<p>
<code>ehcache.single.vm.config.location=/ehcache/liferay-single-vm.xml</code></p>
<ul>
<li>
<strong>Multi-VM CacheManager</strong>: usado para cachear recursos que sí requieren ser replicados en entornos clusterizados. Estos recursos incluyen respuestas a consultas de tipo finder, entidades, etc. El fichero de configuración que parametriza el comportamiento de este manager se define mediante la propiedad <strong>ehcache.multi.vm.config.location</strong>. Si queremos modificar el comportamiento de este gestor de cache, debemos redefinir en el fichero portal-ext.properties la propiedad apuntando a otra ruta.</li>
</ul>
<p>
<code>ehcache.multi.vm.config.location=/ehcache/liferay-multi-vm-clustered.xml</code></p>
<p>
Por otro lado , la propiedad <strong>ehcache.cache.event.listener.factory</strong> determina la estrategia de replicación utilizada:</p>
<p>
<code>ehcache.cache.event.listener.factory=net.sf.ehcache.distribution.RMICacheReplicatorFactory </code></p>
<p>
<code>#ehcache.cache.event.listener.factory=net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory</code></p>
<h1>
Cache en Hibernate</h1>
<p>
Como ya sabemos, <strong>Liferay Portal</strong> utiliza Hibernate framework en la capa de persistencia. Hibernate soporta dos niveles de caché:<br />
El primer nivel de caché es implementado por el propio framework, en cambio, el segundo se ofrece a través de <strong>frameworks de caché externos</strong>.</p>
<p>
Por defecto, <strong>Liferay Portal</strong> configura Hibernate para que trabaje con <strong>EHCache</strong> como caché de segundo nivel. La propiedad <strong>hibernate.cache.provider_class</strong> detemina dicho proveedor. Como podemos ver, también nos ofrece otro tipo de alternativas:<br />
<br />
<code> <strong>hibernate.cache.provider_class=com.liferay.portal.dao.orm.hibernate.EhCacheProvider</strong><br />
<br />
#hibernate.cache.provider_class=net.sf.hibernate.cache.HashtableCacheProvider<br />
#hibernate.cache.provider_class=com.liferay.portal.dao.orm.hibernate.OSCacheProvider<br />
#hibernate.cache.provider_class=com.liferay.portal.dao.orm.hibernate.TerracottaCacheProvider</code></p>
<p>
Se recomienda trabajar con EHCache en entornos clusterizados. La propiedad <strong>hibernate.cache.region.factory_class</strong> nos permite definir Factorías de regiones de cache para Hibernate mediante la siguiente configuración:</p>
<p>
<code>hibernate.cache.region.factory_class=com.liferay.portal.dao.orm.hibernate.region.SingletonLiferayEhcacheRegionFactory</code></p>
<p>
Si Hibernate utiliza <strong>EHCache</strong> y su factoría de regiones, la propiedad <strong>net.sf.ehcache.configurationResourceName</strong> nos ayuda a configurar su comportamiento:</p>
<p>
<code>net.sf.ehcache.configurationResourceName=/ehcache/hibernate-clustered.xml</code></p>
<p>
Otras propiedades interesantes relacionadas con la configuración de Hibernate son:</p>
<p>
<code> hibernate.cache.use_query_cache=true<br />
hibernate.cache.use_second_level_cache=true<br />
hibernate.cache.use_minimal_puts=true<br />
hibernate.cache.use_structured_entries=false
</code>
</p>Jesus Salinas2013-09-20T09:37:59ZLiferay 6.1, gestión de comentarios avanzada con DISQUSJesus Salinashttp://www.tekuento.net/inicio/-/blogs/liferay-6-1-gestion-de-comentarios-avanzada-con-disqus2013-07-18T15:35:17Z2013-07-18T12:41:38Z<p>
Pasamos a comentar de forma muy simple el proceso para integrar, en general, las gestión de comentarios en Liferay 6.1 y en particular, las entradas de un blog con DISQUS.</p>
<h1>
¿Qué es Disqus?</h1>
<p>
Es un servicio online que ofrece una plataforma centralizada para discusiones en blogs y sitios Web. Nos permite gestionar los comentarios que se generan en nuestros sistemas externalizadamente. Su principal característica es que dispone de muchas herramientas que lo hacen flexible y que nos permiten manejar los comentarios de manera segura. Herramientas que incluyen moderación, tratamiento de spam, bloqueos, estadísticas y mucho más.</p>
<h1>
¿Cómo trabajar con Disqus en Liferay Portal?</h1>
<p>
La primera operación que tenemos que realizar es general e independiente de si trabajamos o no con <strong>Liferay Portal</strong>.</p>
<h2>
Paso 1: Darse de alta en el site Disqus</h2>
<p>
Se accede a la web https://disqus.com y nos damos de alta accediendo a la opción <strong>Sign-in</strong> que se encuentra en la esquina superior derecha:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus1.jpg/2292dd8e-efb5-4b12-a897-3813b75ed04d?t=1374152921731" style="width: 600px; height: 119px;" /></p>
<p>
Para crear una nueva cuenta debemos hacer click sobre <strong>create an account</strong>.</p>
<h2>
Paso 2: Gestión de sitio web desde la cuenta</h2>
<p>
Una vez dado de alta, asociamos al usuario creado una web sobre la que vamos a trabajar. Esta operación se realiza accediendo al <strong>Dashboard</strong> y seleccionando la opción <strong>+Add</strong> asociada a <strong>Your sites</strong>:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus3.jpg/c1c55c73-82ac-4183-9652-a9f92d0bddb0?t=1374152922375" style="width: 600px; height: 190px;" /></p>
<p>
</p>
<p>
Cuando se da de alta un sitio web debemos incluir una serie de parámetros:</p>
<ul>
<li>
<strong>Site url:</strong> por ejemplo, http://www.misitio.com</li>
<li>
<strong>Site name</strong>: por ejemplo, MiSitio</li>
<li>
<strong>Site Shortname</strong>: por ejemplo, misitio.disqus.com</li>
</ul>
<p>
A partir de este momento este usuario podrá gestionar y moderar todos los comentarios que se generen en ese sitio web. El portal sobre el que queremos trabajar ha sido desarrollado mediante Liferay Portal versión 6.1. Veamos que operaciones tenemos que realizar para conseguir este objetivo.</p>
<h2>
Paso 3: Configuración de DISQUS en Liferay 6.1</h2>
<p>
Para ello trabajamos apoyándonos en el <a href="https://www.liferay.com/es/marketplace" target="_blank">marketplace de Liferay</a>. Nos encontramos con App llamada <strong>Social Comments Portlet</strong> que nos permite trabajar con DISQUS de una forma extremadamente sencilla. Ha sido desarrollado por <a href="https://www.liferay.com/es/web/mika.koivisto/profile" target="_blank">Mika Koivisto.</a></p>
<p>
Pasamos a instalarla siguiendo el procedimiento habitual mediante el panel de control de la instancia de Liferay Portal donde deseamos utilizarla.</p>
<p>
</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus4.jpg/5397504d-b2ea-4e58-9fd7-239f5cfc9034?t=1374159272095" style="width: 600px; height: 178px;" /></p>
<p>
</p>
<p>
Una vez instalada, el panel de control se ve modificado, cada sitio web tendrá una nueva sección llamada <strong>Social Comments</strong>.</p>
<p>
</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus6.jpg/c5ff5a47-6b5f-48e5-9556-3bbb965ca75b?t=1374159698024" style="width: 600px; height: 497px;" /></p>
<p>
El usuario debe activar <strong>Disqus</strong> marcando la opción <strong>Habilitado</strong> y en el campo <strong>Short name</strong> debe incluir el nombre corto con el que se ha dado de alta el Sitio web en la cuentas de <strong>Disqus</strong>.</p>
<h2>
Paso 4: Activación de comentarios en el portlet Blog</h2>
<p>
Para poder manejar los comentarios dentro de un blog de Liferay debemos activarlo en la sección de Preferencias.</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus8.jpg/577838f5-0917-4934-ace4-b44d82c38124?t=1374160402032" style="width: 600px; height: 390px;" /></p>
<p>
</p>
<p>
Una vez hecho esto, tenemos configurado el blog para trabajar con comentarios desde DISQUS:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus9.jpg/224e8ab6-fe80-447f-b568-dc690a768d58?t=1374160954554" style="width: 600px; height: 111px;" /></p>
<p>
</p>
<p>
Además, desde el <strong>panel de control en Disqus</strong> con la cuenta de usuario que hemos creado previamente, se podrá controlar de forma avanzada la gestión de comentarios.</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18951/disqus12.jpg/dd375e02-e5be-45d6-97e5-7887eb40c292?t=1374160955022" style="width: 600px; height: 110px;" /></p>
<p>
Desde <strong>Ematiz</strong> recomendamos el uso de este <strong>plugin Liferay </strong>proporcionado por y para la Comunidad, es muy útil y nos ofrece una gestión avanzada de comentarios de los <strong>sitios web</strong> manejados desde cualquier instancia <strong>Liferay Portal</strong>.</p>
<p>
Esperamos que este sencillo post os sea de utilidad y os anime a utilizar esta funcionalidad en vuestros portales.</p>Jesus Salinas2013-07-18T12:41:38ZLiferay 6.1, trabajando con LDAPJesus Salinashttp://www.tekuento.net/inicio/-/blogs/liferay-6-1-trabajando-con-ldap2013-07-17T14:57:15Z2013-07-16T11:57:29Z<p>
En este artículo pretendemos describir el proceso para, <strong>trabajando con LDAP</strong>, Liferay Portal no obligue al usuario a resetear contraseñas ni a tener que contestar a preguntas como <em>¿Cuál es el segundo apellido de tu padre?</em>.</p>
<h1>
Introducción</h1>
<p>
Si no conocéis el panel de control de <strong>Liferay Portal </strong>y cómo de una forma fácil y sencilla podemos <strong>trabajar con LDAP</strong> para gestionar los procesos de autenticación, aquí tenéis una <a href="http://es.slideshare.net/EmatizTecnologia/liferay-version-61-trabajando-con-ldap-introduccion" target="_blank">presentación</a> que os puede ser de interés.</p>
<p>
En principio, si conocéis los <strong>parámetros del LDAP</strong> contra el que váis a trabajar y seguís la presentación anteriormente mencionada, no váis a tener ningún problema a la hora de configurarlo.</p>
<p>
Ahora bien, nos hemos encontrado con el <strong>siguiente problema</strong>, cuando un usuario dado de alta en el LDAP intenta acceder al portal, el sistema le obliga a resetear la contraseña y a contestar la típica pregunta. Alguien hacía referencia a este tema en el <a href="http://issues.liferay.com/browse/LPS-22316?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel">siguiente enlace</a>. He necesitado un rato para ver qué está pasando. Hemos jugado con la configuración definiendo diferentes escenarios:</p>
<h1>
Escenario 1</h1>
<p>
Se activa la configuración LDAP pero sin activar su política de contraseñas.</p>
<h2>
Apartado Autentificación dentro de la sección Configuración:</h2>
<p>
Opción <strong>Habilitado</strong> activo y</p>
<p>
</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18843/LDAP05.jpg/0cfc3fa4-8ebc-4a59-93ca-de507afd7ef8?t=1374052221341" style="width: 638px; height: 165px;" /></p>
<p>
</p>
<p>
<strong> Política de contraseñas</strong> no activo.</p>
<p>
</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18843/LDAP07.jpg/5740cdc3-4649-4a08-a02b-716f67aa99cd?t=1373981518169" style="width: 647px; height: 104px;" /></p>
<p style="text-align: center;">
</p>
<h2>
Apartado Usuarios dentro de la sección Configuración:</h2>
<p>
<span class="aui-field aui-field-choice"><span class="aui-field-content">La opción Se exige aceptar las condiciones de uso</span></span> <strong>NO</strong> activo.</p>
<p>
</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18843/LDAP11.jpg/ec13c781-143e-4e19-bc0b-8cfa5df76770?t=1374052381928" style="width: 636px; height: 227px;" /></p>
<h2>
Resultado:</h2>
<p>
Cuando un usuario dado de alta dentro del LDAP intenta acceder la primera vez al portal, éste no se lo permite. La autenticación <strong>no</strong> se produce adecuadamente. El mensaje de error es:</p>
<p>
<code>08:28:36,429 ERROR [http-bio-8080-exec-6][LDAPAuth:327] Problem accessing LDAP server<br />
com.liferay.portal.<strong>UserPasswordException</strong><br />
at com.liferay.portal.security.pwd.PasswordPolicyToolkit.validate(PasswordPolicyToolkit.java:116)<br />
at com.liferay.portal.security.pwd.ToolkitWrapper.validate(ToolkitWrapper.java:49)<br />
at com.liferay.portal.security.pwd.PwdToolkitUtil.validate(PwdToolkitUtil.java:49)<br />
at com.liferay.portal.service.impl.UserLocalServiceImpl.validate(UserLocalServiceImpl.java:5533)<br />
at com.liferay.portal.service.impl.UserLocalServiceImpl.addUserWithWorkflow(UserLocalServiceImpl.java:668)<br />
at com.liferay.portal.service.impl.UserLocalServiceImpl.addUser(UserLocalServiceImpl.java:539)<br />
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)</code></p>
<h1>
Escenario 2</h1>
<p>
Para cambiar esta situación, activamos la política de contraseñas gestionada desde LDAP.</p>
<h2>
Apartado Autentificación dentro de la sección Configuración:</h2>
<p>
Opción <strong>Habilitado</strong> activo y <strong>Política de contraseñas</strong> activo.</p>
<h2>
Apartado Usuarios dentro de la sección Configuración:</h2>
<p>
<em> <span class="aui-field aui-field-choice"><span class="aui-field-content">Se exige aceptar las condiciones de uso</span></span></em> <strong>NO</strong> activo.</p>
<h2>
Resultado:</h2>
<p>
Cuando un usuario dado de alta dentro del LDAP intenta acceder la primera vez al portal, la autenticación se produce correctamente pero nos pide que respondamos a una <strong>pregunta</strong>, lo que habitualmente llamamos <em>reminder queries</em>.</p>
<h1>
Escenario 3</h1>
<p>
No queremos que aparezcan estas preguntas la primera vez que el usuario accede al sistema</p>
<h2>
Apartado Autentificación dentro de la sección Configuración:</h2>
<p>
Opción <strong>Habilitado</strong> activo y <strong>Política de contraseñas</strong> activo.</p>
<h2>
Apartado Usuarios dentro de la sección Configuración:</h2>
<p>
<em> <span class="aui-field aui-field-choice"><span class="aui-field-content">Se exige aceptar las condiciones de uso</span></span></em> <strong>NO</strong> activo.</p>
<h2>
Fichero portal-ext.properties</h2>
<p>
<code>users.reminder.queries.enabled=false<br />
users.reminder.queries.custom.question.enabled=false</code></p>
<h2>
Resultado:</h2>
<p>
Cuando un usuario dado de alta dentro del LDAP intenta acceder la primera vez al portal, la autenticación se produce correctamente y accede al portal.</p>Jesus Salinas2013-07-16T11:57:29ZTextos internacionalizados en Liferay 6.1 manejados en JavaScriptJesus Salinashttp://www.tekuento.net/inicio/-/blogs/textos-internacionalizados-en-liferay-6-1-manejados-en-javascript2013-07-03T12:31:37Z2013-05-24T07:44:06Z<p>
La internacionalización en Liferay Portal es tema muy conocido. Como ya sabemos, nos apoyamos en ficheros de propiedades para definir los textos a manejar dentro de la web. El problema aparece cuando queremos manejar y mostrar estos texto mediante funciones JavaScript. </p>
<p>
Vamos a ilustrar con un ejemplo la situación comentada. Se diseña una página en la que el usuario puede realizar un conjunto de operaciones:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/0/img00.png/df806433-ad25-4486-8073-2d2c4747e060?t=1369382332000?t=1369382332961" style="width: 559px; height: 124px;" /></p>
<p>
El usuario puede hacer click sobre cualquiera de los iconos que aparecen en la página para desencadenar operaciones (borrado, edición, etc). En determinadas ocasiones, nos interesará solicitar una confirmación de la operación mediante un popup JavaScript:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/0/img01.png/de50bdc6-dc77-4bb4-93d9-1fd907adda52?t=1369382737000?t=1369382737137" style="width: 336px; height: 132px;" /></p>
<p>
Queremos que estos mensajes estén internacionalizados. La función JavaScript es muy simple:</p>
<p style="margin-left: 40px;">
<code>function confirmOperation(message){<br />
var agree =<br />
confirm(message);<br />
if (agree)<br />
return true ;<br />
else<br />
return false ;<br />
}</code></p>
<p>
Desde Liferay, tenemos que inyectar el mensaje en la función JavaScript. A continuación, planteamos una posible estrategia. Supongamos que action.jsp es la vista que nos muestra las diferentes operaciones sobre las que el usuario puede actuar, vamos a trabajar sobre la acción deleteTeam:</p>
<p>
<code> <portlet:actionURL name="deleteTeam" var="deleteTeam"><br />
<portlet:param name="teamId" value="<%=teamId %>"></portlet:param><br />
</portlet:actionURL></code></p>
<p>
<code> ...</code></p>
<p>
<code> <span><br />
<aui:a href="<%= deleteTeam%>" title="delete-team" cssClass="simple" ><br />
<img alt="delete-team" src="<%= srcDelete%>" /><br />
</aui:a><br />
</span></code></p>
<p>
Para incorporar la función Javascript trabajamos con una clase genérica llamada <strong>ConfirmationUtil</strong> que modifica la url, en este caso, <strong>srcDelete</strong>, para inyectar la función Javascript:</p>
<p>
<code>public class <strong>ConfirmationUtil</strong> {<br />
<br />
public static String addConfirmation(PageContext pageContext, String url, String message){<br />
<br />
if (url.startsWith("javascript:")) {<br />
url = url.substring(11);<br />
}<br />
<br />
if (url.startsWith(Http.HTTP_WITH_SLASH) || url.startsWith(Http.HTTPS_WITH_SLASH)) {<br />
url = "submitForm(document.hrefFm, '" + HttpUtil.encodeURL(url) + "');";<br />
}<br />
<br />
if (url.startsWith("wsrp_rewrite?")) {<br />
url = StringUtil.replace(url, "/wsrp_rewrite", "&wsrp-extensions=encodeURL/wsrp_rewrite");<br />
url = "submitForm(document.hrefFm, '" + url + "');";<br />
}<br />
// La variable message almacena el mesaje que se mostrara en el ventana<br />
String message = LanguageUtil.get(pageContext, message);<br />
<br />
url = "javascript:if (<strong>confirm</strong>('" + </code><code>message</code><code> + "')) { " + url + " } else { self.focus(); }";<br />
<br />
return url;<br />
}<br />
}</code></p>
<p>
La página action.jsp quedaría tal que así:</p>
<p>
<code> <</code><code>portlet:actionURL name="deleteTeam" var="deleteTeam"><br />
<portlet:param name="teamId" value="<%=teamId %>"></portlet:param><br />
</portlet:actionURL></code></p>
<p>
<code> <% </code></p>
<p>
<code>deleteTeam = ConfirmationUtil.addConfirmation(<br />
pageContext,<br />
deleteTeam,<br />
"are-you-sure-you-want-to-delete-team");</code></p>
<p>
<code> %></code></p>
<p>
<code><code> <span><br />
<aui:a href="<%= deleteTeam%>" title="delete-team" cssClass="simple" ><br />
<img alt="delete-team" src="<%= srcDelete%>" /><br />
</aui:a><br />
</span></code></code></p>
<p>
<code> </code></p>
<p>
<code> </code></p>
<p>
<code> </code></p>
<p>
<code>El fichero de propiedades, por ejemplo, Language_es.properties (UTF-8):</code></p>
<p>
...</p>
<p>
<code>are-you-sure-you-want-to-delete-team=<strong>Esta seguro de que desea eliminar este grupo de trabajo</strong></code><br />
<strong>...</strong></p>
<p>
La ventaja emergente será tal que así:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/0/img02.png/5ebc1e33-68a2-4596-a13d-dfeece98c12a?t=1369385310000?t=1369385310433" style="width: 336px; height: 138px;" /></p>
<p>
Si deseamos que el texto que muestra sea <em>"¿Está seguro de que desea eliminar este grupo de trabajo?"</em> debemos modificar el fichero de propiedades, pero ¿cómo?:</p>
<p>
Opción 1:</p>
<p>
...<br />
<code>are-you-sure-you-want-to-delete-team=<strong>¿Está seguro de que desea eliminar este grupo de trabajo</strong></code>?<br />
<strong>...</strong></p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/0/img03.png/4ffd8727-62fe-44e2-a3ec-43eda21c1274?t=1369385593000?t=1369385593000" style="width: 356px; height: 131px;" /></p>
<p>
</p>
<p>
Opción 2:</p>
<p>
...<br />
<code>are-you-sure-you-want-to-delete-team=¿Está seguro de que desea eliminar este grupo de trabajo?</code><br />
<strong>...</strong></p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/0/img04.png/d1a33cbf-46e5-4458-91cb-b956ba5a6467?t=1369386983000?t=1369386983436" style="width: 417px; height: 130px;" /></p>
<p>
Encontramos problemas cuando manejamos este tipo de caracteres. Probablemente existan diferentes soluciones, describimos la que utilizamos habitualmente:</p>
<p>
Paso 1: Ficheros de i18n siempre en formato UTF-8, por tanto, quedaría para esa entrada tal que así:</p>
<p>
...<br />
<code>are-you-sure-you-want-to-delete-team=¿Está seguro de que desea eliminar este grupo de trabajo?</code><br />
<strong>...</strong></p>
<p>
Paso 2: Modificación de la clase ConfirmationUtil:</p>
<p>
<strong><code>public class ConfirmationUtil {<br />
<br />
public static String addConfirmation(PageContext pageContext, String url, String message){<br />
<br />
if (url.startsWith("javascript:")) {<br />
url = url.substring(11);<br />
}<br />
<br />
if (url.startsWith(Http.HTTP_WITH_SLASH) || url.startsWith(Http.HTTPS_WITH_SLASH)) {<br />
url = "submitForm(document.hrefFm, '" + HttpUtil.encodeURL(url) + "');";<br />
}<br />
<br />
if (url.startsWith("wsrp_rewrite?")) {<br />
url = StringUtil.replace(url, "/wsrp_rewrite", "&wsrp-extensions=encodeURL/wsrp_rewrite");<br />
url = "submitForm(document.hrefFm, '" + url + "');";<br />
}<br />
// La variable message almacena el mesaje que se mostrara en el ventana</code></strong><br />
<code> <span style="display: none;"> </span>String htmlMessage = LanguageUtil.get(pageContext, message);</code></p>
<p>
<code>// Se aplica una transformacion sobre el mensaje I18n transformando</code></p>
<p>
<code>// su formato HTML en texto.<br />
<strong>String message = HtmlUtil.extractText(htmlMessage);</strong> <br />
url = "javascript:if (confirm('" + message + "')) { " + url + " } else { self.focus(); }";</code><strong><code><span style="display: none;"> </span><br />
<br />
return url;<br />
}<br />
}</code></strong></p>
<p>
</p>Jesus Salinas2013-05-24T07:44:06ZTrabajando con JavaScript con JSF+RichFaces: jsFunctionJesus Salinashttp://www.tekuento.net/inicio/-/blogs/trabajando-con-javascript-con-jsf-richfaces-jsfunction2013-07-02T10:28:49Z2013-05-17T08:59:48Z<h1>
Introducción</h1>
<p>
El <strong>framework RichFaces</strong> nos permite manejar <strong>JavaScript</strong> dentro de nuestras páginas de una forma muy elegante. Una de las alternativas más utilizadas es el componente <strong>a4j:jsFunction</strong> nos permite hacerlo de una forma sencilla.</p>
<h1>
El componente a4j:jsFunction</h1>
<h2>
Introducción</h2>
<p>
Permite crear funciones JavaScript de una forma algo especial.</p>
<p>
<strong>IMPORTANTE</strong>: Debe definirse <strong>SIEMPRE</strong> dentro de un formulario.<br />
Su sintaxis se muestra a continuación:</p>
<pre>
<a4j:jsFunction
name=""
action="">
...
</a4j:jsFunction>
</pre>
<p>
<a4j:jsfunction action="" name=""></a4j:jsfunction>Sus atributos más importantes son name, action y render.</p>
<h2>
Atributo name</h2>
<p>
Permite definir el <strong>nombre</strong> de la función <strong>JavaScript</strong> que posteriormente podrá ser ejecutada <strong>desde cualquier componente</strong> dentro de alguno de sus atributos de tipo evento. Por ejemplo:</p>
<pre>
<a4j:form>
<h:inputText value="#{js.texto2}"></h:inputText>
...
<a4j:jsFunction name="<strong>metodo1</strong>" ...>
</a4j:jsFunction>
</a4j:form></pre>
<p>
</p>
<pre>
<span onclick="<strong>metodo1</strong>();">accion</span></pre>
<p>
</p>
<p>
Al hacer <strong>click sobre el span</strong> accion se <strong>ejecuta la función JavaScript</strong> definida mediante el componente js:Function dentro de un formulario.</p>
<h2>
Atributo action</h2>
<p>
Permite desencadenar una <strong>acción</strong> en <strong>servidor</strong> mediante la función JavaScript. El lenguaje de expresión permite identificar el <strong>nombre del bean</strong> y el <strong>nombre del método</strong> a ejecutar. Por ejemplo:</p>
<pre>
<a4j:form>
<h:inputText value="#{js.texto2}"></h:inputText>
<a4j:jsFunction name="<strong>metodo1</strong>" <strong>action</strong>="#{<strong>js.accion</strong>}">
</a4j:jsFunction>
</a4j:form></pre>
<p>
</p>
<pre>
...
public class BeanJS {
...
public String accion(){
...
}
</pre>
<p>
De tal forma que, desde cualquier <strong>punto de la página</strong> se podrá activar el envío de un <strong>determinado formulario</strong> sin más que, por ejemplo:</p>
<pre>
...
<span onclick="<strong>metodo1();</strong>">accion</span>
...</pre>
<h1>
Paso de parámetros</h1>
<p>
Podemos utilizar funciones JavaScript que reciben parámetros:</p>
<pre>
<span onmouseover="<strong>metodo1('valor1</strong><strong>');</strong>">Prueba</span></pre>
<p>
</p>
<p>
El componente <strong>a4j:actionparam</strong> permite definir parámetros y asignarlos donde sea necesario:<a4j:form></a4j:form></p>
<pre>
<a4j:form>
<a4j:jsFunction name="metodo1" action="#{js.accion}">
<<strong>a4j:actionparam</strong> name="param1" assignTo="#{js.texto}"/>
</a4j:jsFunction>
</a4j:form></pre>
<h1>
Renderización parcial</h1>
<p>
La <strong>función JavaScript</strong>, además de desencadenar acciones, es capaz de <strong>re-renderizar parcialmente la respuesta</strong> generada posteriormente, como muchos otros componentes.<br />
Los atributos que podemos utilizar son, como en cualquier otro componente:</p>
<ul>
<li>
reRender.</li>
<li>
process.</li>
<li>
...</li>
</ul>
<p>
Ejemplo con el atributo <strong>render</strong>:</p>
<pre style="margin-left: 40px;">
<a4j:form>
<a4j:jsFunction name="metodo1" action="#{js.accion}"
<strong> render=”campo01”</strong>>
<<strong>a4j:actionparam</strong> name="param1" assignTo="#
{js.dato1}"/>
</a4j:jsFunction>
</a4j:form>
<div>
<h:outputText id="campo01" value="#{js.dato1}" />
</div></pre>
<div>
<h:outputtext id="campo01" value="#{js.dato1}"> </h:outputtext></div>
<h1>
Conclusión</h1>
<p>
El programador debe familiarizarse con el uso de este componente, dado que muchas funcionalidades JavaScript podrán resolverse gracias a él.</p>
<p>
</p>Jesus Salinas2013-05-17T08:59:48ZTruco para Alfresco: Como crear propiedades internacionalizadas ( I18N ) en AlfrescoJesus Salinashttp://www.tekuento.net/inicio/-/blogs/truco-para-alfresco-como-crear-propiedades-internacionalizadas-i18n-en-alfresco2013-07-02T09:52:32Z2013-07-02T09:17:14Z<div class="content">
<h2>
Metadata i18n</h2>
<p>
Os dejo aquí un pequeño truco para crear propiedades internacionalizadas en nuestros modelos de contenidos personalizados de Alfresco. Es una característica que considero muy importante y de la que no he encontrado documentación, por eso este post.</p>
<p>
Cuando hablo de propiedades internacionalizadas me refiero a metadatos que pueden tomar distintos valores para cada idioma. El modelo de contenidos por defecto del ECM ya aplica esta característica para el título y la descripción de un contenido. Puedes crear un contenido y probar a loguearte en varios idiomas, editando el título o la descripción para cada uno de ellos. Observarás que Alfresco almacena las dos cadenas y las asocia con el LOCALE o idioma con el que te habías logueado.</p>
<h2>
La solución</h2>
<p>
La solución pasa por crear la propiedad asignándole el tipo cm:mltext y posteriormente visualizarla en la consola de Alfresco Explorer a través de component-generator="MultilingualTextAreaGenerator" .</p>
<p>
Ejemplo en nuestro modelo de contenidos (customModel.xml):</p>
<pre>
<type name="tekuento:mitipo">
<title>Type with internacionalized metadata</title>
<parent>cm:content</parent>
<properties>
.....
<property name="tekuento:i18nmetadata">
<type><span style="color: rgb(255, 0, 0);">d:mltext</span></type>
</property>
.....
</properties>
</type></pre>
<p>
Ejemplo en nuestro fichero de personalización de Explorer (web-client-config-custom.xml):</p>
<pre>
<config evaluator="node-type" condition="tekuento:mitipo">
<property-sheet>
...
<show-property name="tekuento:i18nmetadata" component-generator="<span style="color: rgb(255, 0, 0);">MultilingualTextAreaGenerator</span>">
</show-property>
...
</property-sheet>
</pre>
<p>
Para Alfrescho Share no hay que hacer ningún tipo de modificación. Directamente le mostrará el valor del metadato al usuario en el idioma que tenga en su navegador (ya que lee la cabecera de la petición HTTP "Accept-Languages"). He testeado esta característica en la versión 4.0 Alfresco Community.</p>
<p>
Espero que os sea de utilidad. Saludos!</p>
</div>Jesus Salinas2013-07-02T09:17:14ZConsultas dinámicas (Dynamic Query) en Liferay 6Jesus Salinashttp://www.tekuento.net/inicio/-/blogs/consultas-dinamicas-dynamic-query-en-liferay-62013-07-19T09:17:26Z2013-07-01T17:27:42Z<p>
Las <strong>consultas dinámicas</strong> son una herramienta muy útil para construir <strong>métodos</strong> en cualquiera de los <strong>servicios Liferay</strong>.</p>
<p>
Estos métodos serán capaces de realizar búsquedas avanzadas en función de diferentes criterios sobre cualquiera de las entidades que maneja Liferay.</p>
<h1>
Introducción</h1>
<p>
Todos los servicios de capa de negocio generados mediante el <strong>Service Builder</strong> incorporan un método sobrecargado llamado <strong>dynamicQuery()</strong>:</p>
<pre>
<code>public static List dynamicQuery(DynamicQuery dynamicQuery)
throws SystemException
</code></pre>
<pre>
<code>public static List dynamicQuery(DynamicQuery dynamicQuery,
int start,int end)
throws SystemException
</code></pre>
<pre>
<code>public static List dynamicQuery(DynamicQuery dynamicQuery,
int start,int end,
OrderByComparator orderByComparator)
throws SystemException
</code></pre>
<p>
</p>
<p>
Estos métodos nos permiten recuperar una <strong>lista de entidades</strong> que cumplen una determinada condición.</p>
<p>
Esta condición se define gracias a un conjunto de criterios definidos mediante un objeto que implementa la interface <strong>DynamicQuery</strong>, por lo tanto, aprender a construir <strong>búsquedas avanzadas</strong> implica aprender a crear este <strong>tipo de objeto</strong>. Se pasa a describir a continuación el proceso para conseguirlo:</p>
<ul>
<li>
Creación de la consulta.</li>
<li>
Parametrización de la consulta.</li>
<li>
Ejecución de la consulta.</li>
</ul>
<h1>
Interface DynamicQuery</h1>
<p>
El conocimiento profundo de esta <strong>interface</strong> nos permitirá construir consultas avanzadas. Se encuentra en el paquete <strong>com.liferay.portal.kernel.dao.orm</strong>:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18038/1.jpg/aeefa41a-eb2c-4e32-b2b8-0a18f2ceff49?t=1374224801054" style="width: 403px; height: 398px;" /></p>
<p>
El método add() permite añadir condiciones sobre la búsqueda y el método addOrder() criterios de ordenación.</p>
<p>
Los programadores que trabajan habitualmente con <strong>Hibernate</strong> y su interface <strong>Criteria</strong> deben estar familiarizados con estos conceptos.<br />
Ahora bien,</p>
<p class="rteindent1">
<strong>¿cómo creo un objeto que implemente la interface DynamicQuery?.</strong></p>
<p class="rteindent1">
<strong>¿busco una implementación?.</strong></p>
<p>
A continuación, respondemos a estas preguntas.</p>
<h1>
Creación de consultas</h1>
<p>
La clase <strong>DynamicQueryFactoryUtil</strong> se encarga de construir las consultas dinámicas:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18038/2.jpg/44bb928e-5601-4c33-a092-b91a11851923?t=1374224801571" style="width: 498px; height: 331px;" /></p>
<p class="rtecenter">
</p>
<p>
El método <strong>forClass</strong> es el responsable de la generación del objeto:</p>
<pre>
public static DynamicQuery forClass(Class<!--?--> clazz,
ClassLoader classLoader)</pre>
<ul>
<li>
clazz: NombreDeClase.class.</li>
<li>
classLoader: cargador de clases.</li>
</ul>
<p>
Ejemplo:</p>
<pre>
DynamicQuery consulta =
DynamicQueryFactoryUtil.forClass(
JournalArticle.class,
PortalClassLoaderUtil.getClassLoader());</pre>
<p>
<br />
Si la consulta se realiza sobre una <strong>entidad de Liferay</strong> debemos incluir el cargador de clases.</p>
<pre>
DynamicQuery consulta = DynamicQueryFactoryUtil.forClass(
JournalArticle.class,
PortalClassLoaderUtil.getClassLoader());
</pre>
<p>
En cambio, si se realiza sobre una entidad creada por el desarrollador no debemos incluirlo:</p>
<pre>
DynamicQuery consulta =
DynamicQueryFactoryUtil.forClass(ExpenseSheet.class);</pre>
<p>
<img alt="" src="file:///Users/ematiz/Library/Caches/TemporaryItems/moz-screenshot.png" /></p>
<h1>
Parametrización de la consulta</h1>
<p>
Una vez que se ha creado el objeto, se deben añadir <strong>criterios de búsqueda y ordenación </strong>que definen la consulta:</p>
<ul>
<li>
DynamicQuery <strong>add</strong>(Criterion criterion): el método add() permite incluir nuevos criterios de búsqueda en la consulta.</li>
<li>
DynamicQuery <strong>addOrder</strong>(Order order): el método addOrder() permite definir los criterios de ordenación a aplicar.</li>
</ul>
<p>
Un criterio se define como un objeto que implementa la interface <strong>Criterion</strong>. Una ordenación se define como un objeto que implementa la interface <strong>O</strong><strong>rder</strong>.</p>
<h1>
Definición de criterios de búsqueda</h1>
<p>
La clase <strong>RestrictionsFactoryUtil</strong> define un conjunto de <strong>métodos estáticos</strong> muy útiles que construyen objetos que implementan la interface <strong>Criterion</strong>, es decir, estos métodos nos permiten definir las <strong>restricciones a aplicar en la consulta</strong>:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18038/3.jpg/0e0a2a72-ce54-48c3-8b6d-5c9fe581e9cb?t=1374224802109" style="width: 480px; height: 333px;" /></p>
<p>
Algunos de estos <strong>métodos</strong> estáticos son:</p>
<ul>
<li>
eq: define una restricción de tipo <strong>igual que</strong>.</li>
<li>
ge: define una restricción de tipo <strong>mayor o igual que</strong>.</li>
<li>
gt: define una restricción de tipo <strong>mayor que</strong>.</li>
<li>
ilike: define una restricción <strong>ilike</strong>.</li>
<li>
isNotNull: define una restricción de tipo <strong>no es nulo</strong>.</li>
<li>
le: define una restricción de tipo <strong>menor o igual que</strong>.</li>
<li>
like: función like.</li>
<li>
lt: menor que.</li>
<li>
ne: no es igual.</li>
<li>
...</li>
</ul>
<p>
Además, proporciona métodos que permiten construir una consulta con <strong>Funciones and</strong> y <strong>or</strong> de forma flexible:</p>
<ul>
<li>
public static Conjunction <strong>conjunction</strong>()</li>
<li>
public static Disjunction <strong>disjunction</strong>().</li>
<li>
public static Criterion <strong>and</strong>(Criterion lhs, Criterion rhs)</li>
<li>
public static Criterion <strong>or</strong>(Criterion lhs, Criterion rhs)</li>
</ul>
<h1>
Definición de ordenación</h1>
<p>
La clase <strong>OrderFactoryUtil</strong>:</p>
<p style="text-align: center;">
<img alt="" src="http://www.tekuento.net/documents/11022/18038/4.jpg/e346d50c-93bf-44cc-aff0-5c14a68db256?t=1374224802438" style="width: 516px; height: 343px;" /></p>
<p>
</p>
Proporciona dos métodos para definir ordenación ascendente o descendente en función de una propiedad:</p>
<ul>
<li>
public static Order <strong>asc</strong>(String propertyName)</li>
<li>
public static Order <strong>desc</strong>(String propertyName)</li>
</ul>
<h1>
Ejecución de la búsqueda</h1>
<p>
Una vez que la consulta ha sido creada y parametrizada adecuadamente, ésta debe ser pasada como parámetro al método <strong>dynamicQuery</strong><strong>()</strong> asociado a cualquiera de los servicios de negocio nos permite recuperar una lista con los objetos que cumplen la condición definida por la consulta:</p>
<pre>
public static List dynamicQuery(DynamicQuery dynamicQuery)
throws SystemException
</pre>
<p>
Ejemplo:</p>
<pre>
List<calevent> eventos =
CalEventLocalServiceUtil.dynamicQuery(consulta);
</calevent></pre>
<h1>
Ejercicio</h1>
<p>
Para ilustrar este artículo, se describe a continuación, cómo incorporar nuevas <strong>funcionalidades de búsqueda</strong> sobre una entidad definida por el usuario llamada <strong>Informe</strong>.</p>
<p>
El fichero <strong>service.xml</strong> que se ha utilizado para este ejemplo se muestra a continuación:</p>
<pre>
<service-builder package-path="com.ematiz.business">
<author>Jesus Salinas</author>
<namespace>Tekuento</namespace>
<entity local-service="true" name="Informe" remote-service="false" uuid="true">
<column name="informeId" primary="true" type="long"></column>
<column name="userId" type="long"></column>
<column name="companyId" type="long"></column>
<column name="groupId" type="long"></column>
<column name="userName" type="String"></column>
<column name="titulo" type="String"></column>
<column name="informacion" type="String"></column>
</entity>
</service-builder></pre>
<p>
El método que se crea a modo de ejemplo se llama <strong>getInformesByTituloOrUserName()</strong>. Como su nombre indica este método recupera todos los informes que tengan un determinado título o que haya sido creado por un determinado usuario.</p>
<p>
Este método debe <strong>OBLIGATORIAMENTE</strong> ser incorporado en la clase <strong>InformeLocalServiceImpl</strong>.</p>
<pre>
/**
* El metodo getInformesByTituloOrUserName
* recupera todos los informes que tengan
* el titulo pasado como parametro o que hayan sido
* creado por el usuario pasado
* como parametro.
*
* @param titulo titulo del informe.
* @param userName nombre del usuario que ha
* creado el informe.
* @return lista de informes que cumple la condicion
* pasada como parametros.
* @throws SystemException
*/
@SuppressWarnings("unchecked")
public List<informe> getInformesByTituloOrUserName(String titulo, String userName)
throws SystemException{
<strong> // Paso 1: Creacion de la consulta.</strong>
DynamicQuery consulta = DynamicQueryFactoryUtil.forClass(Informe.class);
<strong> // Paso 2: Parametrizacion de la consulta.</strong>
// Paso 2.1: Se crea el criterio asociado al
// titulo del informe.
// En este caso se utiliza la funcion ilike.
Criterion criterio1 = null;
if (titulo != null && !titulo.equals("")) {
// Si titulo existe se construye la restriccion.
criterio1 = RestrictionsFactoryUtil.ilike("titulo", "%"
+ titulo + "%");
}
// Paso 2.2: Se crea el criterio asociado al
// nombre de usuario que ha creado el informe.
// En este caso se utiliza la funcion eq.
// Debe coincidir exactamente.
Criterion criterio2 = null;
if (userName != null && !userName.equals("")) {
// Si userName existe se construye la
// restriccion.
criterio2 = RestrictionsFactoryUtil.eq("userName", userName);
}
// Paso 2.3: Se construye la funcion OR.
Disjunction funcionOr = RestrictionsFactoryUtil.disjunction();
if(criterio1!=null){
funcionOr.add(criterio1);
}
if(criterio2!=null){
funcionOr.add(criterio2);
}
// Paso 2.4: Se cargan todos los criterios en la consulta.
consulta.add(funcionOr);
<strong> // Paso 3: Se ejecuta la consulta previamente creada.</strong>
return dynamicQuery(consulta);
}
</informe></pre>
<h1>
Conclusión</h1>
<p>
La generación de <strong>servicios Liferay</strong> mediante el <strong>Service Builder</strong> se complementa mediante la incorporación de nueva funcionalidad de forma manual.</p>
<p>
El conocimiento profundo de las <strong>consultas dinámicas</strong> permite al programador adaptar sus servicios de forma específica incorporando <strong>búsquedas avanzadas</strong> haciéndolos más completos y útiles.</p>
<p>
</p>Jesus Salinas2013-07-01T17:27:42ZValidación de certificados digitales (PKI)Jesus Salinashttp://www.tekuento.net/inicio/-/blogs/validacion-de-certificados-digitales-pki-2013-07-19T09:20:39Z2013-07-01T21:14:57Z<h1>
Validación de certificados</h1>
<p>
Se describe, a continuación, el proceso a llevar a cabo cuando se quiere verificar la validez de un certificado digital en formato X509.<br />
A partir de la información que proporciona un certificado, una aplicación debe ser capaz de comprobar si es válido o no.<br />
¿El estándar permite manejar esa información?</p>
<p>
Para ello necesita saber cuál es su lista de revocación asociada y cómo consultarla.<br />
La clase <strong>X509Certificate</strong> no proporciona ningún mecanismo para extraer esa información.</p>
<p>
Para ello se cambia de implementación, se trabajará con una clase hija mucho más específica, X509CertImpl. Esta clase tiene un método que proporciona información sobre los puntos de distribución de las CRLs:</p>
<ul>
<li>
getCRLDistributionPointsExtension().</li>
<li>
Este método devuelve un objeto de tipo CRLDistributionPointsExtension.</li>
</ul>
<p>
Esta clase tiene un método que proporciona información sobre los puntos de distribución de las CRLs:</p>
<ul>
<li>
getCRLDistributionPointsExtension().</li>
<li>
Este método devuelve un objeto de tipo CRLDistributionPointsExtension.</li>
</ul>
<p>
A partir de los puntos de distribución se puede extraer la url asociada a la lista de revocación.</p>Jesus Salinas2013-07-01T21:14:57ZProgramando contenidos en Liferay 6Jesus Salinashttp://www.tekuento.net/inicio/-/blogs/programando-contenidos-en-liferay-62013-07-19T09:23:52Z2013-06-13T14:23:28Z<h1>
¿Qué es un contenido?</h1>
<p>
Un contenido será cualquier entidad que podemos <strong>etiquetar o categorizar</strong>. Liferay los identifica con el término <strong>asset</strong> y proporciona un <strong>framework</strong> para su gestión. Los <strong>tipos de contenido</strong> que Liferay maneja por defecto son:</p>
<ul>
<li>
Contenidos web (Web Content).</li>
<li>
Imágenes de la galería de Imágenes (Image Gallery Image).</li>
<li>
Entradas de blog (Blog Entry).</li>
<li>
Documentos de la biblioteca de documentos (Document Library Document).</li>
<li>
Marcador (Bookmark).</li>
<li>
Mensaje en un foro (Message Board message).</li>
<li>
Página en una Wiki (Wiki page).</li>
<li>
Evento de calendario (Calendar Event).</li>
</ul>
<h1>
La API para manejar Assets</h1>
<p>
Para trabajar con asset se debe manejar dos conceptos, su <strong>modelo</strong> y su <strong>servicio</strong> asociados.</p>
<p>
La interface <strong>AssetEntry</strong> representa el concepto de asset y su tabla asociada en la base de datos de Liferay se llama <strong>assetentry</strong>. Sus campos más importantes son:</p>
<ul>
<li>
<strong>entryId</strong>: identificador único del asset.</li>
<li>
<strong>userId</strong>: identificador del usuario que crea el contenido.</li>
<li>
<strong>groupId</strong>: identifica el alcance en el que ha sido creado.</li>
<li>
<strong>classNameId</strong>: identifica el tipo de contenido. Es el identificador de la clase asociado al contenido, se encuentra representado mediante la entidad <strong>ClassName</strong> y la tabla <strong>classna</strong><strong>me_</strong>.</li>
</ul>
<ul>
<li>
<strong>classPK</strong>: identifica el contenido específico asociado al asset. Suele ser la <strong>clave primaria</strong> de la tabla donde se almacena el contenido.</li>
</ul>
<p>
El concepto <strong>asset</strong> se encuentra estrechamente relacionado con el concepto de <strong>categoría</strong> y <strong>etiqueta</strong>. Efectivamente, en la base de datos de Liferay se encuentran dos tablas que definen esa relación:</p>
<ul>
<li>
assetentries_assetcategories.</li>
<li>
assetentries_assettags.</li>
</ul>
<p>
La clase <strong>AssetEntryLocalServiceUtil</strong> proporciona los métodos que nos permitirán manejar estos contenidos, recuperándolos, editándolos, etc.</p>
<p>
El ejemplo que se muestra a continuación ilustra lo anteriormente comentado:</p>
<p>
int total = AssetEntryLocalServiceUtil.<strong>getAssetEntriesCount</strong>();</p>
<p>
int total = AssetEntryLocalServiceUtil.<strong>getAssetEntriesCount</strong>();</p>
<p>
List contenidos = AssetEntryLocalServiceUtil.getAssetEntries(0,total);</p>
<p>
for(int i=0;i<contenidos.size();i++){</p>
<p>
AssetEntry c = contenidos.get(i);</p>
<p>
...<br />
}</p>
<p>
Una vez definidos <strong>AssetEntry y de AssetEntryLocalServiceUtil</strong>, se pasa a describir uno de los tipos de contenidos más utilizados, los <strong>contenidos web</strong>.</p>
<h1>
Contenidos web</h1>
<h2>
Introducción</h2>
<p>
Los <strong>contenidos web</strong> en Liferay son manejados mediante un conjunto de servicios y modelos y son almacenados en la base de datos en la tabla <strong>journalarticle</strong>. Sus campos más significativos son:</p>
<ul>
<li>
Identificador del contenido: <strong>articleId</strong>.</li>
<li>
Título: <strong>title</strong>.</li>
<li>
Descripción: <strong>description</strong>.</li>
<li>
Identificador de la estructura asociada al contenido: <strong>structureId</strong>.</li>
<li>
Identificador de la plantilla asociada al contenido: <strong>templateId</strong>.</li>
<li>
Identificador de grupo: <strong>groupId</strong>.</li>
<li>
Identificador de usuario: <strong>userId</strong>.</li>
<li>
Información del contenido: <strong>content</strong>.</li>
</ul>
<p>
Un contenido web en Liferay es un objeto que implementa la interface <strong>JournalArticle</strong> que se encuentra en el paquete com.liferay.portlet.journal.model:</p>
<p>
Son manejados mediante la clase <strong>JournalArticleLocalServiceUtil</strong>.</p>
<p>
List<JournalArticle> lista = JournalArticleLocalServiceUtil.getArticles(scopeGroupId); for(int i=0;i<lista.size();i++){<br />
JournalArticle journalArticle = lista.get(i);<br />
...</p>
<p>
Esta clase proporciona métodos para recuperar, actualizar, borrar y buscar contenidos web, de la misma forma en la que lo hacen todos los servicios de Liferay. A continuación, pasamos a describir alguna de las operaciones más habituales.</p>
<h2>
Mostrando contenidos</h2>
<p class="rteindent1">
¿La entidad <strong>JournalArticle</strong> tiene acceso a la información del contenido web? ¿El método <strong>getContent</strong>() me permite recuperar el contenido del contenido web? ¿Esto tiene que ver con <strong>estructuras</strong> y <strong>plantillas</strong>?</p>
<p>
Vamos a intentar aclarar estas preguntas a continuación.</p>
<h3>
Trabajando con plantillas y estructuras</h3>
<p>
Cuando se crea un contenido web con una <strong>estructura</strong> y una <strong>plantilla específicas,</strong> el proceso para mostrar el contenido web es algo diferente.<br />
El método <strong>getContent() </strong>de la interface JournalArticle <strong>NO</strong> es válido.<br />
Para mostrar el contenido generado como resultado de la aplicación de una estructura y una plantilla específicas aparece la entidad <strong>JournalArticleDisplay</strong>.</p>
<p>
La interface <strong>JournalArticleDisplay</strong> y su método <strong>getContent()</strong> muestra el contenido web <strong>tras la aplicación</strong> de su estructura y plantilla asociada.</p>
<p>
<code>// Paso 1: Se recuperan todos los contenidos web<br />
// que se encuentran en la comunidad donde se ha<br />
// desplegado el portlet.<br />
List<JournalArticle> lista = JournalArticleLocalServiceUtil.getArticles(scopeGroupId);<br />
for(int i=0;i<lista.size();i++){<br />
// Paso 2: Se recupera el articulo que se desea mostrar.<br />
JournalArticle journalArticle = lista.get(i);<br />
// Paso 3: Se recupera el idioma utilizado.<br />
String languageId = LanguageUtil.getLanguageId(request);<br />
// Paso 4: Se recupera el objeto JournalArticleDisplay<br />
JournalArticleDisplay articleDisplay = JournalArticleLocalServiceUtil.getArticleDisplay(<br />
journalArticle, null, null, languageId, 1, null, themeDisplay);</code></p>
<p>
Una vez recuperado el objeto de tipo JournalArticleDisplay asociado a un determinado artículo, se podrá mostrar en cualquier página, tal que así:</p>
<p>
<code>...<br />
<div><br />
<%= articleDisplay.getContent() %><br />
</div><br />
...</code></p>
<h2>
Extracción de campos específicos</h2>
<p class="rteindent1">
¿Cómo se pueden recuperar <strong>campos específicos</strong> de un contenido web para procesarlos de forma independiente?</p>
<p class="rteindent1">
<br />
¿JournalArticle o JournalArticleDisplay?</p>
<p>
Como se ha visto en el ejemplo anterior, la información asociada a un contenido web puede recuperarse a través del objeto de tipo JournalArticleDisplay, pero cuando necesito manejar parte de la información almacenada en el contenido, ¿qué puedo hacer? A continuación, se muestra una solución a este problema:</p>
<p>
<code>/*<br />
* En este ejemplo se supone que el contenido<br />
* esta asociado a una <strong>plantilla</strong> o <strong>estructura</strong> con un<br />
* campo llamado title.<br />
*/</code></p>
<p>
<code>String contenidoArticulo = journalArticle.<br />
getContentByLocale(themeDisplay.getLanguageId());<br />
Document documento = null;<br />
String title=null;<br />
String summary = null;<br />
try{<br />
documento = SAXReaderUtil.read(<br />
new StringReader(contenidoArticulo));</code></p>
<p>
<code>Node node1 = documento.selectSingleNode(<br />
<strong>"/root/dynamic-element[@name='title']/dynamic- content");</strong></code></p>
<p>
<code>if (node1!=null && node1.getText().length() > 0) {<br />
title = node1.getText();<br />
}<br />
...</code></p>
<h2>
¿Para qué trabajar con Assets?</h2>
<p>
Cuando trabajamos con <strong>contenidos web</strong>, ¿necesitamos incorporar el concepto <strong>Asset</strong> en algún momento? ¿Por qué existe esta entidad? Debe haber alguna razón. La respuesta es clara: <strong>categorías</strong> y <strong>etiquetas</strong>.<br />
Es <strong>IMPORTANTE</strong> tener claro que la gestión de assets nos permite relacionar cualquier tipo de contenido con <strong>categorías</strong> y <strong>etiquetas</strong>, de cualquier otra forma sería imposible.</p>
<h2>
Gestión de assets</h2>
<p>
Para manejar assets se trabaja con las clases utility <strong>AssetEntryLocalServiceUtil</strong> y <strong>AssetEntryServiceUtil</strong>. Nos proporcionan los métodos habituales para manejar este tipo de entidades: add, create, delete, update, get, ...<br />
Los procesos de búsqueda pueden llevarse a cabo de forma específica mediante el método <strong>getEntries</strong>:</p>
<p>
<code>public static List<AssetEntry>getEntries(AssetEntryQuery entryQuery)<br />
throws SystemException</code></p>
<p>
Por ejemplo:</p>
<p>
<code>int total = AssetEntryLocalServiceUtil.getAssetEntriesCount();<br />
List <AssetEntry> contenidos = AssetEntryLocalServiceUtil.getAssetEntries(0,total); </code></p>
<p>
<code>for(int i=0;i<contenidos.size();i++){<br />
AssetEntry c = contenidos.get(i);<br />
}</code></p>
<h2>
Consultas sobre Assets</h2>
<p>
El método <strong>getEntries</strong> asociado al servicio <strong>AssetEntryLocalServiceUtil</strong> es de vital importancia a la hora de llevar a cabo consultas:</p>
<p>
<code>public static List<AssetEntry> getEntries(AssetEntryQuery entryQuery)<br />
throws SystemException</code></p>
<p>
Recibe como parámetro una consulta y devuelve como valor de retorno una lista de contenido que cumple dicha consulta. Ahora bien, ¿cómo se construye ésta?</p>
<p>
La clase <strong>AssetEntryQuery</strong> nos permite realizar consultas avanzadas sobre los diferentes assets almacenados en la instalación. Los métodos <strong>set</strong> nos permiten definir los <strong>criterios de búsqueda</strong>, por ejemplo:</p>
<ul>
<li>
public void <strong>setGroupIds</strong>(long[] groupIds): comunidades donde se han creado los contenidos.</li>
<li>
public void <strong>setClassName</strong>(String className): tipo de contenido.</li>
<li>
public void <strong>setAllCategoryIds</strong>(long[] allCategoryIds): contenidos con las siguientes categorías.</li>
<li>
public void <strong>setAllTagIds</strong>(long[] allTagIds): contenidos con todas las etiquetas.</li>
<li>
...</li>
</ul>
<p>
Para ilustrar esta funcionalidad, se muestra a continuación el siguiente trozo de código:</p>
<p>
.<code>..<br />
// Paso 1: Se construye la consulta gracias a la clase // AssetEntryQuery.<br />
AssetEntryQuery <strong>consulta</strong> = new AssetEntryQuery();</code></p>
<p>
<code>// Paso 2: Se inicializa la consulta.<br />
// Paso 2.1: Comunidades donde se quiere realizar la // busqueda.</code></p>
<p>
<code>// Nota: idsComunidades es un array de tipo long<br />
// que ha sido creado previamente con los identificadores<br />
// de cada comunidad<br />
consulta.setGroupIds(idsComunidades);</code></p>
<p>
<code>// Paso 2.2: Rango de documentos.<br />
consulta.setStart(0);<br />
consulta.setEnd(numNoticias);</code></p>
<p>
<code>// Paso 2.3: Tipo de contenido que se quiere modificar: // Contenido web.</code></p>
<p>
<code>// Se obtiene el <strong>identificador unico</strong> del className<br />
// asociado al tipo contenido web.<br />
ClassName nombre = ClassNameLocalServiceUtil.getClassName(<br />
"com.liferay.portlet.journal.model.JournalArticle");<br />
long classNameIdJournal = nombre.getClassNameId();</code></p>
<p>
<code>long[] tipos = {classNameIdJournal};</code></p>
<p>
<code>// El metodo setClassNameIds nos permite indicar los<br />
// tipos de contenidos que quiere manejar.<br />
consulta.setClassNameIds(tipos);</code></p>
<p>
<code>// Paso 2.4: <strong>Categorias asocidas</strong> a los contenidos web // que se quieren recuperar<br />
// Nota: categoryIds es un array de tipo long creado<br />
// previamente donde se almacenan los ids de las<br />
// categorias a partir de las cuales se realiza<br />
// la busqueda.<br />
consulta.setAllCategoryIds(categoryIds);</code></p>
<p>
<code>// Paso 3: Una vez creada la query, gracias al metodo<br />
// getEntries se ejecuta la consulta de busqueda.<br />
List<AssetEntry> resultados =<br />
AssetEntryLocalServiceUtil.getEntries(consulta);...<br />
// Paso 1: Se construye la consulta gracias a la clase // AssetEntryQuery.<br />
AssetEntryQuery <strong>consulta</strong> = new AssetEntryQuery();</code></p>
<p>
<code>// Paso 2: Se inicializa la consulta.<br />
// Paso 2.1: Comunidades donde se quiere realizar la // busqueda.</code></p>
<p>
<code>// Nota: idsComunidades es un array de tipo long<br />
// que ha sido creado previamente con los identificadores<br />
// de cada comunidad<br />
consulta.setGroupIds(idsComunidades);</code></p>
<p>
<code>// Paso 2.2: Rango de documentos.<br />
consulta.setStart(0);<br />
consulta.setEnd(numNoticias);</code></p>
<p>
<code>// Paso 2.3: Tipo de contenido que se quiere modificar: // Contenido web.</code></p>
<p>
<code>// Se obtiene el <strong>identificador unico</strong> del className<br />
// asociado al tipo contenido web.<br />
ClassName nombre = ClassNameLocalServiceUtil.getClassName(<br />
"com.liferay.portlet.journal.model.JournalArticle");<br />
long classNameIdJournal = nombre.getClassNameId();</code></p>
<p>
<code>long[] tipos = {classNameIdJournal};</code></p>
<p>
<code>// El metodo setClassNameIds nos permite indicar los<br />
// tipos de contenidos que quiere manejar.<br />
consulta.setClassNameIds(tipos);</code></p>
<p>
<code>// Paso 2.4: <strong>Categorias asocidas</strong> a los contenidos web // que se quieren recuperar<br />
// Nota: categoryIds es un array de tipo long creado<br />
// previamente donde se almacenan los ids de las<br />
// categorias a partir de las cuales se realiza<br />
// la busqueda.<br />
consulta.setAllCategoryIds(categoryIds);</code></p>
<p>
<code>// Paso 3: Una vez creada la query, gracias al metodo<br />
// getEntries se ejecuta la consulta de busqueda.<br />
List<AssetEntry> resultados =<br />
AssetEntryLocalServiceUtil.getEntries(consulta);</code></p>
<h1>
AssetEntry vs JournalArticle</h1>
<p>
Una vez que se recuperan los assets asociados a una consulta ...<br />
¿Cómo recupero los contenidos web asociados?</p>
<p>
La relacion entre <strong>JournalArticle</strong> y <strong>AssetEntry</strong> se define mediante una entidad llamada <strong>JournalArticleResource</strong>:</p>
<p>
La entidad <strong>AssetEntry</strong> tiene un campo llamado classPK. Este campo permite relacionar ambas entidades mediante el método <strong>getArticleResource</strong><strong>()</strong>:</p>
<p>
<code>...<br />
AssetEntry e = ...;<br />
JournalArticleResource recurso = JournalArticleResourceLocalServiceUtil.<br />
getArticleResource(e.getClassPK()); JournalArticle articulo =<br />
JournalArticleLocalServiceUtil.getArticle(<br />
e.getGroupId(), recurso.getArticleId());</code></p>
<h1>
Conclusión</h1>
<p>
Si el programador es capaz de manejar los conceptos tratados en este artículo, no tendrá ningún problema a la hora de <strong>extender los portlets del core Liferay</strong> que se encargan de manejar contenidos, por ejemplo:</p>
<ul>
<li>
Asset Publisher.</li>
<li>
Display Content.</li>
<li>
...</li>
</ul>
<p>
Lógicamente, también podrá crear nuevos portlets que trabajen con cualquier tipo de contenido.</p>Jesus Salinas2013-06-13T14:23:28ZUn ejemplo práctico con Web-Harvest de extracción automática de datos de la WWW usando técnicas de WebScrapingJesus Salinashttp://www.tekuento.net/inicio/-/blogs/un-ejemplo-practico-con-web-harvest-de-extraccion-automatica-de-datos-de-la-www-usando-tecnicas-de-webscraping2013-07-02T09:50:17Z2013-07-02T09:20:03Z<h2>
Introducción : Web-Harvest for dummies :-)</h2>
<p>
Web-Harvest es un motor de extracción automática de texto de la WWW, su nombre puede ser traducido literalmente como “cosechador de la Web”. Nos proporciona una potente infraestructura para combinar y utilizar de manera sencilla métodos de extracción ya existentes como pueden ser las expresiones XPath.</p>
<p>
Web-Harvest ofrece un conjunto de procesadores para el manejo de datos y para el control del flujo del programa. Cada procesador puede ser considerado como una función con cero o más parámetros de entrada y un resultado de la ejecución. Los procesadores pueden combinarse sobre una tubería (pipeline) que define la cadena de ejecución. Para facilitar la manipulación y reutilización de los datos, Web-Harvest proporciona un contexto donde se almacenan variables.</p>
<p>
A continuación se muestra un diagrama que modela una tubería de procesadores (Processor1, ...., Processor3) que trabajan sobre los datos de la WWW y sobre el sistema de ficheros (por ejemplo, para escribir los resultados) y sobre el contexto de variables (por ejemplo, para manipular un dato extraído de la WWW antes de registrarlo en el resultado).</p>
<p>
<img alt="Diagrama de la web oficial del proyecto Web-Harvest" height="201" src="http://www.tekuento.net/userfiles/diagram1.gif" width="479" /></p>
<p class="rtecenter">
<em>Diagrama de la web oficial del proyecto </em><a href="http://web-harvest.sourceforge.net/"><em>Web-Harvest</em></a></p>
<p class="rteleft">
<strong>Puedes encontrar Web-Harvest en la web oficial <a href="http://web-harvest.sourceforge.net" title="http://web-harvest.sourceforge.net">http://web-harvest.sourceforge.net</a>. Nosotros vamos a utilizar en este ejemplo la versión 2.0 beta 1. No es necesario que la descargues pues la hemos añadido en el .zip de ejemplo.</strong></p>
<h2>
Lenguaje de configuración</h2>
<p>
Cada proceso de extracción está definido en uno o más ficheros de configuración. Los ficheros de configuración siguen un vocabulario XML propio de Web-Harvest que deberemos aprender aunque su complejidad es relativamente baja e intuitiva.<br />
Cada procesador tiene un elemento XML o una estructura de elementos XML específica que lo describen.</p>
<h2>
Ejemplo de extracción de datos de la Web con Web-Harvest y Eclipse</h2>
<p>
Vamos a hacer un pequeño ejemplo práctico. Nuestro objetivo será el siguiente:</p>
<p>
<strong><em>Extraer de manera automatizada desde un programa Java de consola los títulos de las secciones de la web ematiz.com.</em></strong></p>
<p>
En la imagen se ilustra nuestro objetivo. Puedes ver una captura de pantalla de la web ematiz.com y en ella se han resaltado en amarillo los títulos que deseamos extraer (“¿Qué ofrecemos?”, “¿Por qué somos diferentes?”, “¿Para quién?”, “¿Por qué trabajar con nosotros?”).</p>
<p>
<img alt="Objetivo" src="http://www.tekuento.net/userfiles/imagenPostWebHarvest.jpg" style="width: 506px; height: 444px;" /></p>
<p>
Para conseguirlo vamos a usar Web-Harvest y un proyecto Java Eclipse.</p>
<p>
El primer paso consiste en diseñar nuestro proceso de extracción. Para ello necesitamos estudiar la fuente original de datos y ver en qué formato se encuentran. Si vitamos la web <a href="http://www.ematiz.com" title="http://www.ematiz.com">http://www.ematiz.com</a> y vemos su código fuente, encontraremos que los títulos de las secciones están especificados como encabezados de primer nivel de HTML (esto es, el elemento <h1>). También podemos observar que todo el texto central está dentro de una capa o contenedor (elemento <div>) cuyo identificador único es “portada”.</p>
<p>
A continuación os muestro un extracto del código HTML que estoy describiendo...</p>
<pre>
<div class="post" id="portada">
<h1>¿Qué ofrecemos?</h1>
<p><strong style="color: black;">Servicios avanzados de Ingeniería de Software</strong>.
<strong style="font-size:1.1em">¿Y eso qué es?</strong> Resolución de problemas de proyectos de Software,
Formación a equipos de ingenieros, liderazgo de proyectos y preparación y ejecución de proyectos de I+D+i.
Ofrecemos un gran capital humano y un importante <em>know-how</em>. </p>
<h1>¿Por qué somos diferentes?</h1>
<p> <strong style="color: black; text-decoration:none">Somos auténticos expertos y
apasionados del Software</strong>. <br/>
Trabajamos muy motivados y con un compromiso claro con los resultados.<br />
Te apoyamos cuando los demás se retiran. Cuando se presentan los grandes problemas en los proyectos software,
ahí estamos nosotros para ayudar a solucionarlos. </p>
<h1>¿Para quién?</h1>
<p> Para empresas u organismos que desarrollan Software o para empresas que quieran innovar en tecnología. <br/>
Nuestros clientes naturales son las grandes consultoras de tecnología, utilities y las administraciones públicas. </p>
<h1>¿Por qué trabajar con nosotros?</h1>
<p>Disfruta de verdaderos expertos en Java EE y en la Web y perfiles difíciles de
conseguir en el mercado sin tener que aumentar tu estructura de costes fijos, sin complejos
procesos de selección y sin tener que preocuparte por su formación posterior ni por
mantenerlo motivado en la plantilla. <strong style="font-size:1.2em">Ten al experto que necesitas
cuando y donde quieras</strong>. Y si lo requieres, trabajamos directamente con tus clientes como
<strong style="color: black">marca blanca</strong>. </p>
</div>
</pre>
<p>
Por tanto el proceso de extracción pormenorizado podríamos describirlo tal como sigue....</p>
<ol>
<li>
Solicitar la web <a href="http://www.ematiz.com" title="http://www.ematiz.com">http://www.ematiz.com</a>.</li>
<li>
Obtener su código fuente de una manera que sea simple de procesar.</li>
<li>
Localizar la capa portada.</li>
<li>
Seleccionar los elementos h1 que están anidados dentro de esta capa y extraer su texto.</li>
</ol>
<p>
</p>
<h2>
Diseñando el proceso de extracción con un fichero XML de Web-Harvest</h2>
<p>
Vamos a intentar traducir el diseño a una implementación de Web-Harvest. El fichero de configuración (procesoExtraccion.xml) es el siguiente:</p>
<pre>
<config charset="UTF-8">
<var-def name="titulosCuerpoPortada">
<xpath expression="//div[@id='portada']//h1/text()">
<html-to-xml>
<http url="http://www.ematiz.com/"/>
</html-to-xml>
</xpath>
</var-def>
</config>
</pre>
<p>
</p>
<p>
Este fichero sigue el lenguaje de configuración propio de Web-Harvest al que antes hacíamos referencia. Define el proceso de extracción que habíamos descrito, vamos a estudiarlo en detalle.</p>
<ul>
<li>
La línea 5. contiene el elemento http que sirve para solicitar una URL. Web-Harvest enviará una petición HTTP y obtendrá como resultado la respuesta HTTP (la página HTML).</li>
<li>
Las líneas 4 y 6 definen el elemento html-to-xml. Nótese que el resultado de hacer la petición HTTP está anidado dentro de este nuevo elemento. ¿Para qué sirve esto? Pues fácil, le estamos solicitando a Web-Harvest que pase el código HTML a un formato XML estricto, esto es, si el programador de la web había cometido incorrecciones (como no incorporar la etiqueta de cierre de un elemento HTML) serán corregidas y el documento se interpretará como un documento XML válido.</li>
<li>
En la línea 3 se define el elemento xpath. Este elemento nos sirve para realizar búsquedas de elementos dentro de un documento XML. En nuestro caso lo que queríamos era “Localizar la capa portada y seleccionar los elementos h1 que están anidados dentro de esta capa y extraer su texto.”. Jústamente esto es lo que le estamos pidiendo a Web-Harvest que haga a través de la expresión XPath “//div[@id='portada']//h1/text()” . Esta expresión puede traducirse como “selecciona todas las capas que estén en cualquier lugar del documento XML, quédate con aquella que tenga el identificador ‘portada’, obtén todos los hijos de cualquier nivel (por eso aparece la doble barra) que sean encabezados de primer nivel y por último extrae el texto. Si no sabes XPath puedes ver muchos ejemplos interesantes en XPath Examples (<a href="http://www.w3schools.com/XPath/xpath_examples.asp" title="http://www.w3schools.com/XPath/xpath_examples.asp">http://www.w3schools.com/XPath/xpath_examples.asp</a>) y puedes aprender a construir estas expresiones (que son estándares de la W3C) en W3CShools (<a href="http://www.w3schools.com/XPath/" title="http://www.w3schools.com/XPath/">http://www.w3schools.com/XPath/</a>).</li>
<li>
La línea 2 define una variable. El nombre de la variable es “titulosCuerpoPortada” y su valor será el resultado del elemento anidado, es decir, los títulos.</li>
<li>
La línea 1 define el elemento raíz ‘config’. Todos los ficheros de definición de procesos de extracción de Web-Harvest tendrán este elemento como raíz.</li>
</ul>
<h2>
Creando un programa principal que invoque el proceso de extracción en Web-Harvest</h2>
<p>
Ya tenemos diseñado nuestro proceso de extracción en el documento XML. Ahora necesitamos crear un pequeño programa Java. Podemos hacerlo con cualquier IDE, nosotros hemos utilizado Eclipse Helios y hemos creado un nuevo “Java Project”.<br />
Posteriormente creamos una clave Java y le añadimos un método main en el que vamos a crear un objeto que lea la configuración del proceso de extracción. Este objeto será de la clase org.webharvest.definition.ScraperConfiguration (<a href="http://web-harvest.sourceforge.net/doc/org/webharvest/definition/ScraperConfiguration.html" title="http://web-harvest.sourceforge.net/doc/org/webharvest/definition/ScraperConfiguration.html">http://web-harvest.sourceforge.net/doc/org/webharvest/definition/Scraper...</a>) . Una vez cargada la configuración de extracción, procederemos a crear un objeto extractor. Este objeto será de la clase org.webharvest.runtime.Scraper.</p>
<pre>
// 1. Cargamos la configuracion
ScraperConfiguration config = new ScraperConfiguration(
"procesoExtraccion.xml");
// 2. Creamos el objeto extractor
Scraper scraper = new Scraper(config, "/webscraping");
</pre>
<p>
</p>
<p>
La clase Scraper posee un metodo execute que pone en marcha el proceso de extracción.</p>
<pre>
// 3. Ponemos en marcha el proceso de extracción
scraper.execute();
</pre>
<p>
Una vez ejecutada esta sentencia, tendremos disponibles los resultados...</p>
<pre>
// 4. Obtenemos la variable titulosCuerpoPortada del contexto de variables
// de Web-Harvest. En dicha variable deberiamos encontrar los titulos de la web
// que hemos procesado. Mira en el procesoExtraccion.xml si no lo entiendes
// y veras que en dicha definicion solicitamos a Web-Harvest que cree esta
// variable
Variable titulosCuerpoPortada = (Variable) scraper.getContext().get("titulosCuerpoPortada");
</pre>
<p>
Por último sólo nos resta imprimir los resultados</p>
<pre>
// 6. Imprimimos en consola
System.out.println("=== Encabezados h1 de la web ematiz.com ====");
System.out.println(titulosCuerpoPortada);
</pre>
<p>
</p>
<p>
El resultado esperado es ...</p>
<pre>
=== Encabezados h1 de la web ematiz.com ====
¿Qué ofrecemos?
¿Por qué somos diferentes?
¿Para quién?
¿Por qué trabajar con nosotros?
</pre>
<p>
Como habéis visto en este sencillo ejemplo, Web-Harvest nos permite obtener información de la WWW de manera automática. Esto abre grandes posibilidades a la reestructuración de datos que han sido publicados en la WWW como datos no estructurados con el formato HTML. ¡Podemos usar la WWW como una gran base de datos!. Por ejemplo, podremos volver a estructurar en entidades los datos económicos que un Ayuntamiento haya publicado en una tabla HTML para después poder hacer búsquedas, filtrados, estadísticas, etc. Nosotros ya lo estamos utilizando en proyectos de I+D+i. ¡Esperamos vuestros comentarios!</p>
<h2>
Descarga del ejemplo (Download)</h2>
<p>
Puedes descargar el ejemplo completo en un proyecto de Eclipse (lo hemos creado con Eclipse Helios) en la siguiente URL : <a href="http://www.tekuento.net/downloads/EjemploWebHarvest.zip">http://www.tekuento.net/downloads/EjemploWebHarvest.zip</a></p>
<p>
El proyecto incluye las librerías de Web-Harvest aunque deberás reconfigurar las rutas en el proyecto de Eclipse. Para más información, lee el README.txt.</p>Jesus Salinas2013-07-02T09:20:03Z¿Problemas con el FTP de Alfresco y los firewalls de tu red? ¡Utiliza el FTP Pasivo!Jesus Salinashttp://www.tekuento.net/inicio/-/blogs/¿problemas-con-el-ftp-de-alfresco-y-los-firewalls-de-tu-red-¡utiliza-el-ftp-pasivo-2013-07-02T09:55:20Z2013-07-02T09:30:08Z<p>
En este pequeño post intentaré exponeros rápidamente una solución a la mayoría de los problemas que nos encontramos a la hora de configurar el servicio de FTP en Alfresco. Por defecto, Alfresco utiliza el modo FTP Activo, lo que conlleva algunos problemas con clientes que tienen el firewall activado.</p>
<h2>
Active FTP VS Passive FTP</h2>
<p>
El protocolo FTP tiene dos métodos : activo y pasivo. Alfresco a día de hoy utiliza por defecto el modo activo, tanto en la versión Community como la Enterprise. Este modo funciona correctamente en redes poco protegidas. Sin embargo cuando existen firewalls en la red, el modo FTP activo es muy conflictivo. La razón fundamental es que cuando utilizamos este modo el servidor FTP trata de conectarse a los clientes (¡sí! has leído bien, es el servidor el que hace una conexión contra el cliente) a través de un puerto no privilegiado. Muchos firewalls detectan esta actividad y la bloquean. Los detalles de la interacción y cliente en los modos de FTP están extraordinariamente bien explicadas en <a href="http://slacksite.com/other/ftp.html">Active FTP vs. Passive FTP, a Definitive Explanation</a> por lo que os invito a que leáis el artículo si queréis entender los detalles.</p>
<h2>
Configurando el modo FTP Pasivo en Alfresco</h2>
<p>
<em>[Esta configuración ha sido testeada en Alfresco Enterprise/Community 3.4.2]<br />
</em><br />
Para activar el modo pasivo basta con añadir las siguientes claves de configuración en el alfresco-global.properties (en una instalación sobre tomcat lo encontraréis sobre /shared/classes).</p>
<ul>
<li>
ftp.port: Indicamos el puerto en el que va a escuchar nuestro servidor FTP. Si estamos ejecutando Alfresco con un usuario no privilegiado (que es lo recomendado), no podremos configurar aquí puertos privilegiados (<1023). Normalmente yo utilizo el 8021.</li>
<li>
ftp.enabled: Sirve para habilitar o deshabilitar el servicio. Estará a true</li>
<li>
ftp.dataPortFrom: Inicio del rango de puertos de datos que vamos a utilizar para transferir la información con los clientes. Podemos utilizar cualquier número de puerto, respetando el mismo comentario que en ftp.port con respecto a los puertos privilegiados.</li>
<li>
ftp.dataPortTo: Final del rango de puertos de datos que vamos a utilizar para transferir la información con los clientes. Debe ser mayor que el número especificado en ftp.dataPortFrom. Podemos utilizar cualquier número de puerto, respetando el mismo comentario que en ftp.port con respecto a los puertos privilegiados.</li>
</ul>
<h2>
Ejemplo de configuración</h2>
<pre>
ftp.port=8021
ftp.enabled=true
ftp.dataPortFrom=62000
ftp.dataPortTo=62010
</pre>
<p>
<em>NOTA: En esta configuración hemos habilitado 10 puertos para el intercambio de datos con clientes. Por tanto, estamos limitando a 10 el número de conexiones simultáneas que tendrá nuestro servicio de FTP.</em></p>
<h2>
Abrir los puertos en el firewall del servidor</h2>
<p>
Esta configuración funciona con clientes que están sometidos a controles estrictos. Sin embargo, es necesario que abramos los puertos especificados en ftp.dataPortFrom y ftp.dataFromTo en el firewall del servidor. Si tenemos un sistema Linux con iptables, deberíamos añadir las reglas que permitieran todas las conexiones TCP al puerto ftp.port y al rango [ftp.dataPortFrom, ftp.dataPortTo] . Con los valores de mi ejemplo, serían las siguientes :</p>
<pre>
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:8021
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62000
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62001
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62002
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62003
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62004
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62005
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62006
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62007
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62008
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62009
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:62010
</pre>
<p>
Por último tan sólo nos queda arrancar de nuevo Alfresco (en versiones Community) o reiniciar el subsistema de FileServer a través de la consola JMX de Alfresco Enterprise y listo. Podéis probar a hacer una conexión a vuestro servidor desde un cliente con el firewall activado hasta el puerto especificado en ftp.port del servidor. Esto debería resultar en una conexión exitosa. Si hacéis un netstat en el servidor, veréis que el cliente se habrá conectado a uno de los puertos de datos que estará en el rango [ftp.dataPortFrom, ftp.dataPortTo].</p>
<h2>
FTPS en Alfresco</h2>
<p>
Alfresco soporta SSL para el servicio de FTP. Para ello bastaría configurar las claves ftp.keyStore, ftp.trustStore,<span style="font-weight: bold;"> </span>ftp.passphrase y ftp.requireSecureSession.</p>
<p>
Toda la documentación oficial sobre la <a href="http://docs.alfresco.com/3.4/index.jsp?topic=%2Fcom.alfresco.Enterprise_3_4_0.doc%2Fconcepts%2Ffileserv-ftp-props.html">configuración del servicio FTP sobre la última versión la puedes encontrar en este enlace</a>.</p>
<p>
¡Espero comentarios!</p>Jesus Salinas2013-07-02T09:30:08ZAlta disponibilidad y tolerancia a fallos en Alfresco Enterprise 3.4 con Oracle RAC 10gJesus Salinashttp://www.tekuento.net/inicio/-/blogs/alta-disponibilidad-y-tolerancia-a-fallos-en-alfresco-enterprise-3-4-con-oracle-rac-10g2013-07-02T09:57:02Z2013-07-02T09:31:43Z<p>
Uno de los requisitos más importantes en una configuración de producción de Alfresco es que la información esté alojada en un sistema con alta disponibilidad y tolerancia a fallos. En este post presentaremos una configuración sobre Oracle RAC 10g para garantizar que Alfresco siempre tendrá disponible una fuente de datos.</p>
<p>
[Los impacientes pueden ir directamente a la sección <a href="http://www.tekuento.net/?q=node/46#configuracion">Configuración de Oracle RAC en Alfresco Enterprise 3.4</a>]</p>
<p>
- Para poder montar este entorno es importante que conozcas los <strong>prerequisitos</strong>:<br />
+ Una instancia de Alfresco Enterprise 3.4 funcionando o limpia (primer arranque).<br />
+ Oracle RAC 10g funcionando con un servicio configurado y dos host. En el stack soportado de <a href="http://www.alfresco.com/services/subscription/supported-platforms/">Alfresco Enterprise 3.4</a> se recomienda la versión Oracle 10g v10.2.0.4, yo también te la recomiendo ;-).</p>
<h2>
Primero las definiciones : Alta disponibilidad y tolerancia a fallos</h2>
<p>
Cuando hablamos de alta disponibilidad nos estamos refiriendo al grado en el que el sistema está accesible para los usuarios. Coloquialmente, un sistema en alta disponibilidad es el que "no se cae nunca". La disponibilidad de un sistema puede ser cuantificada tomando como base la disponibilidad total conocida como 24x7.</p>
<p>
La tolerancia a fallos se refiere a la capacidad de un sistema para recuperarse de errores hardware, desastres inesperados y otras condiciones anormales.</p>
<h2>
Alta disponibilidad y tolerancia a fallos en Alfresco Enterprise 3.4</h2>
<p>
Alfresco tiene capacidad para trabajar en Cluster, tanto la versión Community (la gratuita) como la Enterprise (de pago). Alfresco se despliegua normalmente en distintos niveles hardware (varias máquinas físicas o virtuales), normalmente, contamos al menos con un nivel de aplicación (las máquinas donde están desplegadas las instancias de Alfresco) y un nivel de datos o integración donde se encuentran las máquinas con las instancias de nuestro gestor de base de datos (Oracle, MySQL, PostgreSQL...) y los dispositivos de almacenamiento (discos duros, NAS, SAN ...).</p>
<p>
Para conseguir un despliegue completo en alta disponibilidad y tolerancia a fallos es necesario que cada uno de los niveles esté desplegado de esa forma. Sería necesario montar una replicación de contenido sobre varios dispositivos de almacenamiento, configurar Alfresco en Cluster y disponer de una fuente de datos con alta disponibilidad y tolerancia a fallos. En este post sólo nos vamos a centrar en lo último, en concreto, en la configuración de una fuente de datos óptima para producción sobre Oracle.</p>
<p>
En el siguiente diagrama se ilustra el despliegue que vamos a intentar configurar.</p>
<p>
<img alt="Diagrama de despliegue Alfresco Enterprise con Oracle RAC" height="672" src="http://www.tekuento.net/userfiles/diagrama_de_despliegue.png" width="505" /></p>
<p>
</p>
<h2>
Alta disponibilidad en el SGBD con Alfresco</h2>
<p>
Alfresco guarda la información en dos espacios de almacenamiento de distinta naturaleza: el gestor de base de datos y el espacio de almacenamiento o filesystem. Como sabes, los metadatos del nodo se guardan en la base de datos mientras que el contenido se guarda directamente en el espacio de almacenamiento (un disco duro, por ejemplo), al igual que los índices de búsquedas. Alfresco utiliza internamente una fuente de datos (DataSource de Java) para almacenar la información que acompaña al contenido. Esta fuente de datos puede estar mapeada contra cualquiera de los gestores de base de datos más frecuentes en el mercado: Oracle, MySQL, PostgreSQL, ... Desde la versión 3, Oracle sólo está soportado en la versión Enterprise.</p>
<p>
La alta disponibilidad y la tolerancia a fallos a nivel de SGBD no es manejada por Alfresco sino por el driver JDBC de Oracle, el conocido como ojdbc14.jar . Lo puedes descargar de la web oficial de Oracle, pero ¡recuerda elegir el adecuado para tu versión de Oracle! (<a href="http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html" title="http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html">http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html</a>). Es decir, Alfresco tan sólo le solicita una conexión al driver JDBC y este la obtiene del RAC de Oracle. El Driver JDBC tiene la capacidad de recuperarse ante fallos, es decir, si uno de los host de nuestro Oracle RAC se cae, las conexiones que tenía este host pasarán a ser gestionadas por otro host que esté disponible. Por tanto, para Alfresco, la alta disponibilidad y tolerancia a fallos del Datasource es transparente.</p>
<h2>
<a name="configuracion"></a>Configuración de Oracle RAC en Alfresco Enterprise 3.4</h2>
<p>
1.) Descarga el driver JDBC de Oracle (ojdbc14.jar) de la web oficial de Oracle y copialo a la carpeta de librerías compartidas de tu servidor. En las instalaciones sobre Tomcat (v.5 y v.6) la carpeta suele tener esta ruta: /shared/lib. Recuerda que esta ruta es configurable en Tomcat, puedes verla en el fichero /conf/catalina.properties en la clave de configuración shared.loader, generalmente para Alfresco debe tener el valor shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar</p>
<p>
2.) Editar el fichero alfresco-global.properties para configurar nuestra fuente de datos. ¡Esta configuración sólo es válida para Alfresco Enterprise, la versión Community puede no funcionar sobre Oracle!.</p>
<p>
Debemos establecer las siguientes claves:</p>
<ul>
<li>
db.driver: Nombre de la clase que implementa el driver Oracle. No modificar. Siempre es oracle.jdbc.OracleDriver.</li>
<li>
db.url: URL de la fuente de datos. Aquí es donde entra en juego Oracle RAC, la alta disponibilidad y la tolerancia a fallos. Para que el driver JDBC nos gestione estas características debemos especificar una URL de Oracle RAC válida. La construcción de esta cadena es sencilla y está documentada en la web oficial de Oracle (por ejemplo, para la versión 10g puedes verlo aquí: <a href="http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm" title="http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm">http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames...</a> . En nuestro ejemplo voy a usar una cadena de conexión que trabaja contra 2 máquinas (192.168.1.10 y 192.168.1.11) donde se han instalado instancias de Oracle y que están escuchando en el puerto 21. Mi servicio se llama ALFRESCO (debes configurarlo en el tnsnames.ora). La URL que pongo está construida para que se gestionen los fallos (en la parte de FAILOVER_MODE) y para que en condiciones normales se haga balanceo de carga entre las dos instancias.</li>
<li>
db.pool.validate.query: Consulta que se va a utilizar para verificar que la conexión está funcionando. Para Oracle no hay que modificar la que os pongo.</li>
</ul>
<h3>
Ejemplo de configuración [alfresco-global.properties] (sólo debes modificar lo que está en rojo)</h3>
<pre>
db.driver=oracle.jdbc.OracleDriver
db.url=jdbc:oracle:thin:@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = <span style="color: rgb(255, 0, 0);">192.168.1.10</span>)(PORT = <span style="color: rgb(255, 0, 0);">1521</span>))(ADDRESS = (PROTOCOL = TCP)(HOST = <span style="color: rgb(255, 0, 0);">192.168.1.11</span>)(PORT
= <span style="color: rgb(255, 0, 0);">1521</span>))(LOAD_BALANCE = yes)(CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME =
<span style="color: rgb(255, 0, 0);">ALFRESCO</span>)(FAILOVER_MODE = (TYPE = SELECT)(METHOD = BASIC)(RETRIES = 20)(DELAY =
1))))
db.pool.validate.query=SELECT 1 FROM DUAL
</pre>
<p>
3.) Sólo si tienes varias instancias distintas de Alfresco trabajando sobre una misma base de datos Oracle (cada instancia persiste los datos en un esquema distinto), deberás además añadir en cada instancia la siguiente clave para forzar a las consultas sobre metadatos a trabajar con el esquema con el que cada instancia de Alfresco está trabajando:</p>
<pre>
hibernate.default_schema=ALFRESCO
</pre>
<p>
4.) Si tu Alfresco ya tenía datos (no es la primera vez que arrancas) asegúrate que todos los host de Oracle que has apuntado desde la cadena JDBC tienen los datos (impórtalos con un script SQL si ya lo tenías en otra instancia de Oracle).</p>
<p>
5.) Comprueba con una herramienta como TOAD que la cadena de conexión es correcta.</p>
<p>
6.) Arranca Alfresco y disfruta o pon un comentario si falla con tu log!</p>
<h2>
Sobre Oracle RAC y las licencias de Alfresco Enterprise</h2>
<p>
Para utilizar Oracle RAC contra Alfresco no es necesario adquirir licencias de clustering de la versión enterprise. Se puede utilizar Oracle en cluster independientemente de la configuración en cluster o no de Alfresco. Con la licencia más barata de Alfresco Enterprise, la conocida como "1CPU" es suficiente. No obstante, recuerda que el licenciamiento de Oracle RAC va aparte y no está incluido en la licencia básica de Oracle Database.</p>
<h2>
TODO</h2>
<p>
Dejo para otro post y para vosotros las pruebas para ver que está funcionando correctamente (cortar comunicación con uno de los host desde el nivel de aplicación para ver si Alfresco se reconecta a otro host, por ejemplo).</p>
<p>
¡Espero vuestras contribuciones!... más cadenas de configuración JDBC, comentarios útiles, etc.</p>Jesus Salinas2013-07-02T09:31:43Z