How to put JSF message bundle outside of WAR so it can be edited without redeployment? - jsf

We have a JSF application on WildFly 8 which uses the traditionally mechanism with internationalizing text by having message bundles for German and English in the WEB-INF\classes folder of the WAR and a configuration in faces-config.xml mapping a name to it and listing the locales. The application does not have a database connection, but uses REST services to communicate with a 2nd application.
Now we need to be able to change text more easily, meaning not having to build a new WAR file and do a deployment when changing a text. So I need a mechanism to have the message bundles outside of the WAR while being able to use it as before within the XHTML pages.
Two optional requirements would be to change the text and refresh the messages in the application without having to restart the application (priority 2), and to have a default bundle within the WAR, which is overwritten by the external bundle (priority 3).
My thought was to use something like Apache commons configuration to read a property file within an Application scoped bean and expose a getter under the EL name used before. But somehow it feels like having to re-implement an existing mechanism and that this should somehow be easier, maybe even with Java EE core only.
Has someone used this mechanism in such a way and can point me to some example/description on the details or has a better idea to implement the listed requirement(s)?

How to put JSF message bundle outside of WAR?
Two ways:
Add its path to the runtime classpath of the server.
Create a custom ResourceBundle implementation with a Control.
change the text and refresh the messages in the application without having to restart the application
Changing the text will be trivial. However, refreshing is not trivial. Mojarra internally caches it agressively. This has to be taken into account in case you want to go for way 1. Arjan Tijms has posted a Mojarra specific trick to clear its internal resource bundle cache in this related question: How to reload resource bundle in web application?
If changing the text happens in the webapp itself, then you could simply perform the cache cleanup in the save method. If changing the text however can happen externally, then you'd need to register a file system watch service to listen on changes (tutorial here) and then either for way 1 clear the bundle cache, or for way 2 reload internally in handleGetObject().
have a default bundle within the WAR, which is overwritten by the external bundle
When loading them from classpath, the default behavior is the other way round (resources in WAR have higher classloading precedence), so this definitely scratches way 1 and leaves us with way 2.
Below is a kickoff example of way 2. This assumes that you're using property resource bundles with a base name of text (i.e. no package) and that the external path is located in /var/webapp/i18n.
public class YourBundle extends ResourceBundle {
protected static final Path EXTERNAL_PATH = Paths.get("/var/webapp/i18n");
protected static final String BASE_NAME = "text";
protected static final Control CONTROL = new YourControl();
private static final WatchKey watcher;
static {
try {
watcher = EXTERNAL_PATH.register(FileSystems.getDefault().newWatchService(), StandardWatchEventKinds.ENTRY_MODIFY);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
private Path externalResource;
private Properties properties;
public YourBundle() {
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
setParent(ResourceBundle.getBundle(BASE_NAME, locale, CONTROL));
}
private YourBundle(Path externalResource, Properties properties) {
this.externalResource = externalResource;
this.properties = properties;
}
#Override
protected Object handleGetObject(String key) {
if (properties != null) {
if (!watcher.pollEvents().isEmpty()) { // TODO: this is naive, you'd better check resource name if you've multiple files in the folder and keep track of others.
synchronized(properties) {
try (InputStream input = new FileInputStream(externalResource.toFile())) {
properties.load(input);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
return properties.get(key);
}
return parent.getObject(key);
}
#Override
#SuppressWarnings({ "rawtypes", "unchecked" })
public Enumeration<String> getKeys() {
if (properties != null) {
Set keys = properties.keySet();
return Collections.enumeration(keys);
}
return parent.getKeys();
}
protected static class YourControl extends Control {
#Override
public ResourceBundle newBundle
(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException
{
String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
Path externalResource = EXTERNAL_PATH.resolve(resourceName);
Properties properties = new Properties();
try (InputStream input = loader.getResourceAsStream(resourceName)) {
properties.load(input); // Default (internal) bundle.
}
try (InputStream input = new FileInputStream(externalResource.toFile())) {
properties.load(input); // External bundle (will overwrite same keys).
}
return new YourBundle(externalResource, properties);
}
}
}
In order to get it to run, register as below in faces-config.xml.
<application>
<resource-bundle>
<base-name>com.example.YourBundle</base-name>
<var>i18n</var>
</resource-bundle>
</application>

Related

Cannot rebind JHipsterProperties properties with Spring Cloud ContextRefresher

I have a piece of code to do automatic configuration properties reloading in my JHipster application.
A lot of #ConfigurationProperties annotated beans are well refreshed except JHipsterProperties bean that is not.
The JHipsterProperties bean is not known by Sring Cloud Context's ConfigurationPropertiesBeans and I cannot understand why.
A solution is to add the missing #ConfigurationProperties beans to ConfigurationPropertiesBeans but this is just an ugly workaround.
public void triggerReload() throws IOException {
LOG.info("Reloading configuration");
try {
this.contextRefresher.refresh();
} catch (final BeanCreationException e) {
// DO SOMETHING USEFUL
}
LOG.info(this.jHipsterProperties.getMail().getFrom()); // <--- value is not updated
LOG.info(this.mailProperties.getHost()); // <--- value is updated
}

Packaging Facelets (JSF 2.2) files in a JAR - Deployed as JBoss Modul (seperate deployment)

My Question is near the same than Packaging Facelets files (templates, includes, composites) in a JAR. Wich was full quallyfied awnsered by BalusC.
But I'll go a bit farther. Since JBoass AS 7 the ClassLoading in JB has changed. Now there is a strict Modul-ClassLoading and it works so far ;)
I have a WAR-file with an jboss-deployment-structure.xml plus one JAR-file with some CDI-Beans and xhtml-files in /META-INF/resources/modul.
I deploy the two "projects" seperately (so the JAR is not packaged into the WAR both ar copyed in JBOSS_HOME/standalone/deployment).
The WAR references the JAR as a Modul through the jboss-deployment-structure.xml.
I'm able to Inject CDI-Beans from the JAR but I can't reach the xhtml-files in /META-INF/resources/modul/. Everytime I try to load CONTEXT_ROOT/modul/modul.xhtml I get a warning from JB :
[javax.enterprise.resource.webcontainer.jsf.application] (default task-5) JSF1064: Ressource /modul/modul.xhtml can not be found or served.
If I package the JAR into the WAR (WEB-INF/lib) it dosen't work ither.
I provided a faces-config.xml under META-INF/resources
Did I miss somethink?
reggards
I use WildFly 9.0.1 Final and the Provided J2EE implementations.
I found a solution. The problem is, that JBoss doesn't scan the JAR if it is not packaged in WEB-INF/lib.
In my initial question I have wrote "If I package the JAR into the WAR (WEB-INF/lib) it doesn't work ither." thats not true i just didn't recognise that i placed my files in /META-INF/modul instead of /META-INF/resources/modul. I moved the files and it start to work if i placed the JAR in WEB-INF/lib.
But the problem with an separate deployment still persisted.
Now I use an custom ResourceHandler (JSF 2.2 way) and it work like a charm.
All my moduls have to implement a interface. In that way I'm able to Inject all these instances by CDI. Now i itterate over all moduls and look for my resource. The ResourceHandler is placed in the WAR not in the single JAR's. In that way i just have to implement it onetimes.
Here is my code example:
public class ExternalResourceHandler extends ResourceHandler {
#Inject
#Any
Instance<ModulDescriptor> moduls;
private ResourceHandler parent;
private String basePath = "META-INF/resources";
public ExternalResourceHandler() {
}
public ExternalResourceHandler(ResourceHandler parent) {
this.parent = parent;
}
#Override
public ViewResource createViewResource(FacesContext context, String resourceName) {
System.out.println("Invoked for: " + resourceName);
ViewResource resource = parent.createViewResource(context, resourceName);
if (resource == null) {
System.out.println("Parent failed");
if (moduls != null) {
for (ModulDescriptor mod : moduls) {
URL url = mod.getClass().getClassLoader().getResource(basePath + resourceName);
if (url != null) {
return new ViewResource() {
#Override
public URL getURL() {
return url;
}
};
}
}
} else {
System.out.println("INJECTION IS NULL");
}
}
return resource;
}
//followed by all other overriden methods wich return null ;)
}
Thanks to #BalusC for this awnser wich does the clue in the second step.

Using liferay dockbar notifications

I’d like to use the liferay notification feature following the tutorial http://www.codeyouneed.com/liferay-custom-notifications/. And as many people before, I succeeded in increasing the number of notifications, but the notification message is not displayed.
I tried to check by adding log-output whether the methods (getBody, getLink, …) of the UserNotificationHandler are called, and they are not called at all, not even the constructor of the UserNotificationHandler is called.
So I conclude that my notification is written to the database, but my UserNotificationHandler class is not found.
In my project, I have put the
user-notification-definitions into
project/src/main/resources.
They look like:
<?xml version="1.0"?>
<!DOCTYPE user-notification-definitions PUBLIC "-//Liferay//DTD User Notification Definitions 6.2.0//EN" "http://www.liferay.com/dtd/liferay-user-notification-definitions_6_2_0.dtd">
<user-notification-definitions>
<definition>
<notification-type>${com.myproject.portal.notifications.UserNotificationHandler.PORTLET_ID}</notification-type>
<description>receive-a-notification-when-triggered</description>
<delivery-type>
<name>email</name>
<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_EMAIL}</type>
<default>true</default>
<modifiable>true</modifiable>
</delivery-type>
<delivery-type>
<name>website</name>
<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_WEBSITE}</type>
<default>true</default>
<modifiable>true</modifiable>
</delivery-type>
</definition>
</user-notification-definitions>
The liferay-portlet.xml is in
project/src/main/webapp/WEB-INF.
And the UserNotificationHandler in
project/src/main/java/com/myproject/portal/notifications
in the package com.myproject.portal.notifications.
I wrote something like that into the liferay-portlet.xml:
<portlet-name>example</portlet-name>
<icon>/icon.png</icon>
<user-notification-definitions>
user-notification-definitions.xml
</user-notification-definitions>
<user-notification-handler-class>
com.myproject.portal.notifications.UserNotificationHandler
</user-notification-handler-class>
</portlet>
This is my UserNotificationHandlerClass (so far, I am just trying to get it work before adding the actual content):
package com.myproject.portal.notifications;
import ...//all necessary imports
public class UserNotificationHandler extends
BaseUserNotificationHandler {
public static final String PORTLET_ID = "example_WAR_myprojectportlet";
private static final Logger log = Logger.getLogger(UserNotificationHandler.class);
public UserNotificationHandler() {
log.info("UserNotificationHandler - Constructor");
setPortletId(UserNotificationHandler.PORTLET_ID);
}
#Override
protected String getBody(UserNotificationEvent userNotificationEvent,
ServiceContext serviceContext) throws Exception {
log.info("in getBody");
return "";
}
#Override
protected String getLink(UserNotificationEvent userNotificationEvent,
ServiceContext serviceContext) throws Exception {
log.info("in getLink");
return "";
}
protected String getBodyTemplate() throws Exception {
log.info("in getBodyTemplate");
return "";
}
}
I trigger the notification in my portlet like this:
ServiceContext serviceContext = ServiceContextFactory.getInstance(request);
JSONObject payloadJSON = JSONFactoryUtil.createJSONObject();
payloadJSON.put("userId", userId);
payloadJSON.put("yourCustomEntityId", 12345);
payloadJSON.put("additionalData", "success");
UserNotificationEventLocalServiceUtil.addUserNotificationEvent(userId,
UserNotificationHandler.PORTLET_ID,
(new Date()).getTime(),
userId,
payloadJSON.toString(),
false,
serviceContext);
What is the problem here?
Do you literally have public static final String PORTLET_ID = "myportlet"; in your code? If so, note the extra information in the tutorial that you link:
NB Important Information: The com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID string that you use as your notification type has to match an actual portlet ID. It doesn’t actually need to be YOUR portlet ID but that would be the right thing to have there. The reason being that Notifications display portlet uses it to display a small portlet icon next to your notification to help the user identify the source of the notification. Providing a bad Portlet ID or something like null leads to a hard-to-trace NullPointerException in the JSP. Took me an hour to track it down.
Most likely the portlet ID looks rather like "example_WAR_myportlet", this indicates that it's deployed in a plugin named example.war and the portlet id (in portlet.xml) is myportlet. Try if it works then - Liferay might need to find the portlet in order to find, instanciate and use its NotificationHandler. (Note: This is currently a guess - I didn't try the full code posted)
In your liferay-portlet.xml you wrote
<user-notification-handler-class>
UserNotificationHandler
</user-notification-handler-class>
It should be:
<user-notification-handler-class>
com.myproject.portal.notifications.UserNotificationHandler
</user-notification-handler-class>
You should also check if this part is good
<user-notification-definitions>
user-notification-definitions.xml
</user-notification-definitions>
"user-notification-definitions.xml" file should be on WEB-INF/classes in the final WAR

Using property files in Web Applications [duplicate]

This question already has answers here:
How to get properties file from /WEB-INF folder in JSF?
(3 answers)
Closed 7 years ago.
I'm developing a web application(this is my first time) and pretty confused about using property files. I don't know where to put the property files.
The problem is that i have put it under the WEB_INF folder. And when i test run it as a Java Application to check whether Database connections are working according to the properties in the property file it is working without any problem.
But when i run it in a Server as a Web Application it fails to load the properties file saying it could not find the file in the path specified. I tried using every possible path i could give and changing the file directories within the whole project. But I kept getting the same error.
Then i changed my class again from scratch thinking there's some kind of a bug withing my code where i load the properties file. And it seems that it could not find the file either when deployed as a Web App. But my test application works fine. Where do i put this file and how do i use it. I have read #BalusC's answer in this thread https://stackoverflow.com/a/2161583/2999358 but i have no idea why this happens. Can someone help me on this?
I'm using Tomcat 8, Eclipse IDE and building on JSF framework.
Class where i load my properties file
public class ConfigCache {
private static final File FILE = new File("./WebContent/WEB-INF/conf/config.properties");
private static final Properties PROPERTIES = new Properties();
public static final String JDBC_DRIVER = ConfigCache.getProperty("db.driverName");
public static final String DATABASE_URL = ConfigCache.getProperty("db.url");
public static final String DATABASE_USERNAME = ConfigCache.getProperty("db.user");
public static final String DATABASE_PASSWORD = ConfigCache.getProperty("db.pass");
public ConfigCache() {
}
public static String getProperty(String key) {
if (PROPERTIES.isEmpty()) {
loadProperties();
}
Object value;
return (value = PROPERTIES.get(key)) == null ? "" : value.toString();
}
private static void loadProperties() {
if (!FILE.exists()) {
throw new IllegalArgumentException("The 'config.properties' has not been found.");
}
try {
FileInputStream fis = null;
try {
fis = new FileInputStream(FILE);
PROPERTIES.load(fis);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException exp) {
System.out.println("IOException #" + ConfigCache.class + " # loadProperties() : " + exp);
}
}
} catch (Exception exp) {
System.out.println("Exception #" + ConfigCache.class + " # loadProperties() : " + exp);
}
}
}
Folder Structure
Try With this.
put the property in src folder.
Your file is in the WEB-INF directory. This means it's part of the war and reachable as part of the class path. That's perfectly ok, since it makes it portable and independant of the web container installation (e.g. Tomcat).
You can load any file in the class path as a resource:
getClass().getResourceAsStream("/conf/config.properties")
This means you can write your code like this:
private static void loadProperties() {
InputStream is = getClass().getResourceAsStream("/conf/config.properties");
PROPERTIES.load(fis);
}
(Error handling omitted)
You can explode (unzip) your war/ear file and see the contents or folder structure of it and find why your code doesnt work. The reason is that the folder WebContent doesnt exist in your ear/war , but does exist only when run via eclipse. This is the reason why its always better to follow the solution provided in the link posted so that you can retrieve the porperty files from classpath. The below code fetches your property file in eclipse but not in the server.
private static final File FILE = new File("./WebContent/WEB-INF/conf/config.properties");
Contents of WAR file (from JournelDev), it contains WEB-INF directory but there would be no WebContent directory above it

JSF navigating to a different view programatically

My application has a save and retrieve function. I have the save/retrieve working in that the objects are saved to a database and retrieved correctly. However, in my retrieve landing page, depending on the state of the saved application, I either want to validate some details with the user, or silently navigate to the last accessed view. The latter is where I'm having trouble.
We're using spring beans and in my SaveAndRetrieve page bean I have:
#PostConstruct
public void initialise() {
caseNotFound = false;
caseReference = saveAndRetrieveActionHandler.getRequestedCaseReference();
LOGGER.debug("Retrieve initialise. Case ref is {}", caseReference);
if (caseReference != null) {
try {
saveAndRetrieveActionHandler.retrieveApplicationByCaseRef();
LOGGER.debug("Retrieve initialise - case found");
final NavigationOutcome outcome = saveAndRetrieveActionHandler.getLastAccessedView();
if (outcome.getApplicationState() == ApplicationState.QUOTE) {
LOGGER.info("Quote retrieved, navigating to view");
// HERE IS WHERE THE TROUBLE LIES! THIS DOESNT WORK
FacesUtils.setNextViewNavigation(outcome.getViewId());
}
} catch (final FrameworkException fe) {
LOGGER.debug("Exception caught {}", fe);
caseNotFound = true;
}
}
}
outcome is an enumeration containing amongst other things the view I need to navigate to, and the application state (another enumeration). If applicationState is quote, I want to silently navigate. For all other applicationStates I want to challenge the user to verify them.
My facesUtils method is:
public static void setNextViewNavigation(final String p_lastAccessedViewId) {
if (p_lastAccessedViewId != null) {
getCurrentViewRoot().setViewId(p_lastAccessedViewId);
}
}
I've also tried calling this method
public static void navigateToOutcome(final String p_outcome) {
final FacesContext context = getFacesContext();
final NavigationHandler navigationHandler = context.getApplication().getNavigationHandler();
navigationHandler.handleNavigation(context, null, p_outcome);
}
Despite my efforts, I'm seeing the landing page wheras I want to silently navigate to the saved page
Basically I want to abort the current lifecycle and reset the viewroot to the saved view. (note I am not saving the component tree itself, just my business objects)
One more piece of information, this is jsf1.2, but with facelets. I cannot use any jsf2 specific functionality, nor can I use any third party JSF extenstions.
Help please!
We solved this by using a ui:include tag with the src attribute being a jsf method that determines the name of the page to navigate to.

Resources