jueves, 2 de octubre de 2014

WebCenter Portal y WebCenter Content con Framework Folders

Desde la versión 11.1.1.8.3 de WebCenter Portal es posible utilizar la integración WebCenter Portal - WebCenter Content usando Framework Folders en vez de folders_g como componente de organización por carpetas del contenido.

En caso de tener una base de datos Oracle, es obligatorio que Oracle WebCenter Content use OracleTextSearch como motor de búsqueda en vez de DATABASE.FULLTEXT.

En el caso de DATABASE.FULLTEXT ocurrirá una excepción en Portal como la que sigue:

oracle.webcenter.content.integration.spi.ucm.UCMBridge isRecursiveSearch SEVERE: Repository UCM_Conn is not configured for full text search capabilities.

Esta excepción puede despistar puesto que DATABASE.FULLTEXT es full text.

El siguiente código es el responsable del test del indexador configurado.

public boolean isRecursiveSearch()
    throws UCMSearchValidationException
  {
    if (UCMCoreUtils.isFwkFolder())
    {
      SearchConstants.SearchEngine searchEngine = SearchConstants.SearchEngine.getEngine(getSearchEngineName());
      boolean isOracleTextSearch = searchEngine.equals(SearchConstants.SearchEngine.ORACLETEXT);

      if (isOracleTextSearch)
      {
        this.recursiveSearch = true;
      }
      else
      {
        throw new UCMSearchValidationException(logger.format_FTS_NOT_SUPPORTED_ERROR(this.repositoryName));
      }
    }
    return this.recursiveSearch;
  }

Como se puede comprobar solo admite OTS. Además, se recomienda siempre que se tenga una base de datos Oracle que el indexador sea OracleTextSearch.

Referencia:
Documentación Oficial: Integración WCP - WCC

martes, 9 de septiembre de 2014

Multilenguaje en una WebCenter Framework Portal

Hace tiempo publiqué una solución de multidioma basada en un ADF Phase Listener.
Sin embargo, esta solución tuvo ciertos bugs y problemas que me hicieron retirarla del Blog.

Plntilla por defecto con cambio de Idioma
Enlace a la versión en inglés

No me he olvidado de ello y ahora presento una nueva solución y aquí la traigo :).

Esta solución resuelve los siguientes paradigmas del multidioma:
  • Traducción de literales del portal.
  • Traducción de los Navigation Resource del Navigation Model según la Locale actual.
Se basa en:
  • Una solución de multidioma basada en preferencia de usuario (Cookie).
  • Un ADF Phase Listener para la invalidación del Navigation Model.

  Filtro y Cookie de preferencia de usuario

Implementación de un Java Filter que sobreescribirá las Locales permitidas según la Locale actual. Se apoya en un RequestWrapper para la sobrescritura de estas Locales. Por defecto, el lenguage es inglés.

public final class LocaleFilter implements Filter {
    
    /**
     * Filter Configuration
     */
    private FilterConfig _filterConfig = null;

    /**
     * Initializing the filter
     * @param filterConfig
     * @throws ServletException
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        _filterConfig = filterConfig;
    }

    /**
     * Destroy the filter
     */
    public void destroy() {
        _filterConfig = null;
    }

    /**
     * Check the Cookie and overwrite the default language given by the browser
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException,
                                                   ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        String preferredLocale = this.getPreferredLocaleFromCookies(req);
        LocaleRequestWrapper localeReqWrapp = new LocaleRequestWrapper(req,preferredLocale);
        chain.doFilter(localeReqWrapp, response);
    }

    /**
     * Get the preferredLocale from the cookies
     * @param req
     * @return String format locale from the configured cookie
     */
    private String getPreferredLocaleFromCookies(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        boolean found = false;
        int i = 0;
        Cookie cookie = null;
        String prefLang = null;
        if (cookies != null) {
            while (i < cookies.length && !found) {
                cookie = cookies[i];;
                if (cookie.getName().equalsIgnoreCase(CustomLocaleUtils.LANG_COOKIE)) {
                    prefLang = cookie.getValue();
                    found = true;
                }
                ++i;
            }
        }
        return prefLang;
    }
}


public final class LocaleRequestWrapper extends HttpServletRequestWrapper{
    
    /**
     * Locale to apply
     */
    private Locale locale = null;


    /**
     * Initializes de wrapped request setting the language to be used
     * @param req
     * @param lang
     */
    public LocaleRequestWrapper(HttpServletRequest req, String lang) {
        super(req);
        if (StringUtils.isNotEmpty(lang)) {
            // Preferred locale by the user (Cookie)
            locale = LocaleUtils.toLocale(lang);
        } else {
            // Default locale, english hardcoded
            locale = new Locale("en");
        }
    }

    /**
     * Setting the language to be shown instead of the browser Accept-Languages
     * @return Enumeration with just the desired locale
     */
    @Override
    public Enumeration getLocales() {
        Vector locales = new Vector();
        locales.add(locale);
        return locales.elements();
    }


Una clase auxiliar de ayuda para la escritura de Cookies.

public final class CustomLocaleUtils {
    
    /**
     * Name of the cookie storing the user preference
     */
    public static final String LANG_COOKIE = "merchan_lang";
    
    /**
     * Services class. Can not be instantiated
     */
    private CustomLocaleUtils() {
        super();
    }
    
    
    /**
     * Set a new preferred locale
     * @param lang
     */
    public static void setCookieLang(Locale lang) {
        HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
        // Get the context-root because is the same as the cookie-path in weblogic.xml
        ServletContext appContext = (ServletContext)ADFContext.getCurrent().getEnvironment().getContext();
        // Write the cookie in the cookie path /MyApp
        Cookie langCookie = new Cookie(LANG_COOKIE,lang.getLanguage());
        //langCookie.setPath(appContext.getContextPath());
        langCookie.setPath("/");
        response.addCookie(langCookie);
    }
}


Un manejador de la localización para mantener que Locale es la actual e interactuar con la interfaz para modificar la misma.

public final class LocaleHandler {
    /**
     * List of the supported Locales by the WebCenter Portal Application
     */
    private List<SelectItem> supportedLocales;

