The gettext-tutorial states that
The preferable way to create an I18n object is through the I18nFactory. The factory caches the I18n object internally using the the package name of the provided class object and registers it with I18nManager. Thus all classes of the same package will use the same I18n instance.
But running inside an HttpServlet (actually an VelocityViewServlet in my particular case) it seems to me that if I change the locale on the cached object it could stay like that for subsequent requests and that is not desirable because each request must be served with particular locales.
So the question is, should I instead create ad-hoc i18n objects on a per-request basis, with the specific locale of that request? I think that would be a performance penalty, so should I instead create my own manager and select the appropriate instance based on the request's locale?
Any comments greatly appreciated. There doesn't seem to be much doc or examples besides the tutorial and the Javadoc.
Update: There is one thing I tried and seems to work. Instead of declaring a private static member like the tutorial says, I just call the factory method on each request, using this special version of the I18nFactory:
getI18n(java.lang.Class clazz, java.lang.String bundleName, java.util.Locale locale)
This should get a cached instance of that particular locale (or force the load on the first request for that locale), avoiding the performance penalties on subsequent requests for the same one. If anyone can confirm this make sense:
public class Foo extends VelocityViewServlet {
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
Locale reqLocale = request.getLocale();
I18n i18n = I18nFactory.getI18n(Foo.class, "app.i18n.Messages", reqLocale);
String localized = i18n.tr("Bar");
...
}
...
}
Again, I've tested this and it's working but I'd like to know if it's a correct implementation of gettext-commons for this type of application.
Thanks in advance,
--
Alejandro Imass
Related
I am working on a project where every model has this line:
#Model(adaptables = { SlingHttpServletRequest.class,Resource.class },
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
In my understanding:
If Resource or SlingHTTPRequest is not to be used, this dependency injection must be removed from the model
SlingHTTPRequest can help obtain resource with the use of .getResource method anyway, so using SlingHTTPServeltRequest class alone, with required dependencyInjectionStrategy should be sufficient, and Resource class as an adaptable is never needed?
Please share your thoughts. Thanks in advance!
Question 1)
A SlingModel MUST be either created/adapted from SlingHttpServletRequest or a Resource. It cannot be created from nothing.
The the adaptables-property specifies, from which object it can be created.
If the SlingModel can be created from both, the scripting-environment (e.g. HTL scripts) will use the Resource. But SlingModels can be used elsewhere too, so the source will be random.
Hint 1: Do not use both adaptables. So either decide for SlingHttpServletRequest or Resource. Because both will work, but the injecting will be different - and can cause weird bugs (at least it is thin ice, and hard to test). The example with #Self is simple, but some other injectors are even more complicated, as the implicitly the #Via changes.
#Model(adaptables = { SlingHttpServletRequest.class, Resource.class },
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class MySlingModel {
#Self
// will be null, if adapted from Resource!!!
private SlingHttpServletRequest request;
#Self
// will be null, if adapted from SlingHttpServletRequest!!!
private Resource resource;
Question 2
Components (and so SlingModels) should be context-free, and be represented by a Resource (= JCR node and evtl. some sub-nodes).
So a SlingModel should normally adapted from a Resource. It is also easier to use elsewhere (in other services or sling-models).
Only, if you need something from the request, then switch to the SlingHttpServletRequest. Unfortunately this is often needed for URL mapping. But limit yourself to access RequestAttributes. Even something like WcmMode should not be used in a SlingModel. It is better to see SlingModels as a Wrapper around a Resource, a small Java-Layer to access data.
Hint 2: Not everything is a SlingModel! You can create Services, Servlets, AdapterFactories, Filters, Rewriter, ...
yes its not mandatory. but if your data is in a nested structure (think of slides inside a carousel), and you need to adapt a nested resource (slide) to model, you need resource as adaptable. request.getResource will get you the component resource,
additionally, you might have to work with resources without a request object, say in a workflowprocessstep or a sling job processor. capability to just do resource.adaptTo saves you a bit of time.
Use case: system administrator stores a Freemarker template in a database which is used (by Spring Boot REST API) to present information stored by system users (respondents) in a locale-aware way to a different user type (reviewer).
A respondent's response might be stored in this sort of object (or in lists of this sort of object, in the event a question posed to the respondent is expected to have multiple answers):
// snip
import com.fasterxml.jackson.databind.node.ObjectNode;
// more imports snipped
public class LanguageStringMap {
private Map<Language, String> languageStringMap;
public LanguageStringMap(ObjectNode languageMapNode) {
// snip of code instantiating a LanguageStringMap from JSON
}
public void put(Language language, String value) {
if (value.length() == 0)
throw new IllegalArgumentException(String.format(
"value for language '%s' of zero length", language.getCode()));
languageStringMap.put(language, value);
}
public String get(Language language) { return languageStringMap.get(language); }
}
What I think I want to do is write an ObjectWrapper that maps instances of LanguageStringMap to a string (obtained by calling the get() method with a language derived from the Locale requested by the reviewer's browser and set in the template's settings). This presents a cleaner user experience to the system administrator than making the uploaded template contain a bunch of template method calls would.
To do this, my object wrapper needs to access a template setting. I have perused the pertinent Freemarker documentation, but I am still unclear on how to do this or if it is even possible.
I think it would be a mistake to try to implement this with resource bundles uploaded to the database alongside the templates, but that is a consideration.
Typically you simply put the locale specific string into the data-model before the template is processed, along with all the other variables. In that case no ObjectWrapper customization is needed. But if you have to use an ObjectWrapper-based solution, then you can get the locale inside an ObjectWrapper method (like in the override of DefaultObjectWrapper.handleUnknownType) with Environment.getCurrentEnvironment().getLocale().
I have a business with multiple applications using my webservice resource. I have a web service resource that looks in a http header for the application ID. This tell the server which application is requesting data. My goal is to deliver to my web application developers a method they can call to retrieve all the application specific settings via the application ID.
Given an applicationID i can specify device type, properties file for that app, and whether GCM,APNS or Microsoft Push Notification, etc. So each applicationID has distinct properties basically.
I want the developer to be able to call for this object like this (or similar):
ApplicationData appData = ApplicationDataFactory.getCurrentApplicationData();
and the factory would look something like this:
class ApplicationDataFactory
{
public static ApplicationData getCurrentApplicationData()
{
//notice how im not passing in criteria here, im getting it from the request so call doens't have to know
String criteria = Request.getHTTPHeaderInfo("applicationID");
if ( criteria.equals("Android") )
return new Android();
else if ( criteria.equals("Android-germany") )
return new Android_germany();
else if ( criteria.equals("ios_germany") )
return new ios_germany();
else if ( criteria.equals("ios"))
return new ios();
else if ( criteria.equals("windows") )
return new windows();
return null;//or throw exception
}
}
so Android, ios, and windows objects all extend off ApplicationData class clearly.
So for example the Android.java object would look like this:
class Android extends ApplicationData{
#override
public String getType(){
return "Android"
}
#override
public Properties getProperties{
return system.getProperties("android.properties");
}
}
and the Android-germany and ios-germany will have common data since there both from germany.
First, i dont like that im specifying the criteria inside the factory and also can anyone help me
with a good design pattern i can use to achieve this ? Remember, in the end i want to be able to have the developer call only ApplicationDataFactory.getCurrentApplicationData(); (or something similar) and the correct application info will be sent referenced. I dont have to use a factory here either its just the first thing i thought of.
So your problem is with the fact that the logic for the criteria is within the factory method. Meanwhile, you don't want the user to provide the criteria as an parameter to the factor method.
First of all, I don't like the idea of having a static Request class. A request should be an object that contains information about the current request. I have a suspicion that your code may be prone to race conditions, once you have many concurrent requests (how do you know which request is which?). So as a starting point, I would refactor the Request class so that you work with instances of Request.
I think, the clearest approach would be that you pass in applicationID as a parameter. This makes testability trivial and the code becomes very obvious, too. You take an input and produce the output based on the input. You could pass the Request instead of the applicationID and let the factory handle the retrieval of the applicationID from the request (as you are doing now).
If you think the Request -> applicationID logic should not be part of the factory, you can create another class, such as ApplicationIDResolver which translates a Request to an applicationID. From then on ApplicationDataFactory would be used through an instance and the ApplicationIDResolver would be a constructor parameter. (I think, this is an overkill.). Another option is to add a getApplicationID() method to the Request class.
If you use a dependency injection framework, it may take care of object life cycles/scopes automatically for you, so the ApplicationData could be a request-scoped object and you could tell your dependency injection framework to instantiate ApplicationData objects based on requests and inject them into the classes where they get used.
Better to use for this purposes enum which implements ApplicationData interface and define each entry. You can resolve proper by valueOf() from enum.
These two seem to be doing the same things. Can anyone explain the main difference between the two? When would you use one vs the other?
HttpServletRequest.getRemoteUser()
HttpServletRequest.getUserPrincipal().getName()
A Principal represents someone who could potentially authenticate with your application. The Principal's name depends on the authentication method used:
a username such as "fred" (in the case of HTTP Basic authentication)
a Distinguished Name such as "CN=bob,O=myorg" (in the case of X.509 client certificates - in which case a X500Principal may be returned)
getRemoteUser() returns "the login of the user" which, in the case of HTTP Basic authentication, will also be the username; it doesn't map cleanly in the X.509 client certificate case though, since the user doesn't enter a "login" as such - in the example above, we could use the Distinguished Name or simply the CN, "bob".
The Javadocs state that "whether the user name is sent with each subsequent request depends on the browser and type of authentication", suggesting that getRemoteUser() was originally meant to provide data only for requests in which a username was entered. This, however, would result in it returning null for the majority of requests when cookie-based auth is in use - not too helpful!
In reality, getRemoteUser() often just calls getUserPrincipal().getName(); verified in Tomcat 6 and Jetty 6/7.
The getUserPrincipal() method returns an object of some class derived from the Principal interface, which is an abstraction of the entity that is the "user" responsible for the request. From it you get an actual object that, depending on the implementing class, you can use to get all sorts of information about that user/identity. One of those properties is the string-representation of the name of the user/identity, which you obtain by calling getName().
getRemoteUser() is really just a shortcut to getting that string-representation. You don't have access to any other methods implemented by the implementing class, not do you have access to the object itself, just the string-representation of the name.
For most use-cases that I am familiar with, that string-representation is what you want; I believe this is why getRemoteUser() exists - it's a common case so there's an easy/quick way to get access to it without actually getting a reference to an implementing class object.
A bit related issue:
People converting older IBM Portlet API code to JSR168 one had to change PortletRequest to HttpServletRequest in some method parameters, but then from WPS6.1 and up they can't cast that to PortletRequest (it doesn't implement the respective interface anymore as it seems) and if they call "getRemoteUser" directly on the HttpServletRequest they get back null (some say a workarround is to enable application security option in WAS [WebSphere Application Server], others say more security-related markup is needed in web.xml)
A workarround seems to be to use PUMA, but of course that is IBM WebSphere specific. Probably at other Portlet Containers there are other vendor-specific workarrounds if one finds that getRemoteUser always returns null (judging from other replies then getUserPrincipal().getName() also returns null if getRemoteUser is implemented as just a shortcut to that one).
BTW, the PUMA code I mention above is here, since it's a bit hard to find what works in WPS6.1+:
import com.ibm.portal.portlet.service.PortletServiceHome;
import com.ibm.portal.um.*;
import com.ibm.portal.um.exceptions.PumaException;
import com.ibm.portal.puma.User;
//...
public String getCurrentUser(){
try {
Context ctx = new InitialContext();
Name myjndiname = new CompositeName(PumaHome.JNDI_NAME);
PumaHome myHome = (PumaHome) ctx.lookup(myjndiname);
if (myHome!=null) {
PumaProfile pumaProfile = myHome.getProfile();
com.ibm.portal.um.User user = (com.ibm.portal.um.User)pumaProfile.getCurrentUser();
List attributes = new ArrayList();
attributes.add("uid");
Map userAttributes = pumaProfile.getAttributes(user,attributes);
return (String) userAttributes.get("uid");
}
}
I am trying to implement internationalization in Tomcat. There are going to be different resource text files. My idea is to load all the resources in to the memory while tomcat loads.
Below is the sample code to load multiple resource in to the memory.
public class ResourceBundleLoader {
private static ResourceBundle enResourceBundle;
private static ResourceBundle frResourceBundle;
public static void loadBundle(){
Locale enLocale = new Locale("en", "US");
enResourceBundle = ResourceBundle.getBundle("MessagesBundle",enLocale);
enLocale = new Locale("fr", "FR");
frResourceBundle = ResourceBundle.getBundle("MessagesBundle",enLocale);
}
public static ResourceBundle getEnResourceBundle(){
return enResourceBundle;
}
public static ResourceBundle getFrResourceBundle(){
return frResourceBundle;
}
}
The method loadBundle is called once thru startup servlet. And getEnResourceBundle() and getFrResourceBundle() is called accordingly. Is this right way to implement/maintain internationalization in tomcat? or is there any better way?
Thanks in advance.
You dont need to make this helper class, as per the java documentation the bundles are already cached for you in memory. This will just make your code more complicated to maintain. ie You would have to alter your code every time you add a new "bundle".
Just add code like this to your servlets and/or JSP's:
//request.getLocale() returns the web browsers locale
bundle = ResourceBundle.getBundle("MessagesBundle",request.getLocale())
Just make sure you have a default message bundle file with all your text. Then you can just add extra locales at will as things get translated.
UTF-8 support
I also strongly suggest you create a servlet filter that applies to all requests that ensures that UTF-8 is turned on for both the html that is output, and the parsing of the form responses that are posted back to your application:
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
I wouldn't optimize until I knew the i18n was too slow.
But if I proceeded down your path, instead of using scalar ResourceBundles, I'd put the ResouceBundles into a Map. Now your code can use any bundle knowing the locale - which you have to select the appropriate ResourceBundle anyway.
Your code won't have any if locale is this, use English. Instead, it will be myResourceBundle = bundleMap.get(myLocale);