In my current project we need to implement a way for texters to manage the wicket messages/internationalization via upload of property files.
Also see this question: Administrating internationalized wicket applications
As suggested there, I've implemented a custom IStringResourceLoader and added it at the beginning of the StringResourceLoader list to override any properties already in place:
getResourceSettings().getStringResourceLoaders().add(0, new CustomStringResourceLoader());
This however is not enough, because updates can happen and need to be loaded at runtime. StringResources are cached by wicket and updated only when the ResourceWatcher is triggered.
I found where Wicket adds the string resources to the watcher: the PropertiesFactory in the settings. The method to add a resource to the watcher is addToWatcher(...). However this method is protected and also the whole setup suggests this is used for development purposes and not for production.
I managed to use this method by extending PropertiesFactory and effectively creating a custom version to add to settings:
getResourceSettings().setPropertiesFactory(new CustomPropertiesFactory(getResourceSettings()));
getResourceSettings().setResourcePollFrequency(Duration.seconds(1));
So my Question is: I feel this is quite the circuitious solution. Is there another way to watch for changing properties files?
My solution to the problem:
getResourceSettings().getStringResourceLoaders().add(0, new CustomResourceLoader());
getResourceSettings().getResourceFinders().add(new Path("/pathToResources"));
getResourceSettings().setResourcePollFrequency(Duration.seconds(1));
This inserts my CustomResourceLoader at the beginning of the list so all properties are first checked there.
The added Path tells the PropertiesFactory to look for resources in a given arbitrary directory outside of wicket.
I needed custom names for my resource files as well, I realized this in the CustomResourceLoader:
public String loadStringResource(final Class<?> clazz, final String key, final Locale locale, final String style, final String variation) {
final String myResourceFilename = createCustomResourceFileName(locale);
final IPropertiesFactory pF = Application.get().getResourceSettings().getPropertiesFactory();
final org.apache.wicket.resource.Properties props = pF.load(clazz, myResourceFilename);
...
}
When using the PropertiesFactory to load the files, it adds them to the internal IModificationWatcher automatically.
It turns out that part of the problem was, that the resource files are in a non-standard encoding. This can be fixed by adding a special IPropertyLoader to the PropertiesFactory in the settings:
((PropertiesFactory) getResourceSettings().getPropertiesFactory()).getPropertiesLoaders().add(0,
new UtfPropertiesFilePropertiesLoader("properties", "your-favorite-encoding"));
Related
I am building my first ever Wicket project and I find that the amount of properties files in my code base is growing rapidly. Ideally I would like to contain all internationalization in a single file for each language/region. Just so I can find things easily.
I found out that my application properties file could be ideal for this. My application properties file is called ApiAdminApplication.properties. Now I am trying to add my translatables to this file, without making a mess of things.
According to the javadoc of ComponentStringResourceLoader this should be possible. Apparently the lookup order is as follows:
page1.properties => form1.input1.Required
page1.properties => Required
form1.properties => input1.Required
form1.properties => Required
input1.properties => Required
myApplication.properties => page1.form1.input1.Required
myApplication.properties => Required
The second to last line contains the behavior I am looking for, but cannot get to work.
I have a page called CustomerEditPage which in turn contains a form with id customerForm
So here is what I am adding to ApiAdminApplication.properties, and what I think should work according to the snippet above:
CustomerEditPage.customerForm.name=Customer name
Sadly, this does not work. I can however get this to work by leaving out the page name, and starting with customerForm, but that is not what I want. I want per page internationalization contained in a single file.
Can anyone give me some pointers on this? Thanks.
I think the javadoc of ComponentStringResourceLoader is just wrong and should be fixed.
To accomplish what you need you will need to extend ClassStringResourceLoader and override getResourcePath(). In your impl you will have to prepend the result with the name of the page that owns the Component passed as a parameter.
Then you will need to register your loader at ApiAdminApplication#init() method with:
getResourceSettings().getStringResourceLoaders().add(new MyClassStringResourceLoader(ApiAdminApplication.class))
see the defaults.
Please file a bug report at https://issues.apache.org/jira/projects/WICKET/issues so that the javadoc issue is fixed (or someone else who knows better than me how to accomplish this can explain us).
After reporting the bug I ended up doing what martin-g suggested, and extended ClassStringResourceLoader. For your convenience, here is what I did:
public class PrefixedStringResourceLoader extends ClassStringResourceLoader {
public PrefixedStringResourceLoader(Class<?> clazz) {
super(clazz);
}
protected String getResourcePath(final Component component) {
final Class<? extends Page> parentClass = component.getPage().getClass();
final String resPath = super.getResourcePath(component);
if (!resPath.isEmpty())
return String.format("%s.%s", parentClass.getSimpleName(), resPath);
return parentClass.getSimpleName();
}
}
There is a small gotcha to this. It always requires you to work with complete resource paths. This can be a bit tricky, I had some problems with the snippet below:
<input type="submit" wicket:id="save" wicket:message="value:save" />
This evaluated to CustomerEditPage.customerForm.save.save, where I expected it to become: CustomerEditPage.customerForm.save. This is not the case because the wicket:message actually becomes a child of the save form input.
I ended up going for:
<input type="submit" wicket:id="save" wicket:message="value:caption" />
Which evaluates to CustomerEditPage.customerForm.save.caption, which I find somewhat more readable. Of course, you could roll your own more advanced resource loader, but this one is good enough for me.
I have a FHIR Device resource that contains a FHIR DeviceComponent resource. I use the following HAPI FHIR code to 'insert' one resource into the other:
protected static void insertResourceInResouce(BaseResource resource, BaseResource resourceToInsert)
{
ContainedDt containedDt = new ContainedDt();
ArrayList<IResource> resourceList = new ArrayList<IResource>();
resourceList.add(resourceToInsert);
containedDt.setContainedResources(resourceList);
resource.setContained(containedDt);
}
According to the Eclipse debugger the insertion works fine. This resource with its insertion is then added to a bundle. When all the work is done the Eclipse debugger shows the resource with the contained resource properly placed in the bundle. However, when generating a JSON string the contained resources are not there. The encoding operation appears as follows:
return fhirContext.newJsonParser().setPrettyPrint(true)
.encodeResourceToString(bundle);
Any ideas what I am doing wrong?
It turns out that one must reference the contained resource from the parent resource using the "#" to prefix the reference. If one does that then the contained resource will be present in the XML and JSON.
Admittedly this requirement makes no sense to me. Why would I include a resource INSIDE another scoping resource if I did not think it was important?
I'm currently using MvvmCross DownloadCache -- and it's working alright -- especially nice when I just need to drop in an Image URL and it automagically downloads / caches the image and serves up a UIImage.
I was hoping to leverage the code for one other use case -- which is I'd like to grab source images from URL's and cache the files on the local file system, but what I really want for this other use case is the image path on the local file system instead of the UIImage itself.
What would help me most if I could get an example of how I might accomplish that. Is it possible to make that happen in a PCL, or does it need to go into the platform specific code?
Thanks -- that works, but just in case anyone else is following along, I wanted to document how I got the Mvx.Resolve<IMvxFileDownloadCache>() to work. In my setup.cs (in the touch project), I had:
protected override void InitializeLastChance ()
{
Cirrious.MvvmCross.Plugins.DownloadCache.PluginLoader.Instance.EnsureLoaded();
Cirrious.MvvmCross.Plugins.File.PluginLoader.Instance.EnsureLoaded();
Cirrious.MvvmCross.Plugins.Json.PluginLoader.Instance.EnsureLoaded();
...
}
But that wasn't enough, because nothing actually registers IMvxFileDownloadCache inside the DownloadCache plugin (which I was expecting, but it's just not the case).
So then I tried adding this line here:
Mvx.LazyConstructAndRegisterSingleton<IMvxFileDownloadCache, MvxFileDownloadCache>();
But that failed because MvxFileDownloadCache constructor takes a few arguments. So I ended up with this:
protected override void InitializeLastChance ()
{
...
var configuration = MvxDownloadCacheConfiguration.Default;
var fileDownloadCache = new MvxFileDownloadCache(
configuration.CacheName,
configuration.CacheFolderPath,
configuration.MaxFiles,
configuration.MaxFileAge);
Mvx.RegisterSingleton<IMvxFileDownloadCache>(fileDownloadCache);
...
}
And the resolve works okay now.
Question:
I do wonder what happens if two MvxFileDownloadCache objects that are configured in exactly the same way will cause issues by stepping on each other. I could avoid that question by changing the cache name on the one I'm constructing by hand, but I do want it to be a single cache (the assets will be the same).
If you look at the source for the plugin, you'll find https://github.com/MvvmCross/MvvmCross/blob/3.2/Plugins/Cirrious/DownloadCache/Cirrious.MvvmCross.Plugins.DownloadCache/IMvxFileDownloadCache.cs - that will give you a local file path for a cached file:
public interface IMvxFileDownloadCache
{
void RequestLocalFilePath(string httpSource, Action<string> success, Action<Exception> error);
}
You can get hold of a service implementing this interface using Mvx.Resolve<IMvxFileDownloadCache>()
To then convert that into a system-wide file path, try NativePath in https://github.com/MvvmCross/MvvmCross/blob/3.2/Plugins/Cirrious/File/Cirrious.MvvmCross.Plugins.File/IMvxFileStore.cs#L27
We have a site which will be used for two different clients. During first request the user will be asked to choose a client. Based on that text,labels and site content should be displayed.
Is it possible to have two messages file in Play framework and during session startup the messages file would be decided
As of my research we can have more than a file for each Locale, the messages will be get based on locale in the request.
No, it is not supported at the moment.
You can easily do that either in a plugin(Look at MessagesPlugin ) or even using a bootstrap job with the #onApplicationStartup annotation
// From MessagesPlugin.java
//default languange messages
VirtualFile appDM = Play.getVirtualFile("conf/messages");
if(appDM != null && appDM.exists()) {
Messages.defaults.putAll(read(appDM));
}
static Properties read(VirtualFile vf) {
if (vf != null) {
return IO.readUtf8Properties(vf.inputstream());
}
return null;
}
You can wrote you own PlayPlugin and handle implement play.PlayPlugin.getMessage(String, Object, Object...). Then you could choose the right file. The class play.i18n.Messages can be used as inspiration how to implement the method.
Solved this problem with below solution,
Created a class MessagesPlugIn which extends play.i18n.MessagesPlugin
Created a class Messages as like play.i18n.Messages
Had a static Map messaagesByClientID in Messages.java
Overridden onApplicationStart() in MessagesPlugIn
Loaded the Properties in messaagesByClientID as locales loaded in play.i18n.MessagesPlugin
Had a method get() in Messages.java, retrieve the property from messaagesByClientID based ClientId in the session. If the property is not available call get() in play.i18n.Messages
7.Created a Custom tag il8nTag and its used in HTML templates. il8nTag will invoke the methos in Messages.get().
Create your own Module based on play.api.i18n.I18nModule, but bound to your own implementation of MessagesApi, based on Play's DefaultMessagesApi (here is the part defining the files to load)
Then in your application.conf, disable Play's play.api.i18n.I18nModule and enable your own module.
I need to get the absolute output path of the project's assembly via DTE. I tried doing this using this method, where I would access the OutputPath property, combining it with the assembly name, however this produces the relative path, such as:
..\..\Output\AnyCPU\Debug\MyAssembly.dll
Using Path.GetFullPath is not good for me, because my project might be executing from another location.
I noticed that the $(TargetPath) macro (in Build Events tab in project properties) contains the full path of the assembly. How can I access this value programmatically from the DTE?
Actual question is - how do I get the absolute output path of the project?
I don't know how to programmatically access the "$(TargetPath)", I agree that that could've been the best solution.
However, the approach you mentioned should still be workable,since the OutputPath property is relative to the folder in which the project file resides. (Please let me know if I'm missing some scenario where this is not the case?)
So you can do something similar to this:
private static string GetProjectExecutable(Project startupProject, Configuration config)
{
string projectFolder = Path.GetDirectoryName(startupProject.FileName);
string outputPath = (string)config.Properties.Item("OutputPath").Value;
string assemblyFileName = (string)startupProject.Properties.Item("AssemblyName").Value + ".exe";
return Path.Combine(new[] {
projectFolder,
outputPath,
assemblyFileName
});
}
(the overload of Path.Combine used here is only available in .NET 4.0 but you could always backport it)