/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2012 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/CDDLGPL_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.facelets.tag;
import com.sun.faces.facelets.tag.jsf.PassThroughAttributeLibrary;
import com.sun.faces.facelets.tag.jsf.PassThroughElementLibrary;
import com.sun.faces.facelets.tag.jsf.html.HtmlLibrary;
import javax.faces.render.Renderer;
import javax.faces.view.Location;
import javax.faces.view.facelets.FaceletException;
import javax.faces.view.facelets.Tag;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributes;
import javax.faces.view.facelets.TagDecorator;
import java.util.HashMap;
import java.util.Map;
/**
* A simple tag decorator to enable jsf: syntax
*/
class DefaultTagDecorator implements TagDecorator {
private static enum Mapper {
a(
new ElementConverter("h:commandLink", "jsf:action"),
new ElementConverter("h:commandLink", "jsf:actionListener"),
new ElementConverter("h:outputLink", "jsf:value"),
new ElementConverter("h:link", "jsf:outcome")),
img("h:graphicImage"), body("h:body"), head("h:head"), label("h:outputLabel"), script("h:outputScript"),
link("h:outputStylesheet"),
form("h:form"), textarea("h:inputTextarea"),
// TODO if we want the name of the button to become the id, we have to do .id("name")
button(new ElementConverter("h:button", "jsf:outcome"), new ElementConverter("h:commandButton")),
select(new ElementConverter("h:selectManyListbox", "multiple").id("name"),
// TODO this is a little bit ugly to handle the name as if it were jsf:id. we should not support this
new ElementConverter("h:selectOneListbox").id("name")),
input(new ElementConverter("h:inputText", "type")
// TODO this is a little bit ugly to handle the name as if it were jsf:id. we should not support this
.id("name")
.map("hidden", "inputHidden")
.map("password", "inputSecret")
.map("number", "inputText")
.map("search", "inputText")
.map("email", "inputText")
.map("datetime", "inputText")
.map("date", "inputText")
.map("month", "inputText")
.map("week", "inputText")
.map("time", "inputText")
.map("datetime-local", "inputText")
.map("range", "inputText")
.map("color", "inputText")
.map("url", "inputText")
.map("checkbox", "selectBooleanCheckbox")
.map("file", "inputFile")
.map("submit", "commandButton")
.map("reset", "commandButton")
.map("button", "button"));
private ElementConverter elementConverter;
private Mapper(final ElementConverter... elementConverters) {
if (elementConverters.length == 1) {
this.elementConverter = elementConverters[0];
} else {
this.elementConverter = new ElementConverter() {
@Override
public Tag decorate(Tag tag) {
for (ElementConverter converter : elementConverters) {
Tag decorated = converter.decorate(tag);
if (decorated != null) {
return decorated;
}
}
return null;
}
};
}
}
private Mapper(String faceletTag) {
elementConverter = new ElementConverter(faceletTag);
}
}
private static enum Namespace {
p(PassThroughAttributeLibrary.Namespace),
jsf(PassThroughElementLibrary.Namespace),
h(HtmlLibrary.Namespace);
private String uri;
Namespace(String uri) {
this.uri = uri;
}
}
private ElementConverter defaultElementConverter = new ElementConverter("jsf:element");
@Override
public Tag decorate(Tag tag) {
String ns = tag.getNamespace();
if (!hasJsfAttribute(tag)) {
// return immediately, if we have no jsf: attribute
return null;
}
// we only handle html tags!
if (!("".equals(ns) || "http://www.w3.org/1999/xhtml".equals(ns))) {
throw new FaceletException("Elements with namespace " +
ns + " may not have attributes in namespace " +
Namespace.jsf.uri + "." +
" Namespace " + Namespace.jsf.uri +
" is intended for otherwise non-JSF-aware markup, such as <input type=\"text\" jsf:id >" +
" It is not valid to have <h:commandButton jsf:id=\"button\" />.");
}
for (Mapper mapper : Mapper.values()) {
if (tag.getLocalName().equals(mapper.name())) {
return mapper.elementConverter.decorate(tag);
}
}
return defaultElementConverter.decorate(tag);
}
private boolean hasJsfAttribute(Tag tag) {
for (String ns : tag.getAttributes().getNamespaces()) {
if (Namespace.jsf.uri.equals(ns)) {
return true;
}
}
return false;
}
private static class ElementConverter implements TagDecorator {
private String localName;
private Namespace namespace;
private String arbiterAttributeName;
private String arbiterAttributeNamespace = "";
private Map<String, String> additionalMappings = new HashMap<>();
private String otherHtmlIdAttribute;
private ElementConverter() {
super();
}
private ElementConverter(String faceletsTag) {
this(faceletsTag, null);
}
private ElementConverter(String faceletsTag, String arbiterAttributeName) {
String[] strings = faceletsTag.split(":");
this.namespace = Namespace.valueOf(strings[0]);
this.localName = strings[1];
this.arbiterAttributeName = arbiterAttributeName;
if (arbiterAttributeName != null && arbiterAttributeName.indexOf(':') > 0) {
strings = arbiterAttributeName.split(":");
this.arbiterAttributeNamespace = Namespace.valueOf(strings[0]).uri;
this.arbiterAttributeName = strings[1];
}
}
private ElementConverter map(String arbiterAttributeValue, String faceletsTagLocalName) {
additionalMappings.put(arbiterAttributeValue, faceletsTagLocalName);
return this;
}
private ElementConverter id(String otherHtmlIdAttribute) {
this.otherHtmlIdAttribute = otherHtmlIdAttribute;
return this;
}
@Override
public Tag decorate(Tag tag) {
if (arbiterAttributeName == null) {
// no arbiter
return convertTag(tag, namespace, localName);
}
TagAttribute arbiterAttribute = tag.getAttributes().get(arbiterAttributeNamespace, arbiterAttributeName);
if (arbiterAttribute == null) {
// no arbiter
return null;//convertTag(tag, namespace, localName);
}
// PENDING
/**
if (!arbiterAttribute.isLiteral()) {
// TODO should we throw an exception here?
}
**/
String myLocalName = additionalMappings.get(arbiterAttribute.getValue());
if (myLocalName == null) {
myLocalName = this.localName;
}
return convertTag(tag, namespace, myLocalName);
}
protected Tag convertTag(Tag tag, Namespace namespace, String localName) {
Location location = tag.getLocation();
String ns = namespace.uri;
String qName = namespace.name() + ":" + localName;
TagAttributes attributes = convertAttributes(tag.getAttributes());
Tag converted = new Tag(location, ns, localName, qName, attributes);
for (TagAttribute tagAttribute : attributes.getAll()) {
// set the correct tag
tagAttribute.setTag(converted);
}
return converted;
}
protected TagAttributes convertAttributes(TagAttributes original) {
Map<String, TagAttribute> attributes = new HashMap<>();
TagAttribute elementName = createElementName(original.getTag());
attributes.put(elementName.getQName(), elementName);
for (TagAttribute attribute : original.getAll()) {
TagAttribute converted = convertTagAttribute(attribute);
// avoid duplicates
attributes.put(converted.getQName(), converted);
}
return new TagAttributesImpl(attributes.values().toArray(new TagAttribute[attributes.size()]));
}
private TagAttribute createElementName(Tag tag) {
Location location = tag.getLocation();
String ns = Namespace.p.uri;
String myLocalName = Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY;
String qName = "p:" + myLocalName;
String value = tag.getLocalName();
return new TagAttributeImpl(location, ns, myLocalName, qName, value);
}
protected TagAttribute convertTagAttribute(TagAttribute attribute) {
Location location = attribute.getLocation();
String ns = attribute.getNamespace();
String myLocalName = attribute.getLocalName();
String qName;
String value = attribute.getValue();
if (Namespace.jsf.uri.equals(attribute.getNamespace())) {
// make this a component attribute
qName = myLocalName;
ns = "";
} else {
if (ns.length() != 0 && !ns.equals(attribute.getTag().getNamespace())) {
// the attribute has a different namespace than the tag. preserve it.
return attribute;
}
if (attribute.getLocalName().equals(otherHtmlIdAttribute)) {
// special case for input name
qName = "id";
myLocalName = "id";
} else {
// make this a pass through attribute
qName = "p:" + myLocalName;
ns = Namespace.p.uri;
}
}
return new TagAttributeImpl(location, ns, myLocalName, qName, value);
}
}
}