/**
* Copyright (C) 2004 idega Software
*
*/
package com.idega.presentation;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.idega.idegaweb.IWMainApplication;
import com.idega.idegaweb.IWUserContext;
import com.idega.presentation.util.RenderUtil;
import com.idega.util.CoreConstants;
import com.idega.util.IOUtil;
import com.idega.util.ListUtil;
import com.idega.util.StringHandler;
import com.idega.util.StringUtil;
import com.idega.util.datastructures.map.MapUtil;
import com.idega.util.text.AttributeParser;
import com.idega.util.xml.XmlUtil;
/**
* @author <a href="mailto:tryggvi@idega.is">Tryggvi Larusson</a>
* @version 1.0
* HTML based Page component.
* This component can be used to template the look and feel
* of your site in a simple manner. You supply an HTML template
* directly.
* Inside the Layout template tags are used to define "regions" where UIComponent
* components can be added in and rendered dynamically.<br>
* The regions are defined like this:
*
* <code><pre>
* <HTML>... <BODY>... <!-- TemplateBeginEditable name="MyUniqueRegionId1" -->MyUniqueRegionId1<!-- TemplateEndEditable --> ... <table><tr><td><!-- TemplateBeginEditable name="MyUniqueRegionId2" -->MyUniqueRegionId2<!-- TemplateEndEditable --</td></tr></table>
* </pre></code>
*
* This class parses the HTML and looks for the tag <code><pre><!-- TemplateBeginEditable ... ></pre></code>
* Where the first region found becomes the "default".
* This class also parses the <code><pre> <HEAD> </pre></code> attribute contents and includes the things normally found inside
* an idegaWeb Page.
*/
public class HtmlPage extends Page {
private static final Logger LOGGER = Logger.getLogger(HtmlPage.class.getName());
private String html;
private Map<String, Integer> regionMap;
//This variable sets if regions are treated as facets if set to true. Otherwise they are treated as children
private boolean regionAsFacet;
public HtmlPage() {
super();
}
public void setResource(InputStream htmlStream) {
if (htmlStream != null) {
try {
setHtml(StringHandler.getContentFromInputStream(htmlStream));
} catch(Exception e) {
throw new RuntimeException("Attribute <resourceName> for component <" + getId() + ">. Could not load the html from named resource <>");
} finally {
IOUtil.closeInputStream(htmlStream);
}
}
findOutRegions();
}
/**
* Need to render my children myself. Typical for layout
* management components.
*
* @see javax.faces.component.UIComponent#getRendersChildren()
*/
@Override
public boolean getRendersChildren() {
return true;
}
/**
* Gets the default (first found) region.
* Returns null if none is found.
* @return
*/
public String getDefaultRegion(){
for (Iterator<String> iter = getRegionIdsMap().keySet().iterator(); iter.hasNext();) {
String key = iter.next();
Integer value = getRegionIdsMap().get(key);
if (value.intValue() == 0) {
return key;
}
}
return null;
}
/**
*
*/
@Override
public List<UIComponent> getChildren() {
return super.getChildren();
}
/**
*
*/
@Override
protected void setChildren(List<UIComponent> newChildren) {
super.setChildren(newChildren);
}
public UIComponent getRegion(String regionKey) {
if (this.regionAsFacet) {
return getFacets().get(regionKey);
}
else{
Integer index = getRegionIdsMap().get(regionKey);
if(index!=null){
Object o = getChildren().get(index.intValue());
UIComponent child = (UIComponent) o;
return child;
}
else{
return null;
}
}
}
public void setRegion(String regionKey, UIComponent region){
if (this.regionAsFacet) {
if (regionKey != null) {
getFacets().put(regionKey,region);
}
}
else {
getChildren().add(region);
}
}
public void add(UIComponent component, String regionId) {
UIComponent region = getRegion(regionId);
if (region != null) {
region.getChildren().add(component);
}
else{
getLogger().info("No Region found for regionId="+regionId);
}
}
@Override
public void add(UIComponent comp){
add(comp, getDefaultRegion());
}
@Override
public void add(PresentationObject po) {
add((UIComponent) po);
}
/**
* The Map over the regions.
* Has as a key the regionId and as the value the index of
* the corresponding HtmlPageRegion object int the getChildren() List.
* @return
*/
private Map<String, Integer> getRegionIdsMap() {
if (this.regionMap == null) {
this.regionMap = new HashMap<String, Integer>();
}
return this.regionMap;
}
/**
* Returns all the regionIds as Strings
* @return
*/
public Set<String> getRegionIds(){
return getRegionIdsMap().keySet();
}
private void findOutRegions(){
String template = getHtml();
if (template == null) {
LOGGER.info("There is no template for this page");
return;
}
String[] parts = template.split("<!-- TemplateBeginEditable");
int regionIndex=0;
for (int i = 1; i < parts.length; i++) {
String part = parts[i];
String[] t = part.split("TemplateEndEditable -->");
String toParse = t[0];
String[] a1 = toParse.split("name=\"");
if (a1.length < 2) {
LOGGER.warning("Invalid region pattern! Got part:\n" + toParse);
continue;
}
String[] a2 = a1[1].split("\"");
String regionId = a2[0];
getRegionIdsMap().put(regionId,new Integer(regionIndex));
// Instantiate the region in the children list:
HtmlPageRegion region = new HtmlPageRegion();
region.setRegionId(regionId);
setRegion(regionId,region);
regionIndex++;
}
}
/**
* Overrided from Page
*/
@Override
public void encodeBegin(FacesContext context)throws IOException{
//Does nothing here
}
/**
* Overrided from Page
* @throws IOException
*/
@Override
public void encodeChildren(FacesContext context) throws IOException{
//Does just call the print(iwc) method below:
callPrint(context);
}
/**
* Overrided from Page
*/
@Override
public void encodeEnd(FacesContext context)throws IOException{
//Does nothing here
encodeRenderTime(context);
}
@Override
public void print(IWContext ctx) throws IOException {
IWContext iwc = IWContext.getIWContext(ctx);
addSessionPollingDWRFiles(iwc);
addNotifications(iwc);
enableReverseAjax(iwc);
enableChromeFrame(iwc);
Writer out = IWMainApplication.useJSF ? ctx.getResponseWriter() : ctx.getWriter();
String template = getHtml();
if (template == null) {
out.write("Template file could not be found.");
out.close();
return;
}
// Process the HEAD first:
Pattern headOpensPattern = Pattern.compile("<head>", Pattern.CASE_INSENSITIVE);
String[] headOpensSplit = headOpensPattern.split(template);
String preHead = headOpensSplit[0];
String postHeadOpens = headOpensSplit[1];
out.write(preHead);
out.write("<head>");
Pattern headClosesPattern = Pattern.compile("</head>", Pattern.CASE_INSENSITIVE);
String[] headClosesSplit = headClosesPattern.split(postHeadOpens);
String headContent = headClosesSplit[0];
String body = headClosesSplit[1];
// Get the contents from the superclass first
out.write(getHeadContents(ctx));
Script associatedScript = getAssociatedScript();
renderChild(ctx,associatedScript);
// Then printout the head contents from the HTML page find out where the title is in the head:
String htmlTitle = CoreConstants.EMPTY, writtenTitle = null;
try {
// Try to find where the TITLE tag is in the HEAD:
Pattern titlePattern = Pattern.compile("<title>", Pattern.CASE_INSENSITIVE);
String[] titleSplit = titlePattern.split(headContent);
String preTitleHead= titleSplit[0];
String postTitleOpens = titleSplit[1];
Pattern postTitlePattern = Pattern.compile("</title>", Pattern.CASE_INSENSITIVE);
String[] postTitleSplit = postTitlePattern.split(postTitleOpens);
htmlTitle = postTitleSplit[0];
String postTitleHead = postTitleSplit[1];
// Print out all before the TITLE tag in the HEAD
out.write(preTitleHead);
// Print out the title from the idegaWeb page
String locTitle = this.getLocalizedTitle(ctx);
out.write("<title>");
if (StringUtil.isEmpty(locTitle)) {
writtenTitle = htmlTitle;
} else {
writtenTitle = locTitle;
}
out.write(writtenTitle);
out.write("</title>");
// Print out all after the TITLE tag in the HEAD
out.write(postTitleHead);
} catch (ArrayIndexOutOfBoundsException ae) {
// If there is an error (title not found) then just write out the whole head contents + idegaWeb Title
out.write(headContent);
String locTitle = this.getLocalizedTitle(ctx);
out.write("<title>");
if (StringUtil.isEmpty(locTitle)) {
writtenTitle = htmlTitle;
} else {
writtenTitle = locTitle;
}
out.write(writtenTitle);
out.write("</title>");
}
out.write("</head>");
String[] htmlBody = body.split("<body");
int index = 0;
if (htmlBody.length > 1) {
out.write(htmlBody[index++]);
}
body = htmlBody[index];
String attributes = body.substring(0, body.indexOf(">"));
Map<String, String> attributeMap = AttributeParser.parse(attributes);
for (Iterator<String> iter = attributeMap.keySet().iterator(); iter.hasNext();) {
String attribute = iter.next();
String value = attributeMap.get(attribute);
if (attribute.equals("onload")) {
this.setMarkupAttributeMultivalued("onload", value);
}
if (attribute.equals("onunload")) {
this.setMarkupAttributeMultivalued("onunload", value);
}
else {
if (!isMarkupAttributeSet(attribute)) {
setMarkupAttribute(attribute, value);
}
}
}
String attributesString = getMarkupAttributesString();
out.write("<body" + (StringUtil.isEmpty(attributesString) ? CoreConstants.EMPTY : (CoreConstants.SPACE + attributesString + CoreConstants.SPACE)) + ">\n");
body = body.substring(body.indexOf(">") + 1);
// Process the template regions:
String[] parts = body.split("<!-- TemplateBeginEditable");
out.write(parts[0]);
for (int i = 1; i < parts.length; i++) {
String part = parts[i];
String[] t = part.split("TemplateEndEditable -->");
String toParse = t[0];
String[] a1 = toParse.split("name=\"");
if (a1.length < 2) {
LOGGER.warning("Invalid region pattern! Unable to extract region's ID! Got region's part:\n" + toParse);
continue;
}
String[] a2 = a1[1].split("\"");
String regionId = a2[0];
try {
UIComponent region = getRegion(regionId);
renderChild(ctx,region);
}
catch (ClassCastException cce) {
LOGGER.log(Level.WARNING, "Error occured while rendering UI component", cce);
}
if (t.length < 2) {
LOGGER.warning("Invalid region pattern! Got template's part:\n" + part);
} else {
out.write(t[1]);
}
}
for (UIComponent child: getChildren()) {
if (!(child instanceof HtmlPageRegion)) {
renderChild(ctx, child);
}
}
Map<?, ?> renderUtils = WebApplicationContextUtils.getWebApplicationContext(iwc.getServletContext()).getBeansOfType(RenderUtil.class);
if (!MapUtil.isEmpty(renderUtils)) {
String newTitle = getLocalizedTitle(iwc);
for (Object renderUtil: renderUtils.values()) {
if (renderUtil instanceof RenderUtil) {
((RenderUtil) renderUtil).doRemoveNeedlessContentAndSetRealPageTitle(out, newTitle, writtenTitle);
}
}
}
out.close();
}
/**
* @see javax.faces.component.UIPanel#saveState(javax.faces.context.FacesContext)
*/
@Override
public Object saveState(FacesContext ctx) {
Object values[] = new Object[4];
values[0] = super.saveState(ctx);
values[1] = getHtml();
values[2] = this.regionMap;
values[3] = Boolean.valueOf(this.regionAsFacet);
return values;
}
/**
* @see javax.faces.component.UIPanel#restoreState(javax.faces.context.FacesContext, java.lang.Object)
*/
@SuppressWarnings("unchecked")
@Override
public void restoreState(FacesContext ctx, Object state) {
Object values[] = (Object[])state;
super.restoreState(ctx, values[0]);
setHtml((String)values[1]);
this.regionMap = (Map<String, Integer>) values[2];
this.regionAsFacet = ((Boolean)values[3]).booleanValue();
}
/**
* @return
*/
public String getHtml() {
return this.html;
}
/**
* @param string
*/
public void setHtml(String string) {
this.html = string;
findOutRegions();
findOutResources();
}
private void findOutResources() {
if (StringUtil.isEmpty(html)) {
return;
}
Document templateDoc = XmlUtil.getJDOMXMLDocument(html, false);
if (templateDoc == null) {
Logger.getLogger(getClass().getName()).warning("Unable to optimize resources! Template file can not be resolved!");
return;
}
Element root = templateDoc.getRootElement();
Namespace namespace = root.getNamespace();
List<Element> uselessElements = new ArrayList<Element>();
List<Element> javaScripts = XmlUtil.getElementsByXPath(templateDoc, "script", namespace);
if (!ListUtil.isEmpty(javaScripts)) {
for (Element script: javaScripts) {
Attribute source = script.getAttribute("src");
if (source == null || StringUtil.isEmpty(source.getValue())) {
String action = script.getValue();
if (!StringUtil.isEmpty(action)) {
addJavaScriptAction(action);
}
} else {
String sourceUri = source.getValue();
if (StringUtil.isEmpty(sourceUri)) {
break;
}
addJavascriptURL(sourceUri);
}
uselessElements.add(script);
}
}
List<Element> styleSheets = XmlUtil.getElementsByXPath(templateDoc, "link", namespace);
if (!ListUtil.isEmpty(styleSheets)) {
for (Element style: styleSheets) {
Attribute source = style.getAttribute("href");
if (source == null) {
break;
}
String sourceUri = source.getValue();
if (StringUtil.isEmpty(sourceUri)) {
break;
}
Attribute media = style.getAttribute("media");
if (media == null) {
break;
}
addStyleSheetURL(sourceUri, media.getValue());
uselessElements.add(style);
}
}
if (uselessElements.size() > 0) {
for (Iterator<Element> uselessElementsIter = uselessElements.iterator(); uselessElementsIter.hasNext();) {
uselessElementsIter.next().detach();
}
this.html = XmlUtil.getPrettyJDOMDocument(templateDoc);
}
}
@SuppressWarnings("unchecked")
@Override
public Object clone(IWUserContext iwc, boolean askForPermission){
HtmlPage newPage = (HtmlPage) super.clone(iwc,askForPermission);
if (this.regionMap != null) {
newPage.regionMap = (Map<String, Integer>) ((HashMap<String, Integer>) this.regionMap).clone();
}
return newPage;
}
@Override
public void main(IWContext iwc) throws Exception {
super.main(iwc);
}
/* (non-Javadoc)
* @see com.idega.presentation.PresentationObject#_main(com.idega.presentation.IWContext)
*/
@Override
public void _main(IWContext iwc) throws Exception {
super._main(iwc);
}
/**
* This variable gets if regions are treated as facets if set to true.
* Default is false.
* @return Returns the regionAsFacet.
*/
protected boolean isRegionAsFacet() {
return this.regionAsFacet;
}
/**
* This sets if regions are treated as facets if set to true. Otherwise they are treated as children.
* Default value is set to false.
* @param regionAsFacet The regionAsFacet to set.
*/
protected void setRegionAsFacet(boolean regionAsFacet) {
this.regionAsFacet = regionAsFacet;
}
@Override
public Map<String, UIComponent> getFacets() {
if (this.facetMap == null) {
this.facetMap = new HtmlPageRegionFacetMap(this);
}
return this.facetMap;
}
}