    /**
     * Holds the current locale
     */
    private Locale currentLocale;
    
    /**
     * Flag to indicate if the language was changed
     */
    private boolean changed;

    /**
     * Default constructor
     */
    public LocaleHandler() {
        super();
        // Initialize the supportedLocales list
        Iterator<Locale> iteratorSupportedLocales = FacesContext.getCurrentInstance().getApplication().getSupportedLocales();
        supportedLocales = new ArrayList();
        SelectItem itemLocale = null;
        while (iteratorSupportedLocales.hasNext()) {
            Locale locale = iteratorSupportedLocales.next();
            itemLocale = new SelectItem(locale, locale.getDisplayName(), locale.getDisplayName());
            supportedLocales.add(itemLocale);
        }
        currentLocale = ADFContext.getCurrent().getLocale();
        changed = false;
    }

    /**
     * Change the language from a given action of an JSF ActionListener
     * @param ae
     */
    public void changeLanguageEvent(ActionEvent ae) {
        CustomLocaleUtils.setCookieLang(currentLocale);
        NavigationModel navModel = SiteStructureContext.getInstance().getCurrentNavigationModel();
        FacesContext fctx = FacesContext.getCurrentInstance();
        ExternalContext ectx = fctx.getExternalContext();
        try {
            NavigationResource navResource = navModel.getCurrentSelection();
            String prettyUrl = ectx.getRequestContextPath() + navResource.getGoLinkPrettyUrl();
            changed = true;
            ectx.redirect(prettyUrl);
         } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * Set changed
     * @param changed
     */
    public void setChanged(boolean changed) {
        this.changed = changed;
    }

    /**
     * Get changed flag
     * @return boolean
     */
    public boolean isChanged() {
        return changed;
    }

    /**
     * Get the supported locales by the WebCenter Application
     * @return List of locales
     */
    public List <SelectItem> getSupportedLocales() {
        return supportedLocales;
    }

    /**
     * Set the current locale in a variable to easier access
     * @param currentLocale
     */
    public void setCurrentLocale(Locale currentLocale) {
        this.currentLocale = currentLocale;
    }

    /**
     * Get the current locale
     * @return
     */
    public Locale getCurrentLocale() {
        return currentLocale;
    }
}


El siguiente fragmento de código hace uso de este Managed Bean para el cambio de idioma (Page Template).



   
      
        
      
      
      



Esto es suficiente para el multidioma?. No. Si un Navigation Model es configurado para que sus elementos de navegación usen un Resource Bundle para obtener los títulos de los nodos hace falta también invalidar la caché del Navigation Model para que estos títulos sean de nuevo obtenidos.

Invalidación de la caché del Navigation Model

Implementación de un ADF Phase Listener que invalida el Navigation Model en la fase PREPARE_MODEL en caso de que el idioma haya sido cambiado.

public class LocalePhaseListener implements PagePhaseListener {
    public LocalePhaseListener() {
        super();
    }

    public void afterPhase(PagePhaseEvent pagePhaseEvent) {
    }

    public void beforePhase(PagePhaseEvent pagePhaseEvent) {
        if (Lifecycle.PREPARE_MODEL_ID == pagePhaseEvent.getPhaseId()) {
            LocaleHandler localeHandler = (LocaleHandler)ADFContext.getCurrent().getSessionScope().get("localeHandler");
            if (localeHandler != null && localeHandler.isChanged()) {
                SiteStructureUtils.invalidateDefaultNavigationModelCache();
                localeHandler.setChanged(false);
            }
        }
    }
}

Ejemplo descargable a través de mi repositorio GitHub.

jueves, 4 de septiembre de 2014

Recursos / Assets de Framework Portal despues de un re-despliegue

Los recursos / assets de WebCenter Framework Portal son referenciados en el archivo generic-site-resources.xml que se encuentran en la ruta de MDS bajo el GUID de portal por defecto.

/oracle/webcenter/siteresources/scopedMD/s8bba98ff_4cbb_40b8_beee_296c916a23ed

Versión en inglés

La administración de WebCenter Framework Portal permite la edición y gestión de los recursos de portal que estan registrados en dicho archivo.

Recursos / Assets de Portal



En el caso de WebCenter Portal, hay múltiples de ellos, uno por cada GUID de Portal creado con Portal Builder (además de el por defecto).

Ejemplo de fragmento del archivo por defecto de generic-site-resources.xml


   
      
         
      
      
         
      
   
   
      
         
      
      
         
      
      
         
      
      
         
      
      
         
      
      
         
      
      
         
      
   
   
   
      
         
            
            
            
            
            
         
      
      
         
            
            
            
            
            
         
      
    ...
   
   
   



Este post se centra en un "error" muy común cuando se desea redesplegar una aplicación de WebCenter Framework Portal.

"He redesplegado una aplicación de WebCenter Framework Portal y se han perdido los recursos que cree en Runtime! Qué ha pasado?."

Cuando se empaqueta una aplicación de WebCenter Framework Portal se genera un archivo .EAR a través de los scripts ANT de OJDeploy de JDeveloper. Esto crea e introduce en el .EAR un archivo llamado AutoGeneratedMar.mar con los recursos / assets de Portal en tiempo de diseño, incluyendo el famoso generic-site-resources.xml local.

Si no se realiza ninguna modificación a este empaquetado entonces durante el re-despliegue de la aplicación de WebCenter Framework Portal se sobreescribiran todos los archivos que contenga el archivo AutoGeneratedMar.mar y entre ellos el generic-site-resources.xml.

Qué ha pasado con los assets creados en Runtime

Básicamente siguen existiendo, pero al no estar referenciados en el generic-site-resources.xml en la Administración de WebCenter Framework Portal no apareceran como recursos (aunque pueden seguir siendo configurados a través de su path de MDS en la pestaña de Configuration).

Soluciones 

Existen dos soluciones para re-desplegar:
  • Mantener la aplicación de WebCenter Framework Portal de JDeveloper actualizada importando los recursos de portal creados en Runtime a tiempo de Diseño. Con ello se mantendrá sincronizado el archivo generic-site-resources.xml
  • Añadir el archivo mds-transfer-config.xml a la carpeta META-INF dentro del archivo AutoGeneratedMar.mar para indicar qué archivos de MDS no deben ser sobreescritos.
Ejemplo del contenido del archivo mds-transfer-config.xml para prevenir cambios en generic-site-resources.xml


  
    
      
     
      
    
  


martes, 2 de septiembre de 2014

ADF11gR1: Task Flow ejecutados en Dialogo/Popup

Hay dos modos de ejecutar un Task Flow en popup.

Versión en inglés
  • Generando la Bounded Task Flow basada en páginas JSPX en lugar de fragmentos JSFF. Con ello se consigue que el Task Flow pueda ser llamado como diálogo en una actividad de otro Task Flow.

