--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.rwt.RWT;
+
+/** NLS attached to a given thread */
+public class ThreadNLS<T extends NLS> extends InheritableThreadLocal<T> {
+ public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
+
+ private final String bundleLocation;
+
+ private Class<T> type;
+ private Boolean utf8 = false;
+
+ public ThreadNLS(String bundleLocation, Class<T> type, Boolean utf8) {
+ this.bundleLocation = bundleLocation;
+ this.type = type;
+ this.utf8 = utf8;
+ }
+
+ public ThreadNLS(Class<T> type) {
+ this(DEFAULT_BUNDLE_LOCATION, type, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected T initialValue() {
+ if (utf8)
+ return (T) RWT.NLS.getUTF8Encoded(bundleLocation, type);
+ else
+ return (T) RWT.NLS.getISO8859_1Encoded(bundleLocation, type);
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.osgi.util.NLS;
+
+/** RCP specific {@link NLS} to be extended */
+public class DefaultNLS extends NLS {
+ public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
+
+ public DefaultNLS() {
+ this(DEFAULT_BUNDLE_LOCATION);
+ }
+
+ public DefaultNLS(String bundleName) {
+ NLS.initializeMessages(bundleName, getClass());
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.argeo.util.LocaleUtils;
+import org.eclipse.osgi.util.NLS;
+
+/** NLS attached to a given thread */
+public class ThreadNLS<T extends NLS> extends InheritableThreadLocal<T> {
+ public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
+
+ private final String bundleLocation;
+
+ private Class<T> type;
+ private Boolean utf8 = false;
+
+ public ThreadNLS(String bundleLocation, Class<T> type, Boolean utf8) {
+ this.bundleLocation = bundleLocation;
+ this.type = type;
+ this.utf8 = utf8;
+ }
+
+ public ThreadNLS(Class<T> type) {
+ this(DEFAULT_BUNDLE_LOCATION, type, false);
+ }
+
+ @Override
+ protected T initialValue() {
+ ResourceBundle bundle = ResourceBundle.getBundle(bundleLocation,
+ LocaleUtils.threadLocale.get(), type.getClassLoader());
+ T result;
+ try {
+ NLS.initializeMessages(bundleLocation, type);
+ Constructor<T> constructor = type.getConstructor();
+ constructor.setAccessible(true);
+ result = constructor.newInstance();
+ final Field[] fieldArray = type.getDeclaredFields();
+ for (int i = 0; i < fieldArray.length; i++) {
+ int modifiers = fieldArray[i].getModifiers();
+ if (String.class.isAssignableFrom(fieldArray[i].getType())
+ && Modifier.isPublic(modifiers)
+ && !Modifier.isStatic(modifiers)) {
+ try {
+ String value = bundle
+ .getString(fieldArray[i].getName());
+ byte[] bytes = value.getBytes();
+
+ String forcedValue;
+ if (utf8)
+ forcedValue = new String(bytes, "UTF8");
+ else
+ forcedValue = value;
+ if (value != null) {
+ fieldArray[i].setAccessible(true);
+ fieldArray[i].set(result, forcedValue);
+ }
+ } catch (final MissingResourceException mre) {
+ fieldArray[i].setAccessible(true);
+ fieldArray[i].set(result, "");
+ mre.printStackTrace();
+ }
+ }
+ }
+ return result;
+ } catch (final Exception ex) {
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.callback.Callback;
+
+/** Choose in a list of locales */
+public class LocaleCallback implements Callback {
+ private List<Locale> availableLocales = new ArrayList<Locale>();
+
+ private Integer selectedIndex = null;
+ private Integer defaultIndex = null;
+ private String prompt = "Language";
+
+ public LocaleCallback(Integer defaultIndex, List<Locale> availableLocales) {
+ this.availableLocales = Collections
+ .unmodifiableList(new ArrayList<Locale>(availableLocales));
+ this.defaultIndex = defaultIndex;
+ this.selectedIndex = defaultIndex;
+ }
+
+ /**
+ * Convenience constructor based on a comma separated list of iso codes (en,
+ * en_US, fr_CA, etc.). Default selection is default locale.
+ */
+ public LocaleCallback(String locales) {
+ if (locales == null || locales.trim().equals(""))
+ return;
+ String[] codes = locales.split(",");
+ for (int i = 0; i < codes.length; i++) {
+ String code = codes[i];
+ // variant not supported
+ int indexUnd = code.indexOf("_");
+ Locale locale;
+ if (indexUnd > 0) {
+ String language = code.substring(0, indexUnd);
+ String country = code.substring(indexUnd + 1);
+ locale = new Locale(language, country);
+ } else {
+ locale = new Locale(code);
+ }
+ availableLocales.add(locale);
+ if (locale.equals(Locale.getDefault()))
+ defaultIndex = i;
+ }
+
+ if (defaultIndex == null)
+ defaultIndex = 0;
+
+ this.selectedIndex = defaultIndex;
+ }
+
+ public String[] getSupportedLocalesLabels() {
+ String[] labels = new String[availableLocales.size()];
+ for (int i = 0; i < availableLocales.size(); i++) {
+ Locale locale = availableLocales.get(i);
+ if (locale.getCountry().equals(""))
+ labels[i] = locale.getDisplayLanguage(locale) + " ["
+ + locale.getLanguage() + "]";
+ else
+ labels[i] = locale.getDisplayLanguage(locale) + " ("
+ + locale.getDisplayCountry(locale) + ") ["
+ + locale.getLanguage() + "_" + locale.getCountry()
+ + "]";
+
+ }
+ return labels;
+ }
+
+ public Locale getSelectedLocale() {
+ return availableLocales.get(selectedIndex);
+ }
+
+ public void setSelectedIndex(Integer selectedIndex) {
+ this.selectedIndex = selectedIndex;
+ }
+
+ public Integer getDefaultIndex() {
+ return defaultIndex;
+ }
+
+ public String getPrompt() {
+ // TODO localize it?
+ return prompt;
+ }
+
+ public void setPrompt(String prompt) {
+ this.prompt = prompt;
+ }
+
+ public List<Locale> getAvailableLocales() {
+ return availableLocales;
+ }
+
+ public static void main(String[] args) {
+ for (String isoL : Locale.getISOLanguages()) {
+ Locale locale = new Locale(isoL);
+ System.out.println(isoL + "\t" + locale.getDisplayLanguage() + "\t"
+ + locale.getDisplayLanguage(locale));
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.util;
+
+import java.util.Locale;
+
+import org.argeo.ArgeoException;
+
+/** Utilities around internationalization. */
+public class LocaleUtils {
+ /**
+ * The locale of the current thread and its children. Allows to deal with
+ * internationalisation as a cross cutting aspect. Never null.
+ */
+ public final static InheritableThreadLocal<Locale> threadLocale = new InheritableThreadLocal<Locale>() {
+ @Override
+ protected Locale initialValue() {
+ return Locale.getDefault();
+ }
+
+ @Override
+ public void set(Locale value) {
+ if (value == null)
+ throw new ArgeoException("Thread local cannot be null.");
+ super.set(value);
+ }
+
+ };
+}
log4j.configuration=file:../../log4j.properties
org.argeo.security.ui.initialPerspective=org.argeo.jcr.ui.explorer.perspective
eclipse.application=org.argeo.security.ui.rcp.secureUi
+
+user.language=fr
log4j.configuration=file:../../log4j.properties
+argeo.i18n.availableLocales=en,fr,de,ru,ar
+eclipse.registry.MultiLanguage=true
+
# Tomcat
#argeo.server.port.http=7070
#argeo.server.port.https=7443
<bean id="springLoginModule" class="org.argeo.security.equinox.SpringLoginModule"
scope="prototype">
<property name="authenticationManager" ref="authenticationManager" />
+ <property name="availableLocales" value="${argeo.i18n.availableLocales}"/>
</bean>
<bean id="springLoginModuleRemote" class="org.argeo.security.equinox.SpringLoginModule"
argeo.security.systemKey=argeo
argeo.security.anonymousRole=ROLE_ANONYMOUS
+
+argeo.i18n.availableLocales=
\ No newline at end of file
*/
package org.argeo.security.equinox;
+import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.security.NodeAuthenticationToken;
+import org.argeo.util.LocaleCallback;
+import org.argeo.util.LocaleUtils;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.BadCredentialsException;
private Boolean remote = false;
private Boolean anonymous = false;
+ /** Comma separated list of locales */
+ private String availableLocales = "";
private String key = null;
private String anonymousRole = "ROLE_ANONYMOUS";
if (subject.getPublicCredentials() != null)
subject.getPublicCredentials().clear();
+ Locale selectedLocale = null;
// deals first with public access since it's simple
if (anonymous) {
+ // multi locale
+ if (callbackHandler != null && availableLocales != null
+ && !availableLocales.trim().equals("")) {
+ LocaleCallback localeCallback = new LocaleCallback(
+ availableLocales);
+ callbackHandler.handle(new Callback[] { localeCallback });
+ selectedLocale = localeCallback.getSelectedLocale();
+ }
+
// TODO integrate with JCR?
Object principal = UUID.randomUUID().toString();
GrantedAuthority[] authorities = { new GrantedAuthorityImpl(
Authentication auth = authenticationManager
.authenticate(anonymousToken);
registerAuthentication(auth);
- return super.login();
- }
-
- if (callbackHandler == null)
- throw new LoginException("No call back handler available");
-
- // ask for username and password
- NameCallback nameCallback = new NameCallback("User");
- PasswordCallback passwordCallback = new PasswordCallback(
- "Password", false);
- final String defaultNodeUrl = System.getProperty(NODE_REPO_URI,
- "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
- NameCallback urlCallback = new NameCallback("Site URL",
- defaultNodeUrl);
-
- // handle callbacks
- if (remote)
- callbackHandler.handle(new Callback[] { nameCallback,
- passwordCallback, urlCallback });
- else
- callbackHandler.handle(new Callback[] { nameCallback,
- passwordCallback });
-
- // create credentials
- String username = nameCallback.getName();
- if (username == null || username.trim().equals(""))
- return false;
-
- String password = "";
- if (passwordCallback.getPassword() != null)
- password = String.valueOf(passwordCallback.getPassword());
-
- NodeAuthenticationToken credentials;
- if (remote) {
- String url = urlCallback.getName();
- credentials = new NodeAuthenticationToken(username, password,
- url);
} else {
- credentials = new NodeAuthenticationToken(username, password);
+ if (callbackHandler == null)
+ throw new LoginException("No call back handler available");
+
+ // ask for username and password
+ NameCallback nameCallback = new NameCallback("User");
+ PasswordCallback passwordCallback = new PasswordCallback(
+ "Password", false);
+ final String defaultNodeUrl = System
+ .getProperty(NODE_REPO_URI,
+ "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
+ NameCallback urlCallback = new NameCallback("Site URL",
+ defaultNodeUrl);
+ LocaleCallback localeCallback = new LocaleCallback(
+ availableLocales);
+
+ // handle callbacks
+ if (remote)
+ callbackHandler.handle(new Callback[] { nameCallback,
+ passwordCallback, urlCallback, localeCallback });
+ else
+ callbackHandler.handle(new Callback[] { nameCallback,
+ passwordCallback, localeCallback });
+
+ selectedLocale = localeCallback.getSelectedLocale();
+
+ // create credentials
+ String username = nameCallback.getName();
+ if (username == null || username.trim().equals(""))
+ return false;
+
+ String password = "";
+ if (passwordCallback.getPassword() != null)
+ password = String.valueOf(passwordCallback.getPassword());
+
+ NodeAuthenticationToken credentials;
+ if (remote) {
+ String url = urlCallback.getName();
+ credentials = new NodeAuthenticationToken(username,
+ password, url);
+ } else {
+ credentials = new NodeAuthenticationToken(username,
+ password);
+ }
+
+ Authentication authentication;
+ try {
+ authentication = authenticationManager
+ .authenticate(credentials);
+ } catch (BadCredentialsException e) {
+ // wait between failed login attempts
+ Thread.sleep(waitBetweenFailedLoginAttempts);
+ throw e;
+ }
+ registerAuthentication(authentication);
}
- Authentication authentication;
- try {
- authentication = authenticationManager
- .authenticate(credentials);
- } catch (BadCredentialsException e) {
- // wait between failed login attempts
- Thread.sleep(waitBetweenFailedLoginAttempts);
- throw e;
- }
- registerAuthentication(authentication);
- boolean res = super.login();
- return res;
+ if (selectedLocale != null)
+ LocaleUtils.threadLocale.set(selectedLocale);
+
+ return super.login();
} catch (LoginException e) {
throw e;
} catch (ThreadDeath e) {
this.key = key;
}
+ public void setAvailableLocales(String locales) {
+ this.availableLocales = locales;
+ }
+
}
import org.apache.commons.logging.LogFactory;
import org.argeo.ArgeoException;
import org.argeo.eclipse.ui.ErrorFeedback;
+import org.argeo.util.LocaleUtils;
import org.eclipse.equinox.security.auth.ILoginContext;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.rwt.RWT;
SecurityContextHolder
.setContext((SecurityContext) contextFromSessionObject);
- if (log.isDebugEnabled())
- log.debug("THREAD=" + Thread.currentThread().getId()
- + ", sessionStore=" + RWT.getSessionStore().getId()
- + ", remote user=" + httpRequest.getRemoteUser());
+// if (log.isDebugEnabled())
+// log.debug("THREAD=" + Thread.currentThread().getId()
+// + ", sessionStore=" + RWT.getSessionStore().getId()
+// + ", remote user=" + httpRequest.getRemoteUser());
// create display
final Display display = PlatformUI.createDisplay();
loginContext.login();
subject = loginContext.getSubject();
+ // add security context to session
if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null)
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
+ // add thread locale to RWT session
+ log.info("Locale "+LocaleUtils.threadLocale.get());
+ RWT.setLocale(LocaleUtils.threadLocale.get());
// Once the user is logged in, she can have a longer session
// timeout
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
+import org.argeo.util.LocaleCallback;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
}
protected Point getInitialSize() {
- return new Point(300, 180);
+ return new Point(350, 180);
}
@Override
createNameHandler(composite, (NameCallback) callback);
} else if (callback instanceof PasswordCallback) {
createPasswordHandler(composite, (PasswordCallback) callback);
+ } else if (callback instanceof LocaleCallback) {
+ createLocaleHandler(composite, (LocaleCallback) callback);
}
}
}
});
}
+ private void createLocaleHandler(Composite composite,
+ final LocaleCallback callback) {
+ String[] labels = callback.getSupportedLocalesLabels();
+ if (labels.length == 0)
+ return;
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getPrompt());
+
+ final Combo combo = new Combo(composite, SWT.READ_ONLY);
+ combo.setItems(labels);
+ combo.select(callback.getDefaultIndex());
+ combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ combo.addSelectionListener(new SelectionListener() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ callback.setSelectedIndex(combo.getSelectionIndex());
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ });
+ }
+
private void createNameHandler(Composite composite,
final NameCallback callback) {
Label label = new Label(composite, SWT.NONE);