jueves, 11 de agosto de 2011

Seleccionar el lenguaje (locale) según el dominio en Apache Tapestry

Apache Tapestry
Apache Tapestry por defecto determina el lenguaje (locale) de la aplicación por los headers que envía el navegador del usuario con sus preferencias. Si tenemos un sitio internacional con varios dominios y soportando varios lenguajes para cada país o dominio quizá nos interese más determinar el idioma del usuario por el dominio al que accede. Una de las cosas excelentes de Tapestry es que es extremadamente modular y extensible y cualquier cosa que es definida como un servicio puede ser adaptada a nuestras necesidades a través del contenedor de dependencias (IOC).

Para conseguir lo que queremos el servicio que debemos redefinir es org.apache.tapestry5.services.LocalizationSetter que es el responsable de establecer el locale según las preferencias enviadas por el usuario en la petición. Por tanto debemos desarrollar una clase con la lógica que necesitamos y que implemente esa interfaz. Pero no hace falta que la desarrollemos desde cero, podemos partir de la que usa Tapestry internamente org.apache.tapestry5.internal.services.LocalizationSetterImpl (el software libre/código abierto es genial ¿verdad?).

La parte interesante e importante que debemos redefinir es la siguiente:

	/* AppLocalizationSetter.java */
	...
	private final Request request;
                private final PersistentLocale persistentLocale;
        	private final ThreadLocale threadLocale;

                private Map<String, String> defaultLocales;
                ...
                public AppLocalizationSetter(Request request, PersistentLocale persistentLocale, ThreadLocale threadLocale, @Symbol(SymbolConstants.SUPPORTED_LOCALES) String localeNames, ...) {
                        ...
                        this.defaultLocales = new HashMap<String, String>();
                }
	...
	public boolean setLocaleFromLocaleName(String localeName) {
		boolean supported = isSupportedLocaleName(localeName);
		
		Locale supportedLocale = findClosestSupportedLocale(localeName);
		if (!localeName.trim().equals("")) {
			persistentLocale.set(supportedLocale);		
		}

		threadLocale.setLocale(supportedLocale);

		return supported;
        }
        ...
	private Locale findClosestSupportedLocale(String desiredLocale) {
		String localeName = desiredLocale;

		while (true) {
			if (isSupportedLocaleName(localeName))
				return toLocale(localeName);

			localeName = stripTerm(localeName);

			if (localeName.length() == 0)
				break;
		}
               return getDefaultLocale(desiredLocale);
	}

	private String getDomainLocale() {
		String dominio = request.getServerName();
		if (defaultLocales.get(dominio) != null) {
			return defaultLocales.get(dominio);
		}
		// Buscar el idioma según el dominio
                // ¿en una constante?
                // ¿quizá en base de datos?
		String locale = ...;
		defaultLocales.put(dominio, locale);
                return locale;
	}

	private Locale getDefaultLocale(String desiredLocale) {
                String domainLocale = getDomainLocale();
        
                if (domainLocale.equals(desiredLocale)) {
                        // El locale del dominio no está soportado
            	        return supportedLocales.get(0);
                }
        
                Locale defaultLocale = findClosestSupportedLocale(domainLocale);
        
                return defaultLocale;
	}
	...

	/* AppModule.java */
	...
	public static void bind(ServiceBinder binder) {
		binder.bind(LocalizationSetter.class, AppLocalizationSetter.class).withId("AppLocalizationSetter");
	}
	
	public static void contributeServiceOverride(MappedConfiguration configuration, @Local LocalizationSetter override) {
		configuration.add(LocalizationSetter.class, override);
	}
	...


Lo importante a modificar es la forma de encontrar un locale soportado dado un localeName e implementar el método getDomainLocale() que tendrá nuestra lógica, obteniendo el Locale a partir de dominio por el que se ha accedido a la aplicación («request.getServerName()»). La forma de hacer la correspondencia entre el dominio y Locale la podemos hacer de la mejor forma que se nos ocurra de forma simple con un mapa con los dominios como claves y los locales como valor o accediendo a una base de datos que tenga la correspondencia.

Tapestry codifica en las URLs el idioma del usuario para hacerlo persistente, esto se hace con persistentLocale.set(locale). Si el locale que se intenta establecer está soportado por la aplicación se hacer persistente para posteriores peticiones y se hace efectivo para la petición actual. Si el locale que se intenta establecer no está directamente soportado se busca uno más general soportado, esto es lo que hace el método findClosestSupportedLocale. Por ejemplo, si se intenta usar el locale «en_US» y nuestra aplicación no lo soporta pero si soporta «en» se usa este último. Finalmente, si no encuentra ninguno devuelve el del dominio con la lógica que hemos definido y si tampoco es uno de los soportados de entre los de la lista del símbolo SymbolConstants.SUPPORTED_LOCALES finalmente se usa el del defecto de la aplicación (el primero de esa lista).

Deberemos hacer las contribuciones de nuestra aplicación en AppModule.java que sobreescriben el sevicio usado por defecto en Tapestry por el sevicio con nuestra lógica.

Tapestry tiene un gran diseño y es muy flexible y adaptable, este es un ejemplo, redefiinimos el comportamiento con una nueva lógica que está localizada en una clase y puede ser fácilmente redefinida por otra en el momento que tengamos necesidad sin tener que cambiar nada de código en el resto de la aplicación salvo en el AppModule.

Referencia:
Documentación sobre Apache Tapestry