    Task Flow invocado como inline-popup

  • Usando la Bounded Task Flow como Region dentro de un af:popup
    
        
          
            
              
            
            
              
              
                
              
            
          
        
        
        
          
          
            
          
        
     
    

Ventajas e Inconvenientes de cada solución

Solución basada en TF-Call JSPX

Ventajas:
  • Se puede utilizar el framework de ADF para llamar en módo diálogo el Task Flow.
  • El Task Flow puede devolver valores a su vuelta usando el Return Values.
Inconvenientes:
  • Mayor dificultad para aplicar estílos específicos al Diálogo/Popup.
  • El Task Flow no puede ser embebido como Region.

Solución basada en Region embebida en un af:popup

Ventajas:
  • Se puede reutilizar como Region en otras Unbounded/Bounded Task Flow.
  • Facilidad para aplicar styleClass específicos para af:popup y af:dialog.
Inconvenientes:
  • No se puede devolver valores en el Return Values. Se debe realizar mediante Eventos Contextuales y un regionNavigationListener.
  • Para refrescar el Task Flow ejecutado dentro del popup. No basta con una política de refresco en Refresh y Refresh Condition. Hace falta controlar la activación del Task Flow cuando éste se abre y se cierra.

El ejemplo

El ejemplo requiere conocimientos básicos de ADF 11gR1 y contiene ambas soluciones. A través de las siguientes páginas de test se puede seguir el ejemplo:
  • testPopupInTFCall.jspx: Ejecuta los Task Flow que demuestra como un TF es invocado en modo diálogo y, a su vez, devuelve valores utilizando su Return Value. Puntos importantes del ejemplo:
    • El Task Flow es invocado como TF-Call en modo dialogo. Por ello el botón de navgación hacia el dialogo debe tener useWindow="true".
       
      


    • Al tener configurado Return Values, se mapean a una variable de Page Flow Scope para poder ser usada por el fragmento.

      Return Values definidas para el Task Flow invocado como diálogo

      Mapeo del valor devuelto por el Task Flow a una variable local del Task Flow
  • testPopupInRegion.jspx: En el se encuentra una Task Flow que es invocada en un af:popup. Configurada la política de activación para que el Task Flow se active y se cierre según . Además, incluye el uso de Eventos Contextuales para obtener los datos de respuesta. Puntos importantes de esta parte:

    • Politica de activación del Task Flow para que se desactive y active según es abierto / cerrado el popup.
      Política de activación del Task Flow
    • Uso de eventos contextuales (disparado programaticamente) en el botón que cierra el diálogo / popup para devolver los datos al Task Flow llamador.

      
        
        
          
          
            
              
            
          
        
        
          
            
          
        
        
          
            
              
                
                  
                
              
            
          
        
      
      

      /**
       * Closer Picker action
       * @param ae Action Component used
       */
      public void performClose(ActionEvent ae) {
       
       // Execute the Contextual Event programmatically before of unloading the popup
       BindingContainer bindingContainer = BindingContext.getCurrent().getCurrentBindingsEntry();
       JUEventBinding eventBinding = (JUEventBinding)bindingContainer.get("returnDialogEventBinding");  
       ActionListener actionListener = (ActionListener)eventBinding.getListener();  
       actionListener.processAction(ae);  
       
       // Check the Input Parameter if the TF has to be considered as executed in popup
       AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
       Map<String,Object> flowScope = adfFacesContext.getPageFlowScope();
       Boolean isPopup = (Boolean)flowScope.get("isPopup");
       if (isPopup != null && isPopup) {
        this.closePopup(ae);
       }
      }
      
      /**
       * Auxiliar method to close the popup
       * @param ae
       */
      private void closePopup(ActionEvent ae) {
       // Looking for the RichPopup parent of picker
       UIComponent component = ae.getComponent();
       while (!(component instanceof RichPopup))
        component = component.getParent();
       if (component != null)
        ((RichPopup)component).cancel();
      }
      

      public final class ContextualEventHandlerDC {
          
          /**
           * Default Constructor
           */
          public ContextualEventHandlerDC() {
              super();
          }
          
