/*
* � Copyright IBM Corp. 2011
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.ibm.xsp.extlib.designer.tooling.visualizations;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.runtime.Path;
import org.w3c.dom.Node;
import com.ibm.commons.swt.util.ComputedValueUtils;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.xml.XMLException;
import com.ibm.designer.domino.constants.XSPAttributeNames;
import com.ibm.designer.domino.constants.XSPTagNames;
import com.ibm.designer.domino.xsp.api.visual.AbstractVisualizationFactory;
import com.ibm.xsp.extlib.designer.tooling.constants.IExtLibTagLib;
import com.ibm.xsp.registry.FacesComponentDefinition;
import com.ibm.xsp.registry.FacesLibraryFragment;
import com.ibm.xsp.registry.FacesProject;
import com.ibm.xsp.registry.FacesRegistry;
public abstract class AbstractCommonControlVisualizer extends AbstractVisualizationFactory{
public static final boolean DEBUG = false;
//Defined prefixes
public static final String XP_PREFIX = "xp"; // $NON-NLS-1$
public static final String XE_PREFIX = "xe"; // $NON-NLS-1$
public static final String XC_PREFIX = "xc"; // $NON-NLS-1$
//Defined namespaces
public static final String XP_CORE_NAMESPACE = "http://www.ibm.com/xsp/core"; // $NON-NLS-1$
public static final String XE_EXTLIB_NAMESPACE = IExtLibTagLib.EXT_LIB_NAMESPACE_URI; // $NON-NLS-1$
public static final String XC_CUSTOM_CONTROLS_NAMESPACE = "http://www.ibm.com/xsp/custom"; // $NON-NLS-1$
//Static Strings we use when creating the visualizations
public static final String XML_ENCODING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; // $NON-NLS-1$
public static final String LINE_DELIMITER = "\r\n"; // $NON-NLS-1$
public static final String START_SCRIPLET_TAG = "<%";
public static final String START_SCRIPLET_VALUE_TAG = "<%=";
public static final String END_SCRIPLET_TAG = "%>";
public static final String NAMESPACE_DEFINITION_PREFIX = "xmlns:"; // $NON-NLS-1$
//attribute values that were not specified in XSPAttributeNames.
public static final String XSP_ATTR_VAL_NUMBER = "number"; // $NON-NLS-1$
public static final String XSP_ATTR_VAL_RTL = "rtl"; // $NON-NLS-1$
public static final String XLIB_ATTR_TITLE = "title"; // $NON-NLS-1$
public static final String XLIB_ATTR_LABEL = "label"; // $NON-NLS-1$
//static images specific info
public static final String IMAGES_LOCATION = Path.SEPARATOR + "extlib" + Path.SEPARATOR + "designer" + Path.SEPARATOR + "markup"; // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
public static final String DOJO_FORM_IMAGES_LOCATION = "dojoform"; // $NON-NLS-1$
public static final String DATA_ACCESS_IMAGES_LOCATION = "dataaccess"; // $NON-NLS-1$
public static final String EXTENSION_LIBRARY_IMAGES_LOCATION = "extensionlibrary"; // $NON-NLS-1$
public static final String INOTES_IMAGES_LOCATION = "inotes"; // $NON-NLS-1$
public static final String MOBILE_IMAGES_LOCATION = "mobile"; // $NON-NLS-1$
private static final String LIB_URL_START_STRING = "xsp://"; // $NON-NLS-1$
private static final String LIB_URL_END_STRING = "~~";
//top level tag view tag that we create for any visualization
private XPageViewTag _xPageViewTag = new XPageViewTag();
//This is a cache of the markup for the given control. If the control is static the markup will not be
//regenerated every time the node is updated.
private String _cachedMarkup;
/**
* Returns the markup for the XML Declaration and opening view tag for the visualization.
* @return
*/
protected String getXPageHeader(){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(XML_ENCODING);
strBuilder.append(LINE_DELIMITER);
strBuilder.append(_xPageViewTag.toString());
return strBuilder.toString();
}
/**
* Helper method to allow a user to add attributes to the xp:view tag of the visualization.
* @param attributeName
* @param value
*/
protected void addAttributeToHeader(String attributeName, String value){
if(null != _xPageViewTag){
_xPageViewTag.addAttribute(attributeName, value);
}
}
/**
* Helper method to add xmlns:xe="http://www.ibm.com/xsp/coreex" to the view tag
*/
protected void addXEPrefixToHeader(){
if(null != _xPageViewTag){
_xPageViewTag.addPrefix(XE_PREFIX, XP_CORE_NAMESPACE);
}
}
/**
* Helper method to add xmlns:xc="http://www.ibm.com/xsp/custom" to the view tag
*/
protected void addXCPrefixToHeader(){
if(null != _xPageViewTag){
_xPageViewTag.addPrefix(XC_PREFIX, XC_CUSTOM_CONTROLS_NAMESPACE);
}
}
/**
* Helper method to add xmlns:<prefix>="<namseSpaceURI" to the view tag
*/
protected void addPrefixToHeader(String prefix, String nameSpaceURI){
if(null != _xPageViewTag){
_xPageViewTag.addPrefix(prefix, nameSpaceURI);
}
}
/**
* Create a tag object with the given prefix and tagName.
* @param prefix
* @param tagName
* @return the created Tag object
*/
protected Tag createTag(String prefix, String tagName){
return new Tag(prefix, tagName);
}
/**
* Create a tag object with the given prefix and tagName. Then add all the attributes in the
* attributes HasMap to that tag
* @param prefix
* @param tagName
* @param attributes
* @return the created Tag object with the given attributes set on it.
*/
protected Tag createTag(String prefix, String tagName, HashMap<String,String>attributes){
return new Tag(prefix, tagName, attributes);
}
/**
* Create the markup for an opening tag given a prefix and tagName
* @param prefix
* @param tagName
* @return the string representing an opening tag in the form <prefix:tagName>
*/
protected String createStartTag(String prefix, String tagName){
return "<" + prefix + ":" + tagName + ">";
}
/**
* Create the markup for a closing tag given a prefix and tagName
* @param prefix
* @param tagName
* @return the string representing a closing tag in the form </prefix:tagName>
*/
protected String createEndTag(String prefix, String tagName){
return "</" + prefix + ":" + tagName + ">";
}
/**
* Create the markup for an opening tag given a prefix, tagName and Map of attributes
* @param prefix
* @param tagName
* @param attributes
* @return the string representing an opening tag in the form <prefix:tagName attributeA="valueA" attributeB="valueB"...>
*/
protected String createStartTag(String prefix, String tagName, HashMap<String,String>attributes){
HashMap<String,String>complexAttributes = new HashMap<String,String>();
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<" + prefix + ":" + tagName);
if(null != attributes && !attributes.isEmpty()){
//sort the attributes map, so that they are always added in alphabetical order
Set<String> attributeNameKeys = attributes.keySet();
TreeSet<String> attributeNames = new TreeSet<String>(attributeNameKeys);
Iterator<String> attrNamesIter = attributeNames.iterator();
while(attrNamesIter.hasNext()){
String attributeName = attrNamesIter.next();
String attributeValue = attributes.get(attributeName);
if(StringUtil.isNotEmpty(attributeName) && StringUtil.isNotEmpty(attributeValue)){
if(ComputedValueUtils.isStringComputed(attributeValue)||isJSVarAttributeBinding(attributeValue)){
complexAttributes.put(attributeName,attributeValue);
}
else{
strBuilder.append(" " + attributeName + "=\"" + attributeValue + "\"");
}
}
}
}
strBuilder.append(">");
//add the complex attributes
if(!complexAttributes.isEmpty()){
Set<String> complexAttributeNameKeys = complexAttributes.keySet();
TreeSet<String> complexAttributeNames = new TreeSet<String>(complexAttributeNameKeys);
Iterator<String> complexAttrNamesIter = complexAttributeNames.iterator();
while(complexAttrNamesIter.hasNext()){
String complexAttributeName = complexAttrNamesIter.next();
String complexAttributeValue = attributes.get(complexAttributeName);
if(StringUtil.isNotEmpty(complexAttributeName) && StringUtil.isNotEmpty(complexAttributeValue)){
strBuilder.append(LINE_DELIMITER);
strBuilder.append("<" + prefix + ":this." + complexAttributeName + ">"); // $NON-NLS-1$
strBuilder.append(complexAttributeValue);
strBuilder.append("</" + prefix + ":this."+ complexAttributeName + ">"); // $NON-NLS-1$
}
}
}
return strBuilder.toString();
}
/**
* Simple implementation of a tag. Allows you to create a tag, add attributes and child tags to it without having to worry
* about start and end tags.
*
* If you create a tag and add multiple child tags, you only need to call toString on the parent tag and it will return the
* xsp markup of the parent and all its children.
*/
protected class Tag{
//map to hold any attributes set on the tag
private HashMap<String, String> _attributesMap = new HashMap<String, String>();
//List of all the child Tags added to this tag
private ArrayList<Tag> _childTags = new ArrayList<Tag>();
//the prefix for the tag
private String _prefix;
//The tagName for the tag
private String _tagName;
/**
* Simple constructor to create a Tag with a given prefix and tagName
* @param prefix
* @param tagName
*/
public Tag(String prefix, String tagName){
_prefix = prefix;
_tagName = tagName;
}
/**
* Simple constructor to create a Tag with a given prefix and tagName, with the provided attributes set on it.
* @param prefix
* @param tagName
* @param attributes
*/
public Tag(String prefix, String tagName, HashMap<String, String> attributes){
_prefix = prefix;
_tagName = tagName;
_attributesMap = attributes;
}
/**
* Add a given Tag object as a child of this Tag Object
* @param tag
*/
public void addChildTag(Tag tag){
_childTags.add(tag);
}
/**
* This method will add a child Tag which just contains text. It is basically a way to add
* plain text as a child of a Tag. The text will not be wrapped with special tags that define
* it as being text.
* @param text
*/
public void addTextChild(String text){
TextTag textTag = new TextTag(text);
_childTags.add(textTag);
}
/**
*
* This method will create markup like this <%=jsVarToOutput%> as a child of this tag.
* @param jsVarToOutput
*/
public void addJSVarTextChild(String jsVarToOutput){
TextTag textTag = new TextTag(START_SCRIPLET_VALUE_TAG + jsVarToOutput + END_SCRIPLET_TAG);
_childTags.add(textTag);
}
/**
* Takes a given Tag object and returns whether or not that Tag is already a child of this Tag.
* @param tag
* @return
*/
public boolean hasChildTag(Tag tag){
return _childTags.contains(tag);
}
/**
* Add an attribute with the given attributeName and value to this Tag
* @param attributeName
* @param value
*/
public void addAttribute(String attributeName, String value){
_attributesMap.put(attributeName, value);
}
/**
* The get value specified for the given attributeName for this Tag.
* @param attributeName
* @return
*/
public String getAttributeValue(String attributeName){
return _attributesMap.get(attributeName);
}
/**
* This method takes in an attribute name and javaScript variable name.
* It then set the attribute to be bound to the value of the javaScrtipt variable name
* So it will create markup like this <prefix:tagName attributeName="<%=jsVarToSetAttributeTo%>">...
* @param attributeName
* @param jsVarToSetAttributeTo
*/
public void addJSVarAttributeBinding(String attributeName, String jsVarToSetAttributeTo){
_attributesMap.put(attributeName, START_SCRIPLET_VALUE_TAG + jsVarToSetAttributeTo + END_SCRIPLET_TAG);
}
/**
* Returns whether or not this Tag has child Tags added to it.
* @return
*/
public boolean hasChildren(){
return _childTags.size()>0;
}
/**
* Returns all the child Tags that have been added to this Tag.
* @return a Tag[] of child Tags.
*/
public Tag[] getChildTags(){
return _childTags.toArray(new Tag[_childTags.size()]);
}
/**
* Returns a HashMap containing all the attributes and their values that have been set on this Tag
* @return
*/
public HashMap<String, String> getAttributes(){
return _attributesMap;
}
/**
* Returns the prefix for this Tag
* @return
*/
public String getPrefix(){
return _prefix;
}
/**
* Returns the TagName for this Tag.
* @return
*/
public String getTagName(){
return _tagName;
}
/**
* This method will return the xsp markup for this tag and all of its child tags.
* @return the complete xsp source markup for this tag, it's attributes and all of its chid tags.
*/
public String toString(){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(createStartTag(_prefix, _tagName, _attributesMap));
if(hasChildren()){
//strBuilder.append(LINE_DELIMITER);
Tag[] children = getChildTags();
for(int i=0; i<children.length; i++){
Tag child = children[i];
strBuilder.append(child.toString());
}
}
//strBuilder.append(LINE_DELIMITER);
strBuilder.append(createEndTag(_prefix,_tagName));
//strBuilder.append(LINE_DELIMITER);
return strBuilder.toString();
}
}
/**
* Special Tag type to represent the view tag in a visualization.
* Just gives an easier way of adding namespace declarations to the view tag.
*/
private class XPageViewTag extends Tag{
/**
* Just call up to Tag to create a new view tag, and add the standard XP prefix to it.
*/
protected XPageViewTag() {
super(XP_PREFIX, XSPTagNames.XSP_TAG_VIEW);
addPrefix(XP_PREFIX, XP_CORE_NAMESPACE);
}
/**
* Add a namespace declaration to the view tag. We really just set an "xmlns:<prefix>" attribute
* on the Tag with its value set to <nameSpaceURI>
* @param prefix
* @param nameSpaceURI
*/
public void addPrefix(String prefix, String nameSpaceURI){
super.addAttribute(NAMESPACE_DEFINITION_PREFIX + prefix, nameSpaceURI);
}
/**
* Override parent behavior to add custom view tag behavior. We don't want to generate the
* closing tag and we don't need to worry about child tags.The view tag is added to the page
* independently of any markup created by a visualization class.
* @return
*/
public String toString(){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(createStartTag(getPrefix(), getTagName(), getAttributes()));
//strBuilder.append(LINE_DELIMITER);
return strBuilder.toString();
}
}
/**
* Special representation of a TextTag. This is used to add plain text as a child of a Tag.
* Tags essentially map to Nodes in a Dom. This TextNode represents a TextNode, where it doesn't
* have a prefix or tagName so it does not generate opening and closing tags.
*/
private class TextTag extends Tag{
private String _text;
private TextTag(String text){
//text nodes do not have prefixes or tagNames.
super(null,null);
_text = text;
}
public String toString(){
return _text;
}
}
/*
* (non-Javadoc)
* @see com.ibm.designer.domino.xsp.api.visual.AbstractVisualizationFactory#getFullXSPMarkupForControl(org.w3c.dom.Node, com.ibm.xsp.registry.FacesRegistry)
*/
public String getFullXSPMarkupForControl(Node nodeToVisualize, FacesRegistry registry) {
if(isStaticMarkup() && StringUtil.isNotEmpty(_cachedMarkup)){
return _cachedMarkup;
}
StringBuilder strBuilder = new StringBuilder();
//call this first so that we can add extra prefixes to the header before we add the header text.
String markup = getXSPMarkupForControl(nodeToVisualize, null, registry);
strBuilder.append(getXPageHeader());
strBuilder.append(LINE_DELIMITER);
strBuilder.append(markup);
strBuilder.append(LINE_DELIMITER);
strBuilder.append(XSP_FOOTER);
strBuilder.append(LINE_DELIMITER);
if(DEBUG){
System.out.println(strBuilder.toString());
}
_cachedMarkup = strBuilder.toString();
return _cachedMarkup;
}
/**
* This method is used to determine if the render-markup for visualizations are static or dynamic.
* If a visualization updates based on its attributes or child nodes, then you must
* override this method and return false. Otherwise the cached version will always be
* returned and the visualization will not update.
* @return true if the markup is static, false otherwise.
*/
public boolean isStaticMarkup(){
return true;
}
/**
* This is a helper method to generate a javascript blob that to be included in a visualization to
* get the value of an attribute set on the element being visualized.
*
* So say this class was being used to generate a visualization for an xe:djxButton tag, and the djxButtonTag has a
* label attribute set on it. You could use this method to add javaScript to the visualization to get the value of the
* label attribute set on the xe:djxButton tag.
*
* You also need to specify a defaultValue. This is the value that will be returned if the attribute in question has not been
* set.
*
* The markup this method generates will look like this
*
* <%
* var variableName=this.attributeName;
* if(null==variableName || variableName ==""){
* variableName=defaultValue;
* }
* %>
*
* @param attributeName
* @param defaultValue
* @param variableName
* @return
*/
protected String generateFunctionToGetAttributeValue(String attributeName, String defaultValue, String variableName){
return generateFunctionToGetAttributeValue(attributeName, defaultValue, variableName, true);
}
protected String generateFunctionToGetAttributeValue(String attributeName, String defaultValue, String variableName, boolean escapeDefaultValue){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(START_SCRIPLET_TAG + LINE_DELIMITER);
strBuilder.append("var " + variableName + "=this." + attributeName + ";"); // $NON-NLS-2$ $NON-NLS-1$
strBuilder.append(LINE_DELIMITER);
strBuilder.append("if(null=="+variableName + " || " + variableName + "==\"\"){"); // $NON-NLS-1$
strBuilder.append(LINE_DELIMITER);
if(escapeDefaultValue){
strBuilder.append(variableName + "=escape(\""+defaultValue+"\");"); //$NON-NLS-1$ //$NON-NLS-2$
}else{
strBuilder.append(variableName + "=\""+defaultValue+"\";");
}
strBuilder.append(LINE_DELIMITER);
strBuilder.append("}");
strBuilder.append(LINE_DELIMITER);
strBuilder.append("else {"); //$NON-NLS-1$
strBuilder.append(LINE_DELIMITER);
strBuilder.append(variableName + "=escape(" + variableName + ");"); // $NON-NLS-2$ $NON-NLS-1$
strBuilder.append(LINE_DELIMITER);
strBuilder.append("}");
strBuilder.append(END_SCRIPLET_TAG);
strBuilder.append(LINE_DELIMITER);
return strBuilder.toString();
}
/**
* Helper method to create an image tag in a visualization.
* @param imageName - the name (e.g. image.png) of the image file to add
* @param imageSubFolder - we already assume that all images in sub folders of the folder extlib/designer/markup.
* So this set the subfolder of the markup folder that contains the image.
* @return The source of the created image tag.
*/
protected String createImageTag(String imageName, String imageSubFolder){
Tag imageTag = new Tag(XP_PREFIX, XSPTagNames.XSP_TAG_IMAGE);
imageTag.addAttribute(XSPAttributeNames.XSP_ATTR_URL, getURLForImage(imageName, imageSubFolder));
return imageTag.toString();
}
/**
* Helper method to create an image tag object in a visualization.
* @param imageName - the name (e.g. image.png) of the image file to add
* @param imageSubFolder - we already assume that all images in sub folders of the folder extlib/designer/markup.
* So this set the subfolder of the markup folder that contains the image.
* @return The created image tag object.
*/
protected Tag createImageTagObj(String imageName, String imageSubFolder){
Tag imageTag = new Tag(XP_PREFIX, XSPTagNames.XSP_TAG_IMAGE);
imageTag.addAttribute(XSPAttributeNames.XSP_ATTR_URL, getURLForImage(imageName, imageSubFolder));
return imageTag;
}
/**
* Generate the OS specific path to the image given an imageName and subFolder of extlib/designer/markup
* @param imageName
* @param imageSubFolder
* @return
*/
private String getURLForImage(String imageName, String imageSubFolder){
return IMAGES_LOCATION + Path.SEPARATOR + imageSubFolder + Path.SEPARATOR + imageName;
}
/**
* The only XSP tag that currently supports rendering an Image when used in a visualization is the xp:image tag.
* It has custom code to look for images from the library that contains the node being visualized, instead of
* looking in the Designer Project.
*
* For controls that need to display images (like buttons with specified images etc..) we need to give them a custom
* URL so that they can be found when the visualization is being displayed.
*
* The URL needs to take the format xsp://libraryId~~imageURL
* This will tell Designer to use the library with the given libraryID to find the image at the given imageURL.
*
* This method used the node being visualized to find the library associated with that Node, and uses that to
* generate the complete URL.
*
* @param node
* @param registry
* @param imageName
* @param imagesFolderLocation
* @return A complete URL in the form xsp://libraryId~~imageURL
*/
public String generateImageURL(Node node, FacesRegistry registry, String imageName, String imagesFolderLocation){
String id = "";
String imageURL = "";
if(null != registry && null != node){
FacesComponentDefinition def = registry.findComponent(node.getNamespaceURI(), node.getLocalName());
if(null != def){
FacesLibraryFragment fragment = def.getFile();
if(null != fragment){
FacesProject proj = fragment.getProject();
if(null != proj){
id = proj.getId();
}
}
}
}
if(StringUtil.isNotEmpty(id)){
imageURL = LIB_URL_START_STRING + id + LIB_URL_END_STRING + getURLForImage(imageName, imagesFolderLocation);
}
if(StringUtil.isNotEmpty(imageURL)){
return imageURL;
}
return getURLForImage(imageName, imagesFolderLocation);
}
/**
* This method takes a node and get the xsp source markup for it, including the
* source for any child nodes.
* @param n
* @return
*/
public String getMarkupForNode(Node n){
try {
String nodeStr = super.printNode(n);
if(StringUtil.isNotEmpty(nodeStr)){
return nodeStr;
}
} catch (XMLException e) {
//do nothing
}
return null;
}
/**
* Test to see if an attribute value is actually a js binding. i.e. if it contains any scriptlet tags.
* @param attributeValue
* @return
*/
public boolean isJSVarAttributeBinding(String attributeValue){
if(StringUtil.isNotEmpty(attributeValue)){
if(attributeValue.contains(START_SCRIPLET_TAG) ||
attributeValue.contains(START_SCRIPLET_VALUE_TAG) ||
attributeValue.contains(END_SCRIPLET_TAG)){
return true;
}
}
return false;
}
}