/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.taglib.jsf_core;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.el.ELUtils;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.MessageUtils;
import com.sun.faces.util.ReflectionUtils;
import com.sun.faces.util.Util;
import com.sun.faces.util.RequestStateManager;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.webapp.UIComponentClassicTagBase;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>Tag action that loads the specified ResourceBundle as a Map into
* the request scope of the current {@link
* javax.faces.context.FacesContext}.</p>
* <p/>
* <p>The user is discouraged from using multiple dot syntax in their
* resource bundle keys. For example, for the bundle loaded under the
* var <code>msgs</code>, this key: <code>index.page.title</code> is
* discouraged. If your application requires this syntax for resource
* bundle keys, they may be referred to in the page with a syntax like
* this: <code>#{msgs["index.page.title"]}.</code></p>
*/
public class LoadBundleTag extends TagSupport {
private static final long serialVersionUID = -584139192758868254L;
static final String
PRE_VIEW_LOADBUNDLES_LIST_ATTR_NAME =
"com.sun.faces.taglib.jsf_core.PRE_VIEW_LOADBUNDLES_LIST";
private static final Logger LOGGER = FacesLogger.TAGLIB.getLogger();
// ------------------------------------------------------------- Attributes
private ValueExpression basenameExpression;
/**
* <p>Set the base name of the <code>ResourceBundle</code> to be
* loaded.</p>
* @param basename the ValueExpression which will resolve the basename
*/
public void setBasename(ValueExpression basename) {
this.basenameExpression = basename;
}
private String var;
/**
* <p>Set the name of the attribute in the request scope under which
* to store the <code>ResourceBundle</code> <code>Map</code>.</p>
* @param var the variable name to export the loaded ResourceBundle to
*/
public void setVar(String var) {
this.var = var;
}
// --------------------------------------------------------- Public Methods
/**
* <p>Load the <code>ResourceBundle</code> named by our
* <code>basename</code> property.</p> Wrap it in an immutable
* <code>Map</code> implementation and store the <code>Map</code> in
* the request attr set of under the key given by our
* <code>var</code> property.
*
* @throws JspException if a JSP error occurs
*/
@Override
public int doStartTag() throws JspException {
FacesContext context = FacesContext.getCurrentInstance();
// evaluate any VB expression that we were passed
String basename;
basename = (String)
ELUtils.evaluateValueExpression(basenameExpression,
context.getELContext());
if (null == basename) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "baseName");
throw new NullPointerException(message);
}
if (null == var) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "baseName");
throw new NullPointerException(message);
}
final ResourceBundle bundle =
ResourceBundle.getBundle(basename,
context.getViewRoot().getLocale(),
Util.getCurrentLoader(this));
if (null == bundle) {
throw new JspException("null ResourceBundle for " + basename);
}
Map toStore =
new Map() {
// this is an immutable Map
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
Iterator<Map.Entry<String,Object>> entries =
this.entrySet().iterator();
Map.Entry<String,Object> cur;
while (entries.hasNext()) {
cur = entries.next();
sb.append(cur.getKey()).append(": ").append(cur.getValue()).append('\n');
}
return sb.toString();
}
// Do not need to implement for immutable Map
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object key) {
boolean result = false;
if (null != key) {
result = (null != bundle.getObject(key.toString()));
}
return result;
}
@Override
public boolean containsValue(Object value) {
Enumeration<String> keys = bundle.getKeys();
boolean result = false;
while (keys.hasMoreElements()) {
Object curObj = bundle.getObject(keys.nextElement());
if ((curObj == value) ||
((null != curObj) && curObj.equals(value))) {
result = true;
break;
}
}
return result;
}
@Override
public Set<Map.Entry<String,Object>> entrySet() {
HashMap<String,Object> mappings = new HashMap<>();
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
Object value = bundle.getObject(key);
mappings.put(key, value);
}
return mappings.entrySet();
}
@Override
public boolean equals(Object obj) {
return !((obj == null) || !(obj instanceof Map))
&& entrySet().equals(((Map) obj).entrySet());
}
@Override
public Object get(Object key) {
if (null == key) {
return null;
}
try {
return bundle.getObject(key.toString());
} catch (MissingResourceException e) {
return "???" + key + "???";
}
}
@Override
public int hashCode() {
return bundle.hashCode();
}
@Override
public boolean isEmpty() {
Enumeration<String> keys = bundle.getKeys();
return !keys.hasMoreElements();
}
@Override
public Set keySet() {
Set<String> keySet = new HashSet<>();
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
keySet.add(keys.nextElement());
}
return keySet;
}
// Do not need to implement for immutable Map
@Override
public Object put(Object k, Object v) {
throw new UnsupportedOperationException();
}
// Do not need to implement for immutable Map
@Override
public void putAll(Map t) {
throw new UnsupportedOperationException();
}
// Do not need to implement for immutable Map
@Override
public Object remove(Object k) {
throw new UnsupportedOperationException();
}
@Override
public int size() {
int result = 0;
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
keys.nextElement();
result++;
}
return result;
}
@Override
public java.util.Collection values() {
ArrayList<Object> result = new ArrayList<>();
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
result.add(
bundle.getObject(keys.nextElement()));
}
return result;
}
};
ExternalContext extContext = context.getExternalContext();
extContext.getRequestMap().put(var, toStore);
if (WebConfiguration.getInstance(extContext)
.isOptionEnabled
(BooleanWebContextInitParameter.EnableLoadBundle11Compatibility)) {
// the UIComponent that wraps the Map
UIComponent bundleComponent =
createNewLoadBundleComponent(var, toStore);
UIComponentClassicTagBase parentTag = getParentUIComponentTag();
// Is this loadBundle tag instance outside of <f:view>?
if (null == parentTag) {
// Yes. Store the bundleComponent in a list so the <f:view> tag
// can add the list contents as the first children.
List<UIComponent> preViewBundleComponents =
getPreViewLoadBundleComponentList();
preViewBundleComponents.add(bundleComponent);
} else {
// No. Use addChild to add the bundeComponent to the tree.
addChildToParentTagAndParentComponent(bundleComponent, parentTag);
}
}
return (EVAL_BODY_INCLUDE);
}
static void addChildToParentTagAndParentComponent(UIComponent child,
UIComponentClassicTagBase parentTag) {
Method addChildToComponentAndTag;
if (null != (addChildToComponentAndTag =
ReflectionUtils.lookupMethod(UIComponentClassicTagBase.class,
"addChildToComponentAndTag",
UIComponent.class))) {
try {
addChildToComponentAndTag.setAccessible(true);
addChildToComponentAndTag.invoke(parentTag,
child);
}
catch (IllegalAccessException | IllegalArgumentException accessException) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING,
"Unable to add " + child + " to tree:", accessException);
}
}
catch (InvocationTargetException targetException) {
Throwable cause = targetException.getCause();
if (cause instanceof RuntimeException) {
throw ((RuntimeException) cause);
}
}
}
}
static List<UIComponent> getPreViewLoadBundleComponentList() {
FacesContext ctx = FacesContext.getCurrentInstance();
Map<String,Object> stateMap = RequestStateManager.getStateMap(ctx);
//noinspection unchecked
List<UIComponent> result = (List<UIComponent>)
stateMap.get(PRE_VIEW_LOADBUNDLES_LIST_ATTR_NAME);
if (result == null) {
result = new ArrayList<>();
stateMap.put(PRE_VIEW_LOADBUNDLES_LIST_ATTR_NAME, result);
}
return result;
}
private UIComponent createNewLoadBundleComponent(String var,
Map toStore) {
UIComponent result = new LoadBundleComponent(var, toStore);
result.setTransient(true);
return result;
}
/**
* @return the <code>UIComponentClassicTagBase</code> instance
* that represents the tag in the page to which the special
* component should be added as a child
*/
private UIComponentClassicTagBase getParentUIComponentTag() {
Tag parent = this.getParent();
while (null != parent &&
(!(parent instanceof UIComponentClassicTagBase))) {
parent = this.getParent();
}
UIComponentClassicTagBase result = (UIComponentClassicTagBase) parent;
// Check for case where the <f:loadBundle> is inside of an included page,
// but outside of the <f:subview> for that page. This can happen
// either when the <f:subview> is in the includING page *OR* when
// the <f:subview> is in the includED page, yet the <f:loadBundle> is
// outside of the <f:subview> in the includED page.
Stack<UIComponentClassicTagBase> viewTagStack = SubviewTag.getViewTagStack();
if (!viewTagStack.empty()) {
result = viewTagStack.peek();
}
return result;
}
/**
* <p>Release references to any acquired resources.
*/
@Override
public void release() {
this.basenameExpression = null;
this.var = null;
}
private static class LoadBundleComponent extends UIComponentBase {
private String var;
private Map toStore;
public LoadBundleComponent(String var, Map toStore) {
this.var = var;
this.toStore = toStore;
}
@Override
public String getFamily() {
return null;
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
requestMap.put(var, toStore);
}
@Override
public void encodeEnd(FacesContext context) throws IOException {
}
@Override
public void encodeChildren(FacesContext context) throws IOException {
}
@Override
public String toString() {
return "LoadBundleComponent: var: " + var + " keys: " +
toStore.toString();
}
}
}