domingo, 8 de febrero de 2015

WebCenter Portal: Custom Skin en Shared Library

Para WebCenter Framework Portal parece estar claro el procedimiento y la manera de poner una Custom Skin para multidiomar los literales de ADF.  Esto lo explicaba en:

Custom Skin ADF: Modificación de los estilos y traducción de literales

Baja el ejemplo PortalCustomSkins de mi repositorio Git de WebCenter Portal

En esta entrada trato de aclarar una de las mayores dudas acerca de cómo hacer Skins con sobrescritura de literales ADF para WebCenter Portal (anteriormente conocido como Spaces).

Aquí se resuelven las siguientes dudas:
  • Cómo desplegar un Skin como Shared-lib.
  • Cómo hacer el Skin disponible en la Administración de WebCenter Portal.
Cómo despliego un Skin como Shared-lib para WebCenter Portal?
Básicamente se puede crear una aplicación con la siguiente estructura:

Estructura de proyecto de Skins para WebCenter Portal
 Un proyecto por cada Skin que se vaya a desarrollar y poner los archivos en las siguiente carpeta.
    • El archivo trinidad-skins.xml bajo src/META-INF
    • El archivo CSS en una carpeta bajo src/META-INF/adf (Con ello se consigue que el Resource Loader de ADF sea quien sirva el archivo).
    • Generar un archivo JAVA o Properties para incluir los literales de los componentes ADF que se quieran sobrescribir.
  • Generar un Deployment Profile del projecto de tipo JAR Library. En su configuración asegurar que los archivos .class del src y los archivos de META-INF son adheridos a la librería.
    Incluir los archivos de META-INF y Java/Properties dentro del JAR.
  • Un proyecto para desplegar la librería en WC_Spaces como Shared-lib (WAR Deployment Profile dependiente de los deployment profiles de los skins).
    Proyecto para desplegar los Skin como Shared-lib
    Adherir los Skins como parte de la Shared-lib WAR.

    Desplegarla en WC_Spaces donde se encuentra WebCenter Portal
Registrar la nueva Shared-lib en nuestro proyecto de extensión de WebCenter Portal y re-desplegar una nueva versión de extended.webcenter.spaces.war

Registrando la Custom Skin Shared-lib en WebCenter Portal usando la aplicación de Extensión

Cómo hago disponible en la Administración de WebCenter Portal?.
El mejor modo para hacer disponible la nueva Skin es crear una nueva Skin (ya sea mediante la consola de Administración o desde JDeveloper usando el proyecto de DesignWebCenterSpaces) y hacer que extienda de nuestra Custom Skin cambiando el valor de extends.
Nueva Skin que extiende la desplegada en la Shared-lib
Asignar la nueva Skin a un portal / espacio y comprobar que los literales se han traducido.

Literal traducido obtenido del nuevo Skin
 Referencias:

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.