Refering to a specific page in Wicket i18n properties file - internationalization

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.

Related

How do I use the appProperties with the ruby api-client

I can't determine how to add custom properties or search for them.
Everything I have tried is giving me a Error - #<Google::Apis::ClientError: invalid: Invalid query> when I attempt to search for them. I can successfully complete other queries but I don't know if the client is setup to work with appProperties (or even properties at all).
Basically I just need the correct syntax for searching and adding since it doesn't appear to be in the documentation.
Assuming you already have a reference to an authorized DriveService, you can search based on appProperties using a q-parameter (documented here), like this:
file_list = drive.list_files(
q: "appProperties has { key='my_app_key' and value='my_val' }",
fields: 'files(id, name, appProperties)',
spaces: 'drive')
If you omit the fields parameter then the search will still work but the properties themselves won't be returned.
Updating appProperties is definitely arcane and the documentation is opaque. What you need is the ID of the file, and a File value object as a container for the attributes to update. Something like this:
new_app_properties = { 'my_app_key' => 'my_val' }
update_f = Google::Apis::DriveV3::File.new(app_properties: new_app_properties)
drive.update_file(file_id, update_f)

AX2012 - Pre-Processed RecId parameter not found

I made a custom report in AX2012, to replace the WHS Shipping pick list. The custom report is RDP based. I have no trouble running it directly (with the parameters dialog), but when I try to use the controller (WHSPickListShippingController), I get an error saying "Pre-Processed RecId not found. Cannot process report. Indicates a development error."
The error is because in the class SrsReportProviderQueryBuilder (setArgs method), the map variable reportProviderParameters is empty. I have no idea why that is. The code in my Data provider runs okay. Here is my code for running the report :
WHSWorkId id = 'LAM-000052';
WHSPickListShippingController controller;
Args args;
WHSShipmentTable whsShipmentTable;
WHSWorkTable whsWorkTable;
clWHSPickListShippingContract contract; //My custom RDP Contract
whsShipmentTable = WHSShipmentTable::find(whsWorkTable.ShipmentId);
args = new Args(ssrsReportStr(WHSPickListShipping, Report));
args.record(whsShipmentTable);
args.parm(whsShipmentTable.LoadId);
contract = new clWHSPickListShippingContract();
controller = new WHSPickListShippingController();
controller.parmReportName(ssrsReportStr(WHSPickListShipping, Report));
controller.parmShowDialog(false);
controller.parmLoadFromSysLastValue(false);
controller.parmReportContract().parmRdpContract(contract);
controller.parmReportContract().parmRdpName(classStr(clWHSPickListShippingDP));
controller.parmReportContract().parmRdlContract().parmLanguageId(CompanyInfo::languageId());
controller.parmArgs(args);
controller.startOperation();
I don't know if I'm clear enough... But I've been looking for a fix for hours without success, so I thought I'd ask here. Is there a reason why this variable (which comes from the method parameter AifQueryBuilderArgs) would be empty?
I'm thinking your issue is with these lines (try removing):
controller.parmReportContract().parmRdpContract(contract);
controller.parmReportContract().parmRdpName(classStr(clWHSPickListShippingDP));
controller.parmReportContract().parmRdlContract().parmLanguageId(CompanyInfo::languageId());
The style I'd expect to see with your contract would be like this:
controller = new WHSPickListShippingController();
contract = controller.getDataContractObject();
contract.parmWhatever('ParametersHere');
controller.parmArgs(args);
And for the DataProvider clWHSPickListShippingDP, usually if a report is using a DataProvider, you don't manually set it, but the DP extends SRSReportDataProviderBase and has an attribute SRSReportParameterAttribute(...) decorating the class declaration in this style:
[SRSReportParameterAttribute(classstr(MyCustomContract))]
class MyCustomDP extends SRSReportDataProviderBase
{
// Vars
}
You are using controller.parmReportContract().parmRdpContract(contract); wrong, as this is more for run-time modifications. It's typically used for accessing the contract for preRunModifyContract overloads.
Build your CrossReference in a development environment then right click on \Classes\SrsReportDataContract\parmRdpContract and click Add-Ins>Cross-reference>Used By to see how that is generally used.
Ok, so now I feel very stupid for spending so much time on that error, when it's such a tiny thing...
The erronous line is that one :
controller.parmReportName(ssrsReportStr(WHSPickListShipping, Report));
Because WHSPickListShipping is the name of the AX report, but I renamed my custom report clWHSPickListShipping. What confused me was that my DataProvider class was executing as wanted.

Getting the filename/path from MvvmCross Plugins.DownloadCache

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

Watch for updated properties in Wicket

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"));

Spring MVC Portlets: external pagination with displaytag needs to go to the action phase

I'm using Spring MVC portlets I need to implement one display tag with external pagination. In order to do this, I've defined my table in the JSP like this:
<portlet:actionURL var="viewListURL">
<portlet:param name='action' value='${ServletContextKeys.MY_ACTION_METHOD}'/>
</portlet:actionURL>
<display:table name="${whateverList}"
requestURI="${viewListURL}"
class="displayTagTable"
export="true"
uid="item"
pagesize="10"
partialList="true"
sort="external"
defaultsort="1"
size="${ServletContextKeys.SC_LIST_SIZE}">
...
The problem is that, when I click any button to paginate, the displaytag redirects me to the render phase instead the action phase as I want to. What am I doing wrong? Any ideas..?
Thanks a lot
EDIT: I can see in the URL that the parameter p_p_url_type=0 (render phase). it makes no sense to me, as I'm calling an action url, but maybe would be enough just change this parameter to p_p_url_type=1. But, I'm just don't know how... Any ideas?
http://localhost:8080/wsdes/user/sifo3/home?p_p_id=SifoIIIweb_WAR_sifo3economicoweb_INSTANCE_s8jH&p_p_lifecycle=1&p_p_url_type=0&p_p_state=maximized&p_p_mode=view&_SifoIIIweb_WAR_sifo3economicoweb_INSTANCE_s8jH_action=consultaJustificantes&_SifoIIIweb_WAR_sifo3economicoweb_INSTANCE_s8jH_implicitModel=true&_SifoIIIweb_WAR_sifo3economicoweb_INSTANCE_s8jH_d-49489-p=2
Been there before. I solved the problem in a different way, but while looking in DisplayTag source code I found some interesting things. For example, in PortletHref you can find this in the addParameter method:
if (PARAM_TYPE.equals(name))
{
if (TYPE_RENDER.equals(value))
{
this.setAction(false);
}
else if (TYPE_ACTION.equals(value))
{
this.setAction(true);
}
And also:
private static final String PARAM_PREFIX = "portlet:";
public static final String PARAM_TYPE = PARAM_PREFIX + "type";
public static final String TYPE_ACTION = "action";
Apparently, if you need a parameter named portlet:type with value action to make DisplayTag generate an Action URL. I haven't tested myself, so let me know if it works.
I still don't know the reason, but I fixed this issue changing the display tag for Portlets (displaytag-portlet.jar), to the standard displaytag, and deleting from the displaytag.properties file the factory.requestHelper property:
factory.requestHelper=org.displaytag.portlet.PortletRequestHelperFactory
Using the normal displaytag library, instead of the portlet one, fixed my problems.

Resources