com.sun.webui.jsf.util.RenderingUtilities Maven / Gradle / Ivy
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* https://woodstock.dev.java.net/public/CDDLv1.0.html.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at https://woodstock.dev.java.net/public/CDDLv1.0.html.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* you own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.webui.jsf.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import com.sun.webui.html.HTMLAttributes;
import com.sun.webui.html.HTMLElements;
import com.sun.webui.jsf.component.Body;
import com.sun.webui.jsf.component.ComplexComponent;
import com.sun.webui.jsf.component.Icon;
import com.sun.webui.jsf.theme.ThemeImages;
import com.sun.webui.jsf.theme.ThemeStyles;
import com.sun.webui.jsf.util.FocusManager;
import com.sun.webui.jsf.util.LogUtil;
import com.sun.webui.theme.Theme;
/**
* This class provides common methods for renderers.
*/
public class RenderingUtilities {
/** Creates a new instance of RenderingUtilities. */
public RenderingUtilities() {
}
/**
* Render a component.
* @param component The component to render
* @param context The FacesContext of the request
*
*/
public static void renderComponent(UIComponent component,
FacesContext context) throws IOException {
if (!component.isRendered()) {
return;
}
// this is a workaround a jsf bug in tables where it caches the
// client id. We are forcing the caching not to happen.
// this could be a potential source of performance issues if
// it turns out the jsf folks really wanted this
String id = component.getId();
if (id != null) {
component.setId(id);
}
component.encodeBegin(context);
if (component.getRendersChildren()) {
component.encodeChildren(context);
} else {
Iterator kids = component.getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
renderComponent(kid, context);
}
}
component.encodeEnd(context);
}
/**
* This method goes through an array of possible attribute names,
* evaluates if they have been set on the component, and writes
* them out using the specified writer.
* @param component The component being rendered
* @param writer The writer to use to write the attributes
* @param possibleAttributes String attributes that are treated as
* passthrough for this component
*/
public static void writeStringAttributes(UIComponent component,
ResponseWriter writer, String[] possibleAttributes) throws IOException {
// Get the rest of the component attributes and display them
Map attributes = component.getAttributes();
int numNames = possibleAttributes.length;
String attributeName = null;
Object attributeValue;
for (int counter = 0; counter < numNames; counter++) {
attributeName = possibleAttributes[counter];
attributeValue = attributes.get(attributeName);
if (attributeValue != null) {
writer.writeAttribute(attributeName.toLowerCase(),
String.valueOf(attributeValue),
attributeName);
}
}
}
/**
* Add any attributes on the specified list directly to the specified
* ResponseWriter for which the specified UIComponent has a non-null String
* value. This method may be used to "pass through" commonly used attribute
* name/value pairs with a minimum of code. Attribute names are converted to
* lower case in the rendered output. Any name/value pairs in the extraHtml
* String shall take precedence over attribute values.
*
* @param context FacesContext for the current request.
* @param component EditableValueHolder component whose submitted value is
* to be stored.
* @param writer ResponseWriter to which the element start should be rendered.
* @param names List of attribute names to be passed through.
* @param extraHtml Extra name/value pairs to be rendered.
*
* @exception IOException if an input/output error occurs
*/
public static void writeStringAttributes(UIComponent component,
ResponseWriter writer, String names[], String extraHtml)
throws IOException {
if (component == null || names == null) {
return;
}
Map attributes = component.getAttributes();
Object value;
for (int i = 0; i < names.length; i++) {
// Special case for names matching "valign" instead of "align".
if (extraHtml == null
|| extraHtml.indexOf(names[i] + "=") != 0
&& extraHtml.indexOf(" " + names[i] + "=") == -1) {
value = attributes.get(names[i]);
if (value != null) {
if (value instanceof String) {
writer.writeAttribute(names[i].toLowerCase(),
(String) value, names[i]);
} else {
writer.writeAttribute(names[i].toLowerCase(),
value.toString(), names[i]);
}
}
}
}
// Render extra HTML attributes.
renderExtraHtmlAttributes(writer, extraHtml);
}
/**
* This method will output a hidden field for use with Params and components
* that need to submit a value through a hidden field.
* Note: The name of the hidden field will be written as is. For Params
* no encoding inside the form is done. This is intentional.
* @param writer The writer to use to write the attributes
* @param id The identifier of the hidden field.
* passthrough for this component
*/
public static void renderHiddenField(UIComponent component,
ResponseWriter writer, String id, String value) throws IOException {
if (id == null) {
// TODO: when we figure out our exception string strategy, fix this
throw new IllegalArgumentException(
"An f:param tag had a null name attribute");
}
writer.startElement("input", component); //NOI18N
writer.writeAttribute("id", id, null); //NOI18N
writer.writeAttribute("name", id, null); //NOI18N
if (value != null) {
writer.writeAttribute("value", value, null); //NOI18N
}
writer.writeAttribute("type", "hidden", null); //NOI18N
writer.endElement("input"); //NOI18N
}
/**
* Decode a hidden field.
* @param context The FacesContext associated with this request
* @param parameterMapId Identifies the value in the parameters map
*/
public static String decodeHiddenField(FacesContext context,
String parameterMapId) {
Map params = context.getExternalContext().getRequestParameterMap();
Object valueObject = params.get(parameterMapId);
return (String)valueObject;
}
/**
* Return a space-separated list of CSS style classes to render for
* this component, or null
for none.
*
* @param component UIComponent
for which to calculate classes
* @param styles Additional styles specified by the renderer
*/
public static String getStyleClasses(FacesContext context,
UIComponent component, String styles) {
String styleClass = (String)
component.getAttributes().get("styleClass");
boolean componentNotVisible = !isVisible(component);
if (componentNotVisible) {
String hiddenStyleClass = ThemeUtilities.getTheme(context)
.getStyleClass(ThemeStyles.HIDDEN);
if (styleClass != null) {
styleClass += " " + hiddenStyleClass;
} else {
styleClass = hiddenStyleClass;
}
}
if (styleClass != null) {
if (styles != null) {
return styleClass + " " + styles;
} else {
return styleClass;
}
} else {
if (styles != null) {
return styles;
} else {
return null;
}
}
}
/**
*
*/
public static void renderStyleClass(FacesContext context,
ResponseWriter writer, UIComponent component, String extraStyles)
throws IOException {
String classes = getStyleClasses(context, component, extraStyles);
if (classes != null) {
writer.writeAttribute("class", classes, "styleClass");
}
}
/**
* Helper method to render style classes when name/value pairs are given
* via an extraHtml String. This method will append the given style to the
* class name/value pair found in the extraHtml String. The class name/value
* is removed from the returned extraHtml String so that developers may
* invoke the writeStringAttributes method without rendering the style
* class, again.
*
* @param context FacesContext for the current request.
* @param component The UIComponent component to be rendered.
* @param writer ResponseWriter to which the element start should be rendered.
* @param style The style to append to the component's styleClass property.
* @param extraHtml Extra name/value pairs to be rendered.
*/
public static String renderStyleClass(FacesContext context,
ResponseWriter writer, UIComponent component, String styleClass,
String extraHtml) throws IOException {
if (styleClass != null) {
int first = -1;
if (extraHtml != null
&& (first = extraHtml.indexOf("class=")) != -1) {
try {
// Concat given class value with styleClass attribute.
int quote = first + 6; // Quote char index.
char ch = extraHtml.charAt(quote); // Get quote char.
int last = extraHtml.indexOf(ch, quote + 1); // Last index.
String s = extraHtml.substring(first, last + 1); // Get name/value pair
extraHtml = extraHtml.replaceAll(s, ""); // Remove substring.
s = s.substring(7, s.length() - 1); // Remove quote chars.
styleClass = s + " " + styleClass; // Append styleClass.
} catch (IndexOutOfBoundsException e) {}
}
renderStyleClass(context, writer, component, styleClass);
}
return extraHtml;
}
/**
*
*/
public static boolean isPortlet(FacesContext context) {
if (context.getExternalContext().getContext()
instanceof ServletContext) {
return false;
}
return true;
}
/**
* Get the client ID of the last component to have focus.
* @deprecated
* @see com.sun.webui.jsf.component.Body.getRequestFocusElementId(FacesContext)
*/
public static String getLastClientID(FacesContext context) {
return FocusManager.getRequestFocusElementId(context);
}
/**
* Set the client ID of the last component to have focus.
* @deprecated
* @see com.sun.webui.jsf.component.Body.setRequestFocusElementId(FacesContext,String)
*/
public static void setLastClientID(FacesContext context,
String clientId) {
FocusManager.setRequestFocusElementId(context, clientId);
}
/**
* Return the focus element id for component
.
* The returned id should be the id of an HTML element suitable to
* receive the focus.
* If component
is a ComplexComponent
* call component.getFocusElementId
else return
* component.getClientId
.
*/
public static String getFocusElementId(FacesContext context,
UIComponent component) {
if (component instanceof ComplexComponent) {
return ((ComplexComponent)component).getFocusElementId(context);
} else {
return component.getClientId(context);
}
}
/**
* Helper function to render a transparent spacer image.
*
* @param writer The current ResponseWriter
* @param component The uicomponent
* @param height The value to use for the image height attribute
* @param width The value to use for the image width attribute
*/
public static void renderSpacer(ResponseWriter writer, UIComponent component,
String dotSrc, int height, int width) throws IOException {
if (height == 0 && width == 0) {
return;
}
writer.startElement("img", component);
writer.writeAttribute("src", dotSrc, null); // NOI18N
writer.writeAttribute("alt", "", null); // NOI18N
writer.writeAttribute("border", "0", null); // NOI18N
writer.writeAttribute("height", new Integer(height), null); // NOI18N
writer.writeAttribute("width", new Integer(width), null); // NOI18N
writer.endElement("img"); // NOI18N
}
/**
* Helper function to render a transparent spacer image.
*
*
* @param writer The current ResponseWriter
* @param component The uicomponent
* @param height The value to use for the image height attribute
* @param width The value to use for the image width attribute
*/
public static void renderSpacer(FacesContext context, ResponseWriter writer,
UIComponent component, int height, int width) throws IOException {
if (height == 0 && width == 0) {
return;
}
Theme theme = ThemeUtilities.getTheme(context);
String dotSrc = theme.getImagePath(ThemeImages.DOT);
renderSpacer(writer, component, dotSrc, height, width);
}
/**
* Returns an id suitable for the HTML label element's "for" attribute.
* The returned id is obtained as follows.
*
*
* - The value of "id" is expected to be an absolute id and not a
* relative id and will be prepended with NamingContainer.SEPARATOR_CHAR
* and resolved to a component instance from the ViewRoot.
*
* - If the id cannot be resolved, return the id argument.
*
* - If the id can be resolved to a component instance, and it is an
* instance of ComplexComponent, then the instance method
*
getLebeledElementId
is called and the value returned,
* else the value of getClientId
is returned.
*
*
*
*
* @param context The faces context
* @param id The absolute client id of the component to be labeled.
* @return An id suitable for an HTML LABEL element's "for" attribute.
*/
public static String getLabeledElementId(FacesContext context, String id) {
if (id == null || context == null) {
return null;
}
String _id = id;
if (id.charAt(0) != NamingContainer.SEPARATOR_CHAR) {
_id = String.valueOf(NamingContainer.SEPARATOR_CHAR).concat(id);
}
UIComponent component = null;
try {
component = context.getViewRoot().findComponent(_id);
} catch (Exception e) {
if (LogUtil.fineEnabled()) {
LogUtil.fine("Component with that particular id " + //NOI18N
"cannot be found"); //NOI18N
}
}
if (component == null) {
return id;
}
if (component instanceof ComplexComponent) {
return ((ComplexComponent)component).getLabeledElementId(context);
}
return component.getClientId(context);
}
/**
* Helper function to render theme stylesheet link(s)
*
*
* @param context containing theme
* @param writer The current ResponseWriter
* @param component The uicomponent
*/
public static void renderStyleSheetLink(UIComponent component, Theme theme,
FacesContext context, ResponseWriter writer) throws IOException {
//Master.
//String master = theme.getPathToMasterStylesheet();
String[] files = theme.getMasterStylesheets();
if (files != null && files.length != 0) {
renderStylesheetLinks(files, component, writer);
}
// browser specific stylesheets
//
ClientType clientType = ClientSniffer.getClientType(context);
files = theme.getStylesheets(clientType.toString());
if (files != null && files.length != 0) {
renderStylesheetLinks(files, component, writer);
}
// Global stylesheets
//
files = theme.getGlobalStylesheets();
if (files != null && files.length != 0) {
renderStylesheetLinks(files, component, writer);
}
}
/**
* Render link
elments for css
files.
*/
public static void renderStylesheetLinks(String[] css,
UIComponent component, ResponseWriter writer) throws IOException {
for (int i = 0; i < css.length; i++) {
writer.startElement(HTMLElements.LINK, component); //NOI18N
writer.writeAttribute(HTMLAttributes.REL,
"stylesheet", null); //NOI18N
writer.writeAttribute(HTMLAttributes.TYPE,
"text/css", null); //NOI18N
writer.writeURIAttribute(HTMLAttributes.HREF, css[i], null);
writer.endElement(HTMLElements.LINK); //NOI18N
writer.write("\n"); //NOI18N
}
}
/**
* Helper function to render theme stylesheet definitions inline
*
* @param context containing theme
* @param writer The current ResponseWriter
* @param component The uicomponent
*/
public static void renderStyleSheetInline(UIComponent component,
Theme theme, FacesContext context, ResponseWriter writer)
throws IOException {
writer.startElement(HTMLElements.STYLE, component);
writer.writeAttribute(HTMLAttributes.TYPE, "text/css", null); //NOI18N
writer.write("\n"); //NOI18N
String[] files = theme.getMasterStylesheets();
if (files != null && files.length != 0) {
renderImports(files, writer);
}
// browser specific stylesheets
//
ClientType clientType = ClientSniffer.getClientType(context);
files = theme.getStylesheets(clientType.toString());
if (files != null && files.length != 0) {
renderImports(files, writer);
}
files = theme.getGlobalStylesheets();
if (files != null && files.length != 0) {
renderImports(files, writer);
}
writer.endElement(HTMLElements.STYLE);
}
/**
* Render import
directives for imports
.
*/
public static void renderImports(String[] imports, ResponseWriter writer)
throws IOException {
for (int i = 0; i < imports.length; i++) {
writer.write("@import(\""); //NOI18N
writer.write(imports[i]); //NOI18N
writer.write("\");"); //NOI18N
writer.write("\n"); //NOI18N
}
}
/**
* Perform a RequestDispatcher.include
of the specified URI
* jspURI
.
*
* The path identifed by jspURI
must begin with
* a <f:subview>
tag. The URI must not have
* as part of its path the FacesServlet mapping. For example if the
* FacesServlet mapping maps to /faces/*
then
* jspURI
must not have /faces/
as part of
* its path.
*
*
* If jspUIR
is a relative path then the
* request context path is prepended to it.
*
* @param context the FacesContext
for this request
* @param writer the ResponseWrite
destination for the
* rendered output
* @param jspURI the URI identifying a JSP page to be included.
* @throws IOException if response can't be written or jspURI
* cannot be included. Real cause is chained.
*/
public static void includeJsp(FacesContext context, ResponseWriter writer,
String jspURI) throws IOException {
class ResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter printWriter;
public ResponseWrapper(HttpServletResponse response,
Writer writer) {
super((HttpServletResponse)response);
this.printWriter = new PrintWriter(writer);
}
public PrintWriter getWriter() {
return printWriter;
}
public ServletOutputStream getOutputStream() throws IOException {
throw new IllegalStateException();
}
public void resetBuffer() {
}
}
if (jspURI == null) {
return;
}
// prepend the request path if there is one in this path is not
// a relative path. It appears that the servlet context algorithm
// differs from the JspRuntime algorithm that allowed a relative
// path in the lockhart wizard.
//
try {
if (!jspURI.startsWith("/")) { //NOI18N
String contextPath =
context.getExternalContext().getRequestContextPath();
jspURI = contextPath.concat("/").concat(jspURI); //NOI18N
}
ServletRequest request =
(ServletRequest)context.getExternalContext().getRequest();
ServletResponse response =
(ServletResponse)context.getExternalContext().getResponse();
RequestDispatcher rd = request.getRequestDispatcher(jspURI);
// JSF is already buffering and suppressing output.
//
rd.include(request,
new ResponseWrapper((HttpServletResponse)response, writer));
} catch (Exception e) {
throw (IOException)new IOException().initCause(e);
}
}
/**
* Helper method to render extra attributes.
*
* @param writer ResponseWriter
to which the element
* end should be rendered
* @param extraHtml Extra HTML appended to the tag enclosing the header
*
* @exception IOException if an input/output error occurs
*/
public static void renderExtraHtmlAttributes(ResponseWriter writer,
String extraHtml) throws IOException {
if (extraHtml == null) {
return;
}
int n = extraHtml.length();
int i = 0;
while (i < n) {
StringBuffer name = new StringBuffer();
StringBuffer value = new StringBuffer();
// Skip extra space characters.
while (i < n && Character.isWhitespace(extraHtml.charAt(i))) {
i++;
}
// Find name.
for (; i < n; i++) {
char c = extraHtml.charAt(i);
if (c == '\'' || c == '"') {
return; // Not well formed.
} else if (c == '=') {
break;
} else {
name.append(c);
}
}
i++; // Skip =
// Process quote character.
char quote = (i < n) ? extraHtml.charAt(i) : '\0';
if (!(quote == '\'' || quote == '"')) {
return; // Not well formed.
}
i++; // Skip quote character.
// Find value.
for (; i < n; i++) {
char c = extraHtml.charAt(i);
if (c == quote) {
break;
} else {
value.append(c);
}
}
i++; // Skip quote character.
writer.writeAttribute(name.toString(), value.toString(), null); //NOI18N
}
}
/**
* Helper function to render a typical URL.
*
* Note: Path must be a valid absolute URL or full path URI.
*
*
* @param writer The current ResponseWriter
* @param component The uicomponent
* @param name The attribute name of the url to write out
* @param url The value passed in by the developer for the url
* @param compPropName The property name of the component's property that
* specifies this property. Should be null if same as name.
*
*/
public static void renderURLAttribute(FacesContext context,
ResponseWriter writer, UIComponent component, String name, String url,
String compPropName) throws IOException {
if (url == null) {
return;
}
Param paramList[] = getParamList(context, component);
StringBuffer sb = new StringBuffer();
int i = 0;
int len = paramList.length;
sb = new StringBuffer();
// Don't append context path here as themed images already include it.
sb.append(url);
if (0 < len) {
sb.append("?");
}
for (i = 0; i < len; i++) {
if (0 != i) {
sb.append("&");
}
sb.append(paramList[i].getName());
sb.append("=");
sb.append(paramList[i].getValue());
}
String newName = null;
if (compPropName != null) {
newName = (compPropName.equals(name))
? null
: compPropName;
}
// Note: Path must be a valid absolute URL or full path URI -- see
// bugtraq #6306848 & #6322887.
writer.writeURIAttribute(name, (url.trim().length() != 0)
? context.getExternalContext().encodeResourceURL(sb.toString())
: "", newName);
}
static protected Param[] getParamList(FacesContext context,
UIComponent command) {
ArrayList parameterList = new ArrayList();
Iterator kids = command.getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
if (kid instanceof UIParameter) {
UIParameter uiParam = (UIParameter) kid;
Object value = uiParam.getValue();
Param param = new Param(uiParam.getName(),
(value == null ? null :
value.toString()));
parameterList.add(param);
}
}
return (Param[]) parameterList.toArray(new Param[parameterList.size()]);
}
//inner class to store parameter name and value pairs
static protected class Param {
public Param(String name, String value) {
set(name, value);
}
private String name;
private String value;
public void set(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
// This method is written in such a way that you can use it without
// using the component.
/**
* Render an href for a "skip link".
* Note that "anchor" must be unique if this method is called more
* than once to render on the same page.
*/
public static void renderSkipLink(String anchorName, String styleClass,
String style, String toolTip, Integer tabIndex, UIComponent component,
FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div", component); //NOI18N
if(styleClass != null) {
writer.writeAttribute("class", styleClass, null);
} else {
writer.writeAttribute("class", ThemeUtilities.getTheme(context).
getStyleClass(ThemeStyles.SKIP_WHITE), null);
}
if(style != null) {
writer.writeAttribute("style", styleClass, null);
}
StringBuffer buffer = new StringBuffer(128);
// Use this for the href and for the icon id
//
buffer.append("#").
append(component.getClientId(context)).
append("_").
append(anchorName);
writer.startElement("a", component); //NOI18N
writer.writeAttribute("href", buffer.toString(), null);
// This is generating and XHTML invalid error.
// It doesn't like "alt".
//
String defaultAlt = ThemeUtilities.getTheme(context).getMessage(
"skipLink.defaultAlt", new String[] {anchorName}); //NOI18N
if(toolTip != null) {
writer.writeAttribute("alt", toolTip, null);
} else {
writer.writeAttribute("alt", defaultAlt, null); // GF 508 change
}
if(tabIndex != null) {
writer.writeAttribute("tabindex", tabIndex.toString(), null);
}
Icon icon = ThemeUtilities.getIcon(ThemeUtilities.getTheme(context), ThemeImages.DOT);
icon.setParent(component);
icon.setWidth(1);
icon.setHeight(1);
icon.setBorder(0);
icon.setToolTip((toolTip == null) ? defaultAlt : toolTip);
buffer.setLength(0);
buffer.append(anchorName).append("_icon"); //NOI18N
icon.setId(buffer.toString());
RenderingUtilities.renderComponent(icon, context);
writer.endElement("a"); //NOI18N
writer.endElement("div"); //NOI18N
}
// This method is written in such a way that you can use it without
// using the component.
static public void renderAnchor(String anchorName, UIComponent component,
FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
StringBuffer buffer = new StringBuffer(128);
buffer.append(component.getClientId(context));
buffer.append("_");
buffer.append(anchorName);
writer.startElement("div", component); //NOI18N
writer.write("\n"); //NOI18N
writer.startElement("a", component); //NOI18N
writer.writeAttribute("name", buffer.toString(), null);
writer.endElement("a"); //NOI18N
writer.write("\n"); //NOI18N
writer.endElement("div"); //NOI18N
}
/**
* Return whether the given UIComponent
is "visible".
* If the property is null, it will return true. Otherwise the value
* of the property is returned.
*
* @param component The UIComponent
to check
*
* @return True if the property is null or true, false otherwise.
*/
public static boolean isVisible(UIComponent component) {
Object visible = component.getAttributes().get("visible"); //NOI18N
if (visible == null) {
return true;
} else {
return ((Boolean) visible).booleanValue();
}
}
}