          /**
           * Get the list of names returned by the Contextual Event
           * @param names
           */
          public void handleReturnEvent(List<String> names) {
              for (String s: names) {
                  System.out.println(s);
              }
              AdfFacesContext.getCurrentInstance().getPageFlowScope().put("returnedNames", names);
          }
      }
      
Enlace al repositorio GitHub del Ejemplo

Ejemplo desarrollado en JDeveloper 11.1.1.7

viernes, 20 de junio de 2014

Migrando los ejemplos a GitHub

Para evitar pérdidas de código asociado a algunos de los ejemplos he abierto mi propio repositorio de GitHub donde dejaré el anterior y futuro código que postee por el blog.

Repositorio GitHub con los ejemplos

Mientras no esté todo migrado a https://github.com/DanielMerchan los enlaces seguirán bajando los ejemplos de Dropbox.

Un saludo.

af:panelTabbed, Task Flows y WebCenter Portal

Los Task Flow en ADF 11gR1 (framework en el cual se basa WebCenter Portal 11g) se cargan de manera secuencial. Esto significa que hasta que el último Task Flow no finaliza su primera actividad la página no es renderizada.

Existe un truco del ATEAM para simular un Lazy Loading de los Task Flows.
http://www.ateam-oracle.com/improving-adf-page-rendering-time/

Existe un número mágico que dice de no tener más de 8 Task Flows por página.

En numerosas ocasiones, estos Task Flows se encuentran en componentes af:panelTabbed separados por pestañas. El problema reside en que todos los Task Flows (incluyendo las pestañas no visibles) ejecutarán todos los Task Flows que contengan.

Ejecución de los 3 Task Flows aunque los otros dos no se vean
 Cómo ejecutar solo los componentes de la pestaña actual?
  1. Configuración de la activación de los Task Flows
    Andrejus
    explica como configurar la propiedad de Activation de los Task Flow. Con esta propiedad se puede controlar la activación y desactivación de Task Flows cuando se seleccionan las distintas pestañas del panel.
    http://andrejusb.blogspot.com.es/2013/09/conditional-task-flow-activation-in-adf.html

  2. Configurar la propiedad childCretion de af:panelTabbed
    Configurando la propiedad childCreation se puede conseguir que las pestañas no activas no ejecuten los Task Flows o componentes que tienen hasta que son seleccionadas. Los valores que puede tomar childCreation son:

    • immediate: Al cargar la página todas las pestañas se ejecutan.
    • lazy: Solamente se ejecuta la pestaña actual y guarda el estado en el que se quedó el Task Flow. Al volver a la pestaña NO se ejecutara de nuevo el Task Flow desde el principio.
    • lazyUncached:  Solamente se ejecuta la pestaña actual. A diferencia de lazy, cada vezs que se vuelva a una pestaña que ya fue ejecutada el Task Flow se re-ejecutara desde el principio.
     
Con una configuración de af:panelTabbed a lazy o lazyUncached se puede conseguir un aumento de rendimiento considerable en el caso de que algún Task Flow de alguna pestaña sea pesado.

Recordar que por defecto es immediate.

Referencias:
http://docs.oracle.com/cd/E28280_01/apirefs.1111/e12419/tagdoc/af_panelTabbed.html

jueves, 19 de junio de 2014

Task Flows que navegan usando el Navigation Model de WebCenter Portal

Cuando una Bounded Task Flow necesita realizar una navegación de Portal basada en el Navigation Model del mismo necesita incluir una actividad de tipo parentAction para propagar a la aplicación de Portal de que ha ocurrido una navegación de Portal y que debe de actualizar los estados del Navigation Context.

Content Presenter incluye por defecto la actividad wcnav_parentAction

Qué información debe contener esta parentAction?
La parentAction debe contenter la siguiente la siguiente acción de Root:

<parent-action id="wcnav_parentAction">
  <root-outcome>wcnav_outcome</root-outcome>
</parent-action>

Configuación que debe llevar todos los Task Flow que usen internamente el Navigation Model

Qué puede implicar no tener esta acción?.
Popups con ventanas vacías o contextos de navegación que no reflejan el estado actual y el CurrentNavigationModel.

Referencia: Visualizando Navegaciones en WebCenter Portal 11.1.1.8.3

lunes, 16 de junio de 2014

Oracle ACE Associate

Como algunos sabéis, hace poco más de una semana fuí aceptado dentro del programa Oracle ACE, más especificamente dentro del nivel de entrada Oracle ACE Associate.


Agradecer a todos aquellos que no sólo seguís el blog, sino que también en el foro de OTN de WebCenter compartís vuestras dudas y soluciones a toda la comunidad de Oracle WebCenter.

Ser Oracle ACE significa más energíá para seguir compartiendo mis experiencias y soluciones a los problemas del día a día que pueden suponer trabajar con la Oracle Fusion Middleware.
Pronto espero realizar los siguientes cambios al blog:
  • Actualizar entradas con tecnologías actuales como Oracle ADF Mobile, ADF 12c, SOA/BPM e incluso WebCenter Sites.
  • Corregir entradas antiguas las cuales están obsoletas o cree cuando el conocimiento que tenía era menos claro.
  • Seguir tratando de mantener la dualidad blog Spanish - English para mantener a la mayor parte del mundo informada.
  • Y, por supuesto, seguir en el foro de OTN WebCenter Portal / Content / Sites ayudando en todo aquello que pueda.
Un saludo, a seguir programando y sobretodo... Compartiendo!

miércoles, 21 de mayo de 2014

WebCenter Portal con WebCenter Content (Contribución y Presentación) al mismo tiempo

WebCenter Portal puede ser configurado para utilizar varios Repositorios de Contenido al mismo tiempo

Versión en inglés

Varios repositorios de WebCenter Content
 
Content Presenter con múltiples repositorios


Este post cubre un escenario peculiar como el siguiente:

"El contenido debe ser mostrado desde dos repositorios de contenido distintos según el Rol del usuario. Si el usuario es un usuario contribuidor entonces el contenido debe ser consumido desde el repositorio de Contribución. En caso contrario, el contenido será servido desde el repositorio de Presentación".


Escenario propuesto

El escenario ideal es tener totalmente separados Portal-Content de contribución/presentación. Sin embargo, puede que solamente se tenga una instancia de WebCenter Portal.

¿Cómo se puede conseguir este dinamismo entre repositorios basado en Roles de usuario? 

Primero recordar lo siguiente con respecto a Content Presenter:
  • Hay dos maneras de configurarlo:
    • Usando el Configuration Dialog el cual está pensado para configuración en tiempo de ejecución.
      Diálogo de Configuración de Content Presenter
    • En Configuration Properties el cual se suele utilizar para configurar Content Presenter en Diseño
      Configuración de propiedades de Content Presenter
  • Nota: En caso de configurar Content Presenter usando Configuration Properties. Los valores configurados sobrescribiran cualquier cambio realizadon con Configuration Dialog.
    Conflicto de configuración entre Diálogo y Propiedades
La solución consiste en tener un parámetro de página que calcule, según el Rol de usuario, el nombre de conexión del repositorio a utilizar.

Parámetro de página que calcula el nombre de repositorio a utilizar

Finalmente configurar Content Presenter para hacer uso del parámetro de página con el valor calculado

Configuración dinámica del repositorio a utilizar

TIP: El parametro de página puede ser configurado en una Page Style y así toda página creada en Runtime tendrá por defecto el parámetro calculado.

Referencias:

miércoles, 7 de mayo de 2014

Evitar eliminar las policies y credenciales al borrar una aplicación de WebCenter Portal

Una de las preguntas mas frecuentes a la hora de desplegar, re-desplegar y borrar una aplicación de WebCenter Portal es qué pasa con las policies, credenciales, usuarios y grupos.

(English version)

Cuando se genera el archivo .EAR de la aplicación de WebCenter Portal. Este archivo trae, por defecto, las politicas, credenciales y usuarios generados en desarrollo.

Esta configuración se lleva a cabo desde Application Properties -> Deployment -> Security Deployment Options.

Propiedades de seguridad durante el empaquetamiento y despliegue
 ¿Qué significan cuando estan Checked estas propiedades?.
  • En el caso de Application Policies y Credentials hará que éstas creadas durante el despliegue o sobre escritas durante el re-despliegue de la aplicación. Esto significa que se perderán los cambios realizados en el entorno de destino post-deployment.
  • En caso de Usuarios y Grupos estos serán creados en el Identity Store configurado en el entorno de destino.
¿Qué significan cuando estan Unchecked?
  • Ni Credentials, Policies, Usuarios y Grupos serán creados ó sobrescritos en el entorno de destino
¿Dónde se reflejan los cambios realizados sobre las propiedades de seguridad?.
Se modifica el archivo weblogic-application.xml empaquetado en el EAR para reflejar las acciones que debe llevar acabo WebLogic durante el despliegue / re-despliegue de la aplicación en base a la configuración realizada:


  
    
      oracle.xml.jaxp.JXSAXParserFactory
      oracle.xml.jaxp.JXDocumentBuilderFactory
      oracle.xml.jaxp.JXSAXTransformerFactory
    
  
    
        jps.credstore.migration
        OVERWRITE
    
    
        jps.policystore.migration
        OVERWRITE
    
  
    oracle.adf.share.weblogic.listeners.ADFApplicationLifecycleListener
  
  
    oracle.mds.lcm.weblogic.WLLifecycleListener
  
  
    oracle.webcenter.lifecycle.listener.FeatureMetricApplicationListener
  
    
        oracle.security.jps.wls.listeners.JpsApplicationLifecycleListener
    
  
    adf.oracle.domain
  
  
    oracle.jsp.next
  
  
    oracle.webcenter.framework
    11.1.1
  
  
    oracle.webcenter.skin
    11.1.1
  
  
    oracle.sdp.client
  


¿Qué pasa al eliminar la aplicación?.
Por defecto las Credentials, Policies son eliminadas del entorno. 
A veces puede no desearse eliminar la seguridad aplicada a una aplicación al borrarla. Para ello debe añadirse el siguiente parámetro al archivo weblogic-application.xml manualmente:

  
    jps.policystore.removal  
    OFF  
 

Referencias:

lunes, 21 de abril de 2014

Usando Resource Action Handler Tag

Una de las taglibs mas usadas por el Framework de WebCenter Portal es la del Resource Action Handler (<rah:resourceActionBehavior>

RAH Tag

Enlace versión inglés.

Descarga de la aplicación de ejemplo

Este tag permite la creación de enlaces y navegar a los detalles de los recursos/servicios definidos en WebCenter (oracle.webcenter.doclib, oracle.webcenter.page...)
Estos servicios estan definidos en el archivo de configuración service-definition.xml (ya sea dentro de una ADF JAR Lib como la mayoría de servicios OOTB o dentro de la propia aplicación de Portal). Durante la configuración de un nuevo servicio, éste suele enlazarse a un Task Flow como resource-viewer (detalle del recurso) o a una clase URL Rewriter que generara una URL con parámetros hacia la página donde se encuentra el recurso.

Uno de los ejemplos más comunes de uso de esta taglib es el fragmento de resultados del servicio de búsqueda. Cada link de cada elemento de resultado usa rah:resourceActionBehavior para enlazar con el detalle del recurso.


 
 
 

Otro ejemplo de uso de este tag puede encontrarse en los ejemplos de plantillas de Content Presenter que trae la instalación de JDeveloper (articles.jsff)


   
   
   
   
   
   
   
   
   
   


En este caso el tag enlaza con un recurso de tipo oracle.webcenter.content.presenter.
En el archivo service-definition.xml dentro de la libraría de Documentation Library View puede encontrarse que el Resource-Viewer de este servicio es precisamente el Task Flow de Content Presenter.
Para enviar parámetros diferentes a los de por defecto(resourceId, resourceType...) entonces el tag f:attribute debe ser usado conjuntamente con rah:resourceActionBehavior para poder enviar los valores que el Task Flow espera

En el ejemplo adjunto a este post puede encontrarse un sencillo ejemplo de una Framework Portal Application que define un nuevo servicio y utiliza este tag para acceder al resource-viewer asociado al mismo. Los pasos seguidos han sido:
  • Modificar service-definition.xml file definiendo un nuevo servicio denominado oracle.webcenter.merchan.sample y registrando una Custom Bounded Task Flow como resource-viewer


    
    
    Sample resource
    Sample resource for the blog
    
    


  • La Bounded Task Flow usada como Resource Viewer contiene alguno de los parámetros por defecto enviados automáticamente y uno custom para ser mostrados en un fragmento de detalle.

    Parámetros de entrada del Resource-Viewer del ejemplo
  • home.jspx modificado incluyendo rah:resourceActionBehavior tag para linkar con el nuevo servicio


  
  
  


  • Al ejecutar el ejemplo hacer Click sobre el enlace generado por el Tag y comprobar que la información fue enviada de manera correcta a la Bounded Task Flow.
Enlace generado con RAH Tag

Resource-Viewer tras hacer click al Link generado con RAH Tag

Referencias:

martes, 15 de abril de 2014

Integrando WebCenter Likes / Comments API - I

Este post es un ejemplo de como usar el API de Activity Streaming para manejar los Likes y Comentarios de los servicios / contenidos de WebCenter Portal.


Enlace a la versión en inglés
 

Lista de gente que ha hecho Like a una actividad

"La parte de los comentarios será ampliada en Integrando WebCenter Likes / Comments API-II" (Pronto)

Hay un servicio por defecto que permite aplicar la funcionalidad de Likes sobre contenidos.

Descargar LikesCommentsExtension JDeveloper Project

Por ejemplo:
Document Manager Task Flow permite la funcionalidad de “Like“/“Unlike” sobre los contenidos almacenados en WebCenter Content.

Document Explorer trae por defecto Like

How can I add the same functionality to the Content Presenter Templates?

Este ejemplo hace uso de Activity Streaming API aplicado sobre un contenido mostrado por Content Presenter. Para ello, obtiene toda la información necesaria del contenido a partir de la variable  oracle.webcenter.content.integration.Node presente en las plantillas de Content Presenter.

package custom.oracle.webcenter.likescomments;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import oracle.adf.share.logging.ADFLogger;

import oracle.webcenter.activitystreaming.ActivityException;
import oracle.webcenter.activitystreaming.ActivityObject;
import oracle.webcenter.activitystreaming.ActivityStreamingService;
import oracle.webcenter.activitystreaming.ActivityStreamingServiceFactory;
import oracle.webcenter.comments.Comment;
import oracle.webcenter.comments.CommentsSummary;
import oracle.webcenter.content.integration.Node;
import oracle.webcenter.content.integration.RepositoryException;
import oracle.webcenter.content.integration.spi.ucm.UCMConstants;
import oracle.webcenter.doclib.internal.model.VCRUtils;
import oracle.webcenter.framework.service.Scope;
import oracle.webcenter.framework.service.ServiceContext;
import oracle.webcenter.framework.service.ServiceObjectType;
import oracle.webcenter.likes.Like;
import oracle.webcenter.likes.LikesSummary;

/**
 * Utility class to access to Likes and Comments of a specific Node.
 * This class will access in Map EL Expression way
 * TODO: Implement a Declarative Component / or bean bigger scope to don't recalculate everything
 * @author Daniel Merchan Garcia
 * @version 1.0
 */
public final class LikesCommentsProcessor {
    
    /**
     * Logger
     */
    private static final ADFLogger LOG =
        ADFLogger.createADFLogger(LikesCommentsProcessor.class);
    
    /**
     * Class name to be used by the logger
     */
    private static final String CLASS_NAME =
        LikesCommentsProcessor.class.getName();

    /**
     * Map holding nodeLikesComments
     */
    private Map nodeLikesComments;

    /**
     * Default Constructor
     */
    public LikesCommentsProcessor() {
        super();
        
        // Implementation via Map EL expression
        nodeLikesComments = new HashMap() {
                @Override
                public NodeLikeComments get(Object key) {
                    if (key != null && key instanceof Node) {
                        Node node = (Node)key;
                        NodeLikeComments nlc = this.getCommentsLikes(node);
                        return nlc;
                    } else {
                        return super.get(key);
                    }
                }

                /**
                 * Extract from a Node all Comments and Likes
                 * @param node
                 */
                private NodeLikeComments getCommentsLikes(Node node) {
                    LOG.entering(CLASS_NAME, "getCommentsLikes");
                    // FIXME prevent Folder item asking about isFolder
                    NodeLikeComments nlc = new NodeLikeComments();
                    nlc.setNode(node);
                    try {
                        ActivityStreamingService as = ActivityStreamingServiceFactory.getInstance().getActivityStreamingService();
                        // Extract inforamtion required for ActivityStreaming API and Likes Tag
                        String resourceId = getResourceId(node);
                        String serviceId = VCRUtils.getStringProperty(node, UCMConstants.SERVICE_ID_PROP_DEF_NAME);
                        String resourceType = VCRUtils.getStringProperty(node, UCMConstants.RESOURCE_TYPE_PROP_DEF_NAME);
                        String name = node.getName();
                        ServiceObjectType serviceObjType = as.findObjectType(serviceId, resourceType);
                        ActivityObject activityObject = as.createObject(resourceId, serviceObjType, name);
                        activityObject.setServiceID(serviceId);
                        ActivityObject actObj = ActivityStreamingServiceFactory.getInstance().getActivityStreamingService().getObjectDetailsManager().getObjectDetail(activityObject);
                        // Extract all information using ActivityObject and node information
                        if (actObj != null) {
                            nlc = getCommentsLikesFromActivityObject(actObj, nlc);
                        } else {
                            // In case of not being registered yet the Id and the Type must to be provided
                            // FIXME: Current GUID or default GUID???? to be decided...
                            nlc.setScopeGUID(ServiceContext.getContext().getScope().getGUID());
                            //nlc.setScopeGUID(ServiceContext.getContext().getDefaultScope().getGUID());
                            nlc.setActivityId(activityObject.getId());
                            nlc.setActivityType(activityObject.getType().getName());
                        }
                    } catch (RepositoryException e) {
                        e.printStackTrace();
                    } catch (ActivityException e) {
                        e.printStackTrace();
                    }
                    return nlc;
                }

                /**
                 * Auxiliar method to get the resourceId expected from the content
                 * @param node
                 * @return [repositoryName]#dDocName:[dDocNameValue]
                 */
                private String getResourceId(Node node) {
                    String repository = node.getId().getRepositoryName();
                    String dDocName = node.getId().getUid();
                    return repository + "#dDocName:" + dDocName;
                }

                /**
                 * Extract and store likes and comments from an ActivityObject
                 * @param actObj with all content information about comments and Likes
                 * @param nlc NodeLikeComments to fill
                 */
                private NodeLikeComments getCommentsLikesFromActivityObject(ActivityObject actObj,
                                                                            NodeLikeComments nlc) {
                    int commentsCount = 0;
                    int likesCount = 0;
                    Like myLike = null;
                    List recentComments = null;
                    try {
                        // Retrieving all comments 
                        CommentsSummary commentsSummary = actObj.getCommentsSummary();
                        if (commentsSummary != null) {
                            commentsCount = commentsSummary.getCount();
                            recentComments = commentsSummary.getRecentComments();
                            for (Comment o : recentComments) {
                                // TODO: Testing API purpose
                                LOG.fine("Comment:" + o.toString());
//                                LOG.fine("AuthorId:" + o.getId());
//                                LOG.fine("CommentText:" + o.getCommentText());
//                                LOG.fine("Creation Date:" + o.getCreationDate());
                            }
                        }
                        LikesSummary likesSummary = actObj.getLikesSummary();
                        if (likesSummary != null) {
                            likesCount = likesSummary.getCount();
                            myLike = likesSummary.getMyLike();
                        }
                        nlc.setActivityType(actObj.getType().getName());
                        nlc.setActivityId(actObj.getId());
                        Scope scope = actObj.getScope();
                        if (scope != null) {
                            nlc.setScopeGUID(scope.getGUID());
                        } else {
                            nlc.setScopeGUID(ServiceContext.getContext().getDefaultScope().getGUID());
                        }
                        // nlc.setScopeGUID(actObj.getScope().getGUID());
                        //nlc.setScopeGUID(ServiceContext.getContext().getScope().getGUID());
                        nlc.setRecentComments(recentComments);
                        nlc.setCommentsCount(Integer.valueOf(commentsCount));
                        nlc.setLikesCount(Integer.valueOf(likesCount));
                        nlc.setMyLike(myLike);
                        if (LOG.isFinest()) {
                            LOG.finest(CLASS_NAME,"getCommentsLikes",nlc.toString());
                        }
                    } catch (ActivityException e) {
                        LOG.warning(CLASS_NAME,"getCommentsLikes","Error using Activity Stream API for Likes / Comments",e);
                    }
                    LOG.exiting(CLASS_NAME, "getCommentsLikes");
                    return nlc;
                }
            };
    }

    /**
     * Get map containing the nodes and likes associated to the content
     * @return Map
     */
    public Map getNodeLikesComments() {
        return nodeLikesComments;
    }
}


El API esta implementando de tal forma que se pueda acceder en modo Map / EL Expression.

Los parámetros requeridos por el TAG que implementa Likes (<likes:likesLink>) son:

ParámetroDescripciónTipo
idIdentificador del componenteString
renderedFlag que indica si el componente debe ser renderizadoBoolean
serviceIdIdentificador de servicio de WebCenter (Por ejemplo: oracle.webcenter.content, oracle.webcenter.doclib… para contenidos)String
objectTypeTipo de contenido. En caso de ser contenido de WebCenter Content puede ser: webContent, content, blog, wiki, folderString
objectIdIdentificador del objeto de la actividad. Por ejemplo para oracle.webcenter.content/doclib es [repoName]#dDocName:[dDocNameValue]String
scopeIdIdentificador del Portal / Espacio propietario el contenidoString
likesCountNúmero actual de LikesInt
myLikeLikes Services asociado con el usuario actualoracle.webcenter.likes.Like

La plantilla de ejemplo usada con Content Presenter es la siguiente.


    
        
            
            
            
            
            
            
            
            
            
            
            
         
        
    



Como se puede observar, se usa un Managed Bean que ha sido declarado customizando Content Presenter para usar el API implementado en LikesCommentsProcessor.

Al hacer Click en el botón de Like, éste funcionara de igual manera que el que puede aparecer en el Task Flow de Document Explorer.

Botón Like en Content Presenter

Además, el popup que muestra la información de usuarios que han hecho Like funciona perfectamente.

Popup con la gente que hizo Like

Cómo usarlo?

El proyecto despliega una librería compartida que debe ser registrada en el fichero de configuración weblogic.xml de WebCenter Portal / Framework Portal.

Además, (no incluído). Se ha customizado el Task Flow de Content Presenter para añadir un Managed Bean para invocar al API.

La plantilla de Content Presenter de la imagen es la misma que la del código mostrado.

jueves, 10 de abril de 2014

CTRL + Shift + C: Acceso al modo contribución por Java

La manera estándar de acceder al modo contribución es mediante la combinación CTRL + Shift + C.

Modo contribución
Sin embargo, puede que se requiera poder realizar lo mismo mediante otra combinación de teclas o simplemente desde un botón.

Para ello no hay más que usar el siguiente fragmento de código.

import oracle.adfinternal.view.page.editor.bean.PageEditorPanelBean;
import oracle.adfinternal.view.page.editor.utils.Utility;


 if (ModeContext.getCurrent().isInEditMode()) {
    return;
 }

PageEditorPanelBean pgEditorPanelBean = PageEditorPanelBean.getCurrentInstance();

pgEditorPanelBean.toggleCCView();

Utility.refreshPageCustomizable(); 

JDeveloper advertirá que oracle.adfinternal solamente es para uso interno y que estas clases no deberían usarse. Sin embargo, no hay ninguna clase para realizar esto en el API Público como lo hay para cambiar a modo edición (Composer: CTRL + Shift + E). Por ello esta advertencia puede ser "omitida".

Advertencia de uso interno clases

La manera más rápida de probarlo es crear un Managed Bean y asociar al actionListener una acción que ejecute el código.

Cambiar a modo contribución

jueves, 27 de marzo de 2014

Extender WebCenter Portal Analytics

Recientemente he publicado un artículo técnico de cómo extender WebCenter Analytics para recolectar eventos producidos en Content Presenter.

Enlace a la página del artículo (en inglés)

Enlace directo al PDF.
Enlace directo al código.

http://goo.gl/izfpUL

En este artículo se describe lo siguiente:
  • Modelo de datos usado por WebCenter Analytics.
  • Cómo se registra un nuevo evento.
  • Cómo utilizar el API para recolectar información de los nuevos eventos.
  • Extender Content Presenter para añadir los eventos
    • Contenido visto en una plantilla de detalle.
    • Contenido visto en una plantilla de listado.
Espero que sirva para entender y conocer mejor el API de WebCenter.

miércoles, 26 de marzo de 2014

Global Custom Attributes en WebCenter Portal

En WebCenter Portal (anteriormente Spaces) pueden declararse Custom Global Attributes que pueden ser accedidos desde todo portal (space).

Enlace a versión en inglés
Enlace a descarga del proyecto de extensión de Portal

Uno de los attributos que viene por defecto es el wcSessionTimeoutPeriod que establece el tiempo de sesión para la aplicación .

Global Attributes
El acceso a estos atributos puede hacerse desde:
  • EL Expression
  • Java.
¿Cómo accedo usando EL Expression?

#{WCAppContext.application.applicationConfig.customAttributes['wcSessionTimeoutPeriod']}

¿Cómo accedo desde Java? Por ejemplo, ¿desde un Servlet desplegado en Portal (Spaces)?
En caso de acceso por Java puede hacerse también resolviendo la EL Expression anterior. Sin embargo, en caso de hacerlo desde un Servlet esta vía no es factible por lo que se puede acceder usando el siguiente fragmento de código:

import oracle.webcenter.webcenterapp.WebCenterException;
import oracle.webcenter.webcenterapp.internal.model.WebCenterUtils;


try {
    String timeout = WebCenterUtils.getAppCustomAttributeValue("wcSessionTimeoutPeriod");
    System.out.println("Timeout: " + timeout);
} catch (WebCenterException e) {
    e.printStackTrace();
}

Para usar este API es necesario añadir las siguientes librerias:
  • WebCenter Spaces Model
  • WebCenter Spaces Client

martes, 4 de marzo de 2014

Mantenimiento: Purgado y Fragmentación

A la hora de mantener los entornos WebCenter (Content, Portal) limpios y con un rendimiento óptimo hay que tener en cuenta, además de los sistemas de caché y configuraciones optimizadas, el sistema de purgado y limpieza de las bases de datos sobre las que se ejecutan. Aquí se recogen algunos (no todos) que hay que tener en cuenta en entornos productivos.

Versión en inglés

ADF Framework
El framework de ADF usa dos tablas llamada PS_TXN y PCOLL_CONTROL para guardar el estado, la sesión y en general la pasivación de los datos cuando se usa ADF Business Components. Además hace uso de una secuencia llamada PS_TXN_seq.


Tabla PS_TXN
Para mantener las aplicaciones Fusion Middleware en recomendable la configuración de un Job periodico que realize el purgado de estas tablas. Por ejemplo cada día.

Las PL/SQL de purgado pueden ser encontradas (en caso de desarrollos con WebCenter y JDeveloper 11gR1) en el [JDEV_MIDDLEWARE_HOME]/oracle_common/modules/oracle.adf.model_11.1.1/bc4jcleanup.sql

Referencias:
Oracle WebCenter Content
En el caso de Oracle WebCenter Content configurado con alguno de los siguientes indexadores de búsqueda:
  • DATABASE.FULLTEXT
  • OracleTextSearch
Muestras de un indice fragmentado (88%)
Es importantisimo la desfragmentación de los índices utilizados por estos motores para que las búsquedas siempre sean óptimas. Ejecutar la desfragmentación después de una reindexación total es una de las mejores prácticas. En caso de uso de un Job periódico, éste debe ser ejecutado en horario de poco acceso dado que este proceso puede afectar al rendimiento de la plataforma.

Referencias:

WebCenter Portal
WebCenter Portal se basa en MDS (Metadata Service Repository) para almacenar y gestionar las customizaciones y datos internos de la plataforma. El MDS tiene dos configuraciones importantes:

  • Tamaño de caché.
  • Auto purgado y tiempo entre cada purgado.
Esto es configurado en el archivo de configuración adf-config.xml como sigue:

Configuracion de la caché de MDS y Auto Purgado en adf-config.xml
Caché
En caso de que el nivel de customizaciones del portal sea alto es recomendable incrementar este valor:

<mds-config>
...
<cache-config>
        <max-size-kb>100000</max-size-kb>
</cache-config>
 

...
</mds-config>

Purgado
El purgado del repositorio de metadatos es una labor de mantenimiento imprescindible en toda plataforma WebCenter Portal / Framework Portal.  Esta operación no debe ser ejecutada con una frecuencia baja puesto que puede afectar al rendimiento.

<persistence-config>
...
<auto-purge seconds-to-live="3600"/>
...
</persistence-config>

Cómo y dónde configurarlo?
Design Time:
  • WebCenter Framework Portal: Modificando directamente el archivo adf-config.xml
  • WebCenter Portal (previamente llamado Spaces): Customizando el archivo adf-config.xml de configuración.
Runtime:
Para ambos casos es posible configurar tanto el tamaño de caché como el auto purgado desde el árbol de MBeans asociado al servidor.

Configuracion del MDS (Caché y Autopurgado) usando Fusion Middleware Control
Referenciass: