org.infoglue.deliver.invokers.PageInvoker.java Source code

Java tutorial

Introduction

Here is the source code for org.infoglue.deliver.invokers.PageInvoker.java

Source

/* ===============================================================================
 *
 * Part of the InfoGlue Content Management Platform (www.infoglue.org)
 *
 * ===============================================================================
 *
 *  Copyright (C)
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2, as published by the
 * Free Software Foundation. See the file LICENSE.html for more information.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc. / 59 Temple
 * Place, Suite 330 / Boston, MA 02111-1307 / USA.
 *
 * ===============================================================================
 */

package org.infoglue.deliver.invokers;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.exolab.castor.jdo.Database;
import org.infoglue.cms.applications.common.VisualFormatter;
import org.infoglue.cms.controllers.kernel.impl.simple.ContentVersionController;
import org.infoglue.cms.controllers.kernel.impl.simple.PageDeliveryMetaDataController;
import org.infoglue.cms.controllers.kernel.impl.simple.RepositoryController;
import org.infoglue.cms.entities.content.ContentVersionVO;
import org.infoglue.cms.entities.content.SmallestContentVersionVO;
import org.infoglue.cms.entities.management.LanguageVO;
import org.infoglue.cms.entities.management.PageDeliveryMetaDataEntityVO;
import org.infoglue.cms.entities.management.PageDeliveryMetaDataVO;
import org.infoglue.cms.entities.management.RepositoryVO;
import org.infoglue.cms.entities.structure.SiteNode;
import org.infoglue.cms.entities.structure.SiteNodeVO;
import org.infoglue.cms.exception.Bug;
import org.infoglue.cms.exception.NoBaseTemplateFoundException;
import org.infoglue.cms.exception.SystemException;
import org.infoglue.cms.io.FileHelper;
import org.infoglue.cms.util.CmsPropertyHandler;
import org.infoglue.deliver.applications.databeans.DatabaseWrapper;
import org.infoglue.deliver.applications.databeans.DeliveryContext;
import org.infoglue.deliver.cache.PageCacheHelper;
import org.infoglue.deliver.controllers.kernel.URLComposer;
import org.infoglue.deliver.controllers.kernel.impl.simple.LanguageDeliveryController;
import org.infoglue.deliver.controllers.kernel.impl.simple.NodeDeliveryController;
import org.infoglue.deliver.controllers.kernel.impl.simple.TemplateController;
import org.infoglue.deliver.portal.PortalController;
import org.infoglue.deliver.util.CacheController;
import org.infoglue.deliver.util.CompressionHelper;
import org.infoglue.deliver.util.JSMin;
import org.infoglue.deliver.util.RequestAnalyser;
import org.infoglue.deliver.util.Timer;

/**
 * @author Mattias Bogeblad
 *
 * This interface defines what a Invoker of a page have to be able to do.
 * The invokers are used to deliver a page to the user in a certain fashion.
 *
 */

public abstract class PageInvoker {
    private final static Logger logger = Logger.getLogger(PageInvoker.class.getName());

    private static CompressionHelper compressionHelper = new CompressionHelper();
    private final static VisualFormatter vf = new VisualFormatter();

    private DatabaseWrapper dbWrapper = null;
    private HttpServletRequest request = null;
    private HttpServletResponse response = null;
    private TemplateController templateController = null;
    private DeliveryContext deliveryContext = null;

    private String pageString = null;

    /*public PageInvoker()
    {
    }
    */

    /**
     * The default constructor for PageInvokers. 
     * @param request
     * @param response
     * @param templateController
     * @param deliveryContext
     */
    /*
    public PageInvoker(HttpServletRequest request, HttpServletResponse response, TemplateController templateController, DeliveryContext deliveryContext)
    {
       this.request = request;
       this.response = response;
       this.templateController = templateController;
       this.deliveryContext = deliveryContext;
       this.templateController.setDeliveryContext(this.deliveryContext);
    }
    */

    /**
     * This method should return an instance of the class that should be used for page editing inside the tools or in working. 
     * Makes it possible to have an alternative to the ordinary delivery optimized class.
     */

    public abstract PageInvoker getDecoratedPageInvoker(TemplateController templateController,
            DeliveryContext deliveryContext) throws SystemException;

    /**
     * The default initializer for PageInvokers. 
     * @param request
     * @param response
     * @param templateController
     * @param deliveryContext
     */

    public void setParameters(DatabaseWrapper dbWrapper, HttpServletRequest request, HttpServletResponse response,
            TemplateController templateController, DeliveryContext deliveryContext) {
        this.dbWrapper = dbWrapper;
        this.request = request;
        this.response = response;
        this.templateController = templateController;
        this.deliveryContext = deliveryContext;
        this.templateController.setDeliveryContext(this.deliveryContext);
    }

    public Database getDatabase() throws SystemException {
        /*
        if(this.db == null || this.db.isClosed() || !this.db.isActive())
        {
        beginTransaction();
        }
        */
        return dbWrapper.getDatabase();
    }

    /**
     * This is the method that will deliver the page to the user. It can have special
     * handling of all sorts to enable all sorts of handlers. An example of uses might be to
     * be to implement a WAP-version of page delivery where you have to set certain headers in the response
     * or a redirect page which just redirects you to another page.  
     */

    public abstract void invokePage() throws SystemException, Exception;

    /**
     * This method is used to send the page out to the browser or other device.
     * Override this if you need to set other headers or do other specialized things.
     */

    public void deliverPage() throws NoBaseTemplateFoundException, Exception {
        if (logger.isInfoEnabled()) {
            logger.info("PageKey:" + this.getDeliveryContext().getPageKey());
            logger.info("PageCache:" + this.getDeliveryContext().getDisablePageCache());
        }

        LanguageVO languageVO = LanguageDeliveryController.getLanguageDeliveryController()
                .getLanguageVO(getDatabase(), this.getTemplateController().getLanguageId());

        if (logger.isInfoEnabled())
            logger.info("languageVO:" + languageVO);

        if (languageVO == null)
            throw new SystemException("There was no such active language for the page with languageId:"
                    + this.getTemplateController().getLanguageId());

        String isPageCacheOn = CmsPropertyHandler.getIsPageCacheOn();
        String refresh = this.getRequest().getParameter("refresh");

        String pageCacheName = "pageCache"; // + this.getTemplateController().getRepositoryId();
        String pageCacheExtraName = "pageCacheExtra"; // + this.getTemplateController().getRepositoryId();

        if (logger.isInfoEnabled())
            logger.info("isPageCacheOn:" + isPageCacheOn);

        if (isPageCacheOn.equalsIgnoreCase("true") && (refresh == null || !refresh.equalsIgnoreCase("true"))
                && getRequest().getMethod().equals("GET")) {
            Map cachedExtraData = null;

            Integer pageCacheTimeout = (Integer) CacheController.getCachedObjectFromAdvancedCache("pageCacheExtra",
                    this.getDeliveryContext().getPageKey() + "_pageCacheTimeout");
            if (pageCacheTimeout == null)
                pageCacheTimeout = this.getTemplateController().getPageCacheTimeout();

            if (pageCacheTimeout != null && pageCacheTimeout > 0) {
                Object pageCacheFileName = CacheController.getCachedObjectFromAdvancedCache(pageCacheName,
                        this.getDeliveryContext().getPageKey(), pageCacheTimeout.intValue(), false, "utf-8", false);
                if (pageCacheFileName == null || pageCacheFileName.equals("")) {
                    PageCacheHelper.getInstance().clearPageCacheASAP(this.getDeliveryContext().getPageKey());
                }
            }

            Class[] argsClasses = new Class[2];
            argsClasses[0] = String.class;
            argsClasses[1] = String.class;

            Object[] args = new Object[] { pageCacheName, pageCacheExtraName };

            this.pageString = (String) CacheController.getCachedObjectFromAdvancedCache(pageCacheName,
                    this.getDeliveryContext().getPageKey(), true, "utf-8", true, this,
                    this.getClass().getMethod("invokeAndDecoratePage", argsClasses), args, this);
            cachedExtraData = (Map) CacheController.getCachedObjectFromAdvancedCache(pageCacheExtraName,
                    this.getDeliveryContext().getPageKey());

            if (this.pageString != null)
                this.getDeliveryContext().setIsCachedResponse(true);

            logger.info("this.getDeliveryContext().getIsCachedResponse():"
                    + this.getDeliveryContext().getIsCachedResponse());
            if (this.getDeliveryContext().getIsCachedResponse()) {
                String usedEntitiesString = null;
                if (CmsPropertyHandler.getOperatingMode().equals("0")) {
                    usedEntitiesString = (String) CacheController.getCachedObjectFromAdvancedCache("pageCacheExtra",
                            this.getDeliveryContext().getPageKey() + "_entitiesAsByte");
                } else {
                    byte[] usedEntitiesByteArray = (byte[]) CacheController.getCachedObjectFromAdvancedCache(
                            "pageCacheExtra", this.getDeliveryContext().getPageKey() + "_entitiesAsByte");
                    if (usedEntitiesByteArray != null)
                        usedEntitiesString = compressionHelper.decompress(usedEntitiesByteArray);
                }

                if (logger.isInfoEnabled())
                    logger.info("usedEntitiesString:" + usedEntitiesString);
                if (usedEntitiesString != null) {
                    String[] usedEntities = StringUtils.split(usedEntitiesString, "|");
                    for (String usedEntity : usedEntities) {
                        if (usedEntity.startsWith("content_"))
                            this.getDeliveryContext().addUsedContent(usedEntity);
                        else if (usedEntity.startsWith("contentVersion_"))
                            this.getDeliveryContext().addUsedContentVersion(usedEntity);
                        else if (usedEntity.startsWith("siteNode_"))
                            this.getDeliveryContext().addUsedSiteNode(usedEntity);
                        else if (usedEntity.startsWith("siteNodeVersion_"))
                            this.getDeliveryContext().addUsedSiteNodeVersion(usedEntity);
                    }
                }
            }

            getLastModifiedDateTime(true);

            if (cachedExtraData != null)
                this.getDeliveryContext().populateExtraData(cachedExtraData);

            //Caching the pagePath
            this.getDeliveryContext().setPagePath((String) CacheController.getCachedObject("pagePathCache",
                    this.getDeliveryContext().getPageKey()));
            if (this.getDeliveryContext().getPagePath() == null) {
                this.getDeliveryContext().setPagePath(this.getTemplateController().getCurrentPagePath());

                if (!this.getTemplateController().getIsPageCacheDisabled()
                        && !this.getDeliveryContext().getDisablePageCache()) //Caching page path if not disabled
                    CacheController.cacheObject("pagePathCache", this.getDeliveryContext().getPageKey(),
                            this.getDeliveryContext().getPagePath());
            }

            if (logger.isInfoEnabled())
                logger.info("Done caching the pagePath...");
        } else {
            invokePage();
            this.pageString = getPageString();

            //TEST
            getLastModifiedDateTime(false);
            //END TEST

            pageString = decorateHeadAndPageWithVarsFromComponents(pageString);

            this.getDeliveryContext().setPagePath(this.templateController.getCurrentPagePath());
        }

        if (this.getRequest().getParameter("includeUsedEntities") != null
                && this.getRequest().getParameter("includeUsedEntities").equals("true")
                && (!CmsPropertyHandler.getOperatingMode().equals("3")
                        || CmsPropertyHandler.getLivePublicationThreadClass()
                                .equalsIgnoreCase("org.infoglue.deliver.util.SelectiveLivePublicationThread"))) {
            StringBuilder sb = new StringBuilder("<usedEntities>");
            String[] usedEntities = this.getDeliveryContext().getAllUsedEntities();
            for (int i = 0; i < usedEntities.length; i++)
                sb.append(usedEntities[i]).append(",");
            sb.append("</usedEntities>");

            if (this.getTemplateController().getPageContentType() != null
                    && (this.getTemplateController().getPageContentType().equalsIgnoreCase("text/css") || this
                            .getTemplateController().getPageContentType().equalsIgnoreCase("text/javascript")))
                this.pageString = this.pageString + "/*" + sb.toString() + "*/";
            else
                this.pageString = this.pageString + "<!--" + sb.toString() + "-->";
        }

        String contentType = this.getTemplateController().getPageContentType();
        if (this.deliveryContext.getContentType() != null
                && !contentType.equalsIgnoreCase(this.deliveryContext.getContentType()))
            contentType = this.deliveryContext.getContentType();

        if (!CmsPropertyHandler.getOperatingMode().equals("3")) {
            getResponse().setHeader("Cache-Control", "no-cache");
            getResponse().setHeader("Pragma", "no-cache");
            getResponse().setDateHeader("Expires", 0);
        }

        //logger.info("pageString before:" + pageString);
        //pageString = decorateHeadAndPageWithVarsFromComponents(pageString);
        //logger.info("pageString after:" + pageString);

        try {
            //logger.info("ContentType:" + contentType);
            String charSet = languageVO.getCharset();
            if (contentType.indexOf("charset=") > -1) {
                try {
                    int startIndex = contentType.indexOf("charset=");
                    int endIndex = contentType.indexOf(";", startIndex + 1);

                    if (endIndex != -1)
                        charSet = contentType.substring(startIndex + "charset=".length(), endIndex).trim();
                    else
                        charSet = contentType.substring(startIndex + "charset=".length()).trim();

                    if (logger.isInfoEnabled())
                        logger.info("Found a user defined charset: " + charSet);
                } catch (Exception e) {
                    logger.warn("Error parsing charset:" + e.getMessage());
                }
                this.getResponse().setContentType(contentType);
            } else
                this.getResponse().setContentType(contentType + "; charset=" + languageVO.getCharset());

            if (logger.isInfoEnabled())
                logger.info("Using charset: " + charSet);

            Iterator headersIterator = this.getDeliveryContext().getHttpHeaders().keySet().iterator();
            while (headersIterator.hasNext()) {
                String key = (String) headersIterator.next();
                Object valueObject = this.getDeliveryContext().getHttpHeaders().get(key);
                if (valueObject instanceof Date) {
                    Date value = (Date) valueObject;
                    this.getResponse().setDateHeader(key, value.getTime());
                } else {
                    String value = (String) valueObject;
                    this.getResponse().setHeader(key, value);
                }
            }

            if (logger.isInfoEnabled())
                logger.info("contentType:" + contentType + "; charset=" + languageVO.getCharset());

            String compressPageResponse = CmsPropertyHandler.getCompressPageResponse();
            if (logger.isInfoEnabled())
                logger.info("compressPageResponse:" + compressPageResponse);

            if (compressPageResponse != null && compressPageResponse.equalsIgnoreCase("true")) {
                OutputStream out = null;

                String encodings = this.getRequest().getHeader("Accept-Encoding");
                if (encodings != null && encodings.indexOf("gzip") != -1) {
                    this.getResponse().setHeader("Content-Encoding", "gzip");
                    out = new GZIPOutputStream(this.getResponse().getOutputStream());
                } else if (encodings != null && encodings.indexOf("compress") != -1) {
                    this.getResponse().setHeader("Content-Encoding", "x-compress");
                    out = new ZipOutputStream(this.getResponse().getOutputStream());
                    ((ZipOutputStream) out).putNextEntry(new ZipEntry("dummy name"));
                } else {
                    out = this.getResponse().getOutputStream();
                }

                if (pageString != null)
                    out.write(pageString.getBytes(charSet));
                else
                    out.write("Error: got null pagestring".getBytes(charSet));

                out.flush();
                out.close();
            } else {
                PrintWriter out = this.getResponse().getWriter();
                out.println(pageString);
                out.flush();
                out.close();
            }

            if (logger.isInfoEnabled())
                logger.info("sent all data to client:" + pageString.length());
        } catch (IllegalStateException e) {
            logger.error("There was an IllegalStateException when trying to write output for URL: "
                    + this.getTemplateController().getOriginalFullURL() + "\nMessage: " + e.getMessage());
        }

        //New solution which enables us to defeat individual page caches in flow.
        try {
            String pageKey = this.getDeliveryContext().getPageKey();
            if (logger.isInfoEnabled())
                logger.info("pageKey:" + pageKey);
            if (refresh != null && refresh.equalsIgnoreCase("true")) {
                String originalQueryString = this.getDeliveryContext().getOriginalQueryString();
                logger.info("originalQueryString:" + originalQueryString);
                logger.info("Clearing page cache....");
                if (originalQueryString.equalsIgnoreCase("refresh=true"))
                    pageKey = pageKey.replaceFirst("refresh=true", "null");
                else if (originalQueryString.indexOf("&refresh=true") > -1)
                    pageKey = pageKey.replaceFirst("&refresh=true", "");
                else if (originalQueryString.indexOf("refresh=true&") == 0)
                    pageKey = pageKey.replaceFirst("refresh=true&", "");
                logger.info("pageKey:" + pageKey);
                //Timer t = new Timer();
                CacheController.clearPageCache(pageKey);
                //t.printElapsedTime("Clearing page cache took...");

                //CacheController.clearCaches(SiteNodeImpl.class.getName(), "" + this.getTemplateController().getSiteNodeId(), null);
                //t.printElapsedTime("Clearing caches took...");
            }
        } catch (Exception e) {
            logger.error("Could not clear the pagecache:" + e.getMessage());
        }
    }

    public String invokeAndDecoratePage(String pageCacheName, String pageCacheExtraName)
            throws SystemException, Exception, Bug {
        if (this.pageString == null) {
            invokePage();
            this.pageString = getPageString();

            //TEST
            getLastModifiedDateTime(false);
            //END TEST

            this.pageString = decorateHeadAndPageWithVarsFromComponents(pageString);
        } else {
            if (logger.isInfoEnabled())
                logger.info("There was a cached copy..."); // + pageString);
        }
        return this.pageString;
    }

    private void getLastModifiedDateTime(boolean useContentLookup) throws Bug {
        //if(CmsPropertyHandler.getOperatingMode().equals("3") && CmsPropertyHandler.getSetDerivedLastModifiedInLive().equalsIgnoreCase("false"))
        //   return;

        //Integer maxNumberOfVersionsForDerivedLastModifiedInLive = CmsPropertyHandler.getMaxNumberOfVersionsForDerivedLastModifiedInLive();

        Date lastModifiedDateTime = null;
        logger.info("useContentLookup:" + useContentLookup);
        logger.info("UsedContentVersions:" + this.deliveryContext.getUsedContentVersions().size());
        logger.info("UsedContents:" + this.deliveryContext.getUsedContents().size());
        if (this.deliveryContext.getUsedContents().size() > 1000
                && CmsPropertyHandler.getOperatingMode().equals("0"))
            logger.warn("The page " + this.deliveryContext.getSiteNodeId() + "("
                    + this.deliveryContext.getOriginalFullURL() + ") has unhealthy number of relations");
        Timer t = new Timer();
        if (this.deliveryContext.getUsedContentVersions().size() > 0
                || (useContentLookup && this.deliveryContext.getUsedContents().size() > 0)) {
            try {
                SmallestContentVersionVO lastContentVersionVO = null;
                if (useContentLookup)
                    lastContentVersionVO = ContentVersionController.getContentVersionController()
                            .getLatestContentVersionVOByContentIds(this.deliveryContext.getUsedContents(),
                                    getDatabase());
                else
                    lastContentVersionVO = ContentVersionController.getContentVersionController()
                            .getLatestContentVersionVO(this.deliveryContext.getUsedContentVersions(),
                                    getDatabase());
                logger.info("lastContentVersionVO:" + lastContentVersionVO);
                if (lastContentVersionVO != null)
                    lastModifiedDateTime = lastContentVersionVO.getModifiedDateTime();
                logger.info("lastModifiedDateTime from cvVO:" + lastContentVersionVO.getModifiedDateTime());
            } catch (Exception e) {
                e.printStackTrace();
            }
            /*
            Iterator userContentVersionIterator = this.deliveryContext.getUsedContentVersions().iterator();
            int processed = 0;
            while(userContentVersionIterator.hasNext())
            {
               String usedContentVersion = (String)userContentVersionIterator.next();   
               if(usedContentVersion != null && usedContentVersion.startsWith("contentVersion_"))
                {
            try
              {
               String versionId = usedContentVersion.substring(15);
               if(!versionId.equals("null") && !versionId.equals(""))
               {
                  processed++;
                  Integer contentVersionId = new Integer(versionId);
                  //SmallestContentVersionVO contentVersion = ContentVersionController.getContentVersionController().getSmallestContentVersionVOWithId(contentVersionId, getDatabase());
                  ContentVersionVO contentVersion = ContentVersionController.getContentVersionController().getSmallContentVersionVOWithId(contentVersionId, getDatabase());
                  if(lastModifiedDateTime == null || contentVersion.getModifiedDateTime().after(lastModifiedDateTime))
                  {
                     lastModifiedDateTime = contentVersion.getModifiedDateTime();
                     System.out.println("lastModifiedDateTime:" + lastModifiedDateTime);
                  }
               }
              }
            catch (Exception e) 
            {
               logger.warn("Could not use the versionId:" + usedContentVersion + " - reason:" + e.getMessage());
                  }
                }
               if(lastModifiedDateTime != null)
               {
                  long current = System.currentTimeMillis() - lastModifiedDateTime.getTime();
                  if(processed > maxNumberOfVersionsForDerivedLastModifiedInLive || current < (1000 * 60 * 10))
              break;
               }
            }
            */

            if (lastModifiedDateTime != null) {
                logger.info("The page gets " + lastModifiedDateTime);
                this.deliveryContext.setLastModifiedDateTime(lastModifiedDateTime);
            }
        }

        long elapsedTime = t.getElapsedTime();
        RequestAnalyser.getRequestAnalyser().registerComponentStatistics("getLastModifiedDateTime", elapsedTime);
    }

    protected String decorateHeadAndPageWithVarsFromComponents(String pageString) {

        pageString = this.getTemplateController().decoratePage(pageString);

        StringBuilder sb = null;
        Timer t = new Timer();
        this.generateExtensionBundles(
                this.getTemplateController().getDeliveryContext().getScriptExtensionHeadBundles(),
                "text/javascript", "head");
        this.generateExtensionBundles(
                this.getTemplateController().getDeliveryContext().getScriptExtensionBodyBundles(),
                "text/javascript", "body");
        this.generateExtensionBundles(this.getTemplateController().getDeliveryContext().getCSSExtensionBundles(),
                "text/css", "head");

        List htmlHeadItems = this.getTemplateController().getDeliveryContext().getHtmlHeadItems();
        if (htmlHeadItems != null && htmlHeadItems.size() > 0) {
            int indexOfHeadEndTag = pageString.indexOf("</head");
            if (indexOfHeadEndTag == -1)
                indexOfHeadEndTag = pageString.indexOf("</HEAD");

            if (indexOfHeadEndTag != -1) {
                sb = new StringBuilder(pageString);
                String headerItems = "";
                Iterator htmlHeadItemsIterator = htmlHeadItems.iterator();
                while (htmlHeadItemsIterator.hasNext()) {
                    String value = (String) htmlHeadItemsIterator.next();
                    //logger.info("headItem:" + value);
                    headerItems = headerItems + value + "\n";
                }
                sb.insert(indexOfHeadEndTag, headerItems);
                //pageString = sb.toString();
            }
        }

        List<String> htmlBodyEndItems = this.getTemplateController().getDeliveryContext().getHtmlBodyEndItems();
        if (htmlBodyEndItems != null && htmlBodyEndItems.size() > 0) {
            if (sb == null)
                sb = new StringBuilder(pageString);

            int indexOfBodyEndTag = sb.indexOf("</body");
            if (indexOfBodyEndTag == -1)
                indexOfBodyEndTag = sb.indexOf("</BODY");

            if (indexOfBodyEndTag != -1) {
                String bodyItems = "";
                Iterator htmlBodyItemsIterator = htmlBodyEndItems.iterator();
                while (htmlBodyItemsIterator.hasNext()) {
                    String value = (String) htmlBodyItemsIterator.next();
                    //logger.info("headItem:" + value);
                    bodyItems = bodyItems + value + "\n";
                }
                sb.insert(indexOfBodyEndTag, bodyItems);
                //pageString = sb.toString();
            }
        }
        RequestAnalyser.getRequestAnalyser().registerComponentStatistics("pageInvoker", t.getElapsedTime());

        try {
            int lastModifiedDateTimeIndex;
            if (sb == null)
                lastModifiedDateTimeIndex = pageString.indexOf("<ig:lastModifiedDateTime");
            else
                lastModifiedDateTimeIndex = sb.indexOf("<ig:lastModifiedDateTime");

            //logger.info("OOOOOOOOOOOOO lastModifiedDateTimeIndex:" + lastModifiedDateTimeIndex);
            if (lastModifiedDateTimeIndex > -1) {
                if (sb == null)
                    sb = new StringBuilder(pageString);

                int lastModifiedDateTimeEndIndex = sb.indexOf("</ig:lastModifiedDateTime>",
                        lastModifiedDateTimeIndex);

                String tagInfo = sb.substring(lastModifiedDateTimeIndex, lastModifiedDateTimeEndIndex);
                //logger.info("tagInfo:" + tagInfo);
                String dateFormat = "yyyy-MM-dd HH:mm";
                int formatStartIndex = tagInfo.indexOf("format");
                if (formatStartIndex > -1) {
                    int formatEndIndex = tagInfo.indexOf("\"", formatStartIndex + 8);
                    if (formatEndIndex > -1)
                        dateFormat = tagInfo.substring(formatStartIndex + 8, formatEndIndex);
                }
                //logger.info("dateFormat:" + dateFormat);

                String dateString = vf.formatDate(
                        this.getTemplateController().getDeliveryContext().getLastModifiedDateTime(),
                        this.getTemplateController().getLocale(), dateFormat);
                //logger.info("dateString:" + dateString);
                sb.replace(lastModifiedDateTimeIndex,
                        lastModifiedDateTimeEndIndex + "</ig:lastModifiedDateTime>".length(), dateString);
                //logger.info("Replaced:" + lastModifiedDateTimeIndex + " to " + lastModifiedDateTimeEndIndex + "</ig:lastModifiedDateTime>".length() + " with " + dateString);
            }
        } catch (Exception e) {
            logger.error("Problem setting lastModifiedDateTime:" + e.getMessage(), e);
        }

        if (sb != null)
            pageString = sb.toString();

        return pageString;
    }

    private void generateExtensionBundles(Map<String, Set<String>> extensionBundles, String contentType,
            String targetElement) {
        Timer t = new Timer();

        Set<String> bundledSignatures = new HashSet<String>();

        Iterator<String> scriptExtensionBundlesIterator = extensionBundles.keySet().iterator();
        while (scriptExtensionBundlesIterator.hasNext()) {
            String bundleName = scriptExtensionBundlesIterator.next();
            if (logger.isInfoEnabled())
                logger.info("bundleName:" + bundleName);

            Set<String> scriptExtensionFileNames = extensionBundles.get(bundleName);

            if (scriptExtensionFileNames != null && scriptExtensionFileNames.size() > 0) {
                String scriptBundle = "";

                int i = 0;
                String filePath = CmsPropertyHandler.getDigitalAssetPath0();
                while (filePath != null) {
                    try {
                        File extensionsDirectory = new File(filePath + File.separator + "extensions");
                        extensionsDirectory.mkdirs();

                        File extensionsBundleFile = new File(
                                filePath + File.separator + "extensions" + File.separator + bundleName + ".js");
                        if (contentType.equalsIgnoreCase("text/css"))
                            extensionsBundleFile = new File(filePath + File.separator + "extensions"
                                    + File.separator + bundleName + ".css");

                        if (!extensionsBundleFile.exists()) {
                            if (logger.isInfoEnabled())
                                logger.info("No script - generating:" + bundleName);
                            if (scriptBundle.equals("")) {
                                Iterator<String> scriptExtensionFileNamesIterator = scriptExtensionFileNames
                                        .iterator();
                                while (scriptExtensionFileNamesIterator.hasNext()) {
                                    String scriptExtensionFileName = scriptExtensionFileNamesIterator.next();
                                    if (logger.isInfoEnabled())
                                        logger.info("scriptExtensionFileName:" + scriptExtensionFileName);
                                    try {
                                        File file = new File(filePath + File.separator + scriptExtensionFileName);
                                        String signature = "" + file.getName() + "_" + file.length();
                                        if (logger.isInfoEnabled())
                                            logger.info("Checking file:" + filePath + File.separator
                                                    + scriptExtensionFileName);

                                        if (file.exists() && !bundledSignatures.contains(signature)) {
                                            StringBuffer content = new StringBuffer(
                                                    FileHelper.getFileAsStringOpt(file));
                                            //Wonder what is the best signature..
                                            bundledSignatures.add(signature);

                                            //If CSS we should change url:s to point to the original folder
                                            if (contentType.equalsIgnoreCase("text/css")) {
                                                if (logger.isInfoEnabled())
                                                    logger.info("contentType:" + contentType);
                                                String extensionPath = file.getPath().substring(
                                                        extensionsDirectory.getPath().length() + 1,
                                                        file.getPath().lastIndexOf("/") + 1);
                                                if (logger.isInfoEnabled())
                                                    logger.info("extensionPath:" + extensionPath);

                                                int urlStartIndex = content.indexOf("url(");
                                                while (urlStartIndex > -1) {
                                                    if (content.charAt(urlStartIndex + 4) == '"'
                                                            || content.charAt(urlStartIndex + 4) == '\'')
                                                        content.insert(urlStartIndex + 5, extensionPath);
                                                    else
                                                        content.insert(urlStartIndex + 4, extensionPath);
                                                    urlStartIndex = content.indexOf("url(",
                                                            urlStartIndex + extensionPath.length());
                                                }
                                            }
                                            //logger.info("transformed content:" + content.substring(0, 500));

                                            scriptBundle = scriptBundle + "\n\n" + content;
                                        } else {
                                            if (logger.isInfoEnabled())
                                                logger.info("Not adding:" + signature + " as " + file.exists() + ":"
                                                        + bundledSignatures.contains(signature));
                                        }
                                    } catch (Exception e) {
                                        logger.warn("Error trying to parse file and bundle it ("
                                                + scriptExtensionFileName + "):" + e.getMessage());
                                    }
                                }
                            }

                            if (logger.isInfoEnabled())
                                logger.info("scriptBundle:" + scriptBundle.length());
                            if (scriptBundle != null && !scriptBundle.equals("")) {
                                if (contentType.equalsIgnoreCase("text/javascript")) {
                                    try {
                                        JSMin jsmin = new JSMin(new ByteArrayInputStream(scriptBundle.getBytes()),
                                                new FileOutputStream(extensionsBundleFile));
                                        jsmin.jsmin();
                                    } catch (FileNotFoundException e) {
                                        e.printStackTrace();
                                    }
                                } else {
                                    FileHelper.writeToFile(extensionsBundleFile, scriptBundle, false);
                                }
                                if (logger.isInfoEnabled())
                                    logger.info("extensionsBundleFile:" + extensionsBundleFile.length());
                            }
                        }

                        i++;
                        filePath = CmsPropertyHandler.getProperty("digitalAssetPath." + i);
                    } catch (Exception e) {
                        logger.warn("Error trying to write bundled scripts:" + e.getMessage());
                    }
                }
                try {
                    SiteNodeVO siteNodeVO = NodeDeliveryController.getNodeDeliveryController(deliveryContext)
                            .getSiteNodeVO(getDatabase(), getTemplateController().getSiteNodeId());
                    String dnsName = CmsPropertyHandler.getWebServerAddress();
                    if (siteNodeVO != null) {
                        RepositoryVO repositoryVO = RepositoryController.getController()
                                .getRepositoryVOWithId(siteNodeVO.getRepositoryId(), getDatabase());
                        if (repositoryVO.getDnsName() != null && !repositoryVO.getDnsName().equals(""))
                            dnsName = repositoryVO.getDnsName();
                    }

                    String bundleUrl = "";
                    String bundleUrlTag = "";

                    if (contentType.equalsIgnoreCase("text/javascript")) {
                        bundleUrl = URLComposer.getURLComposer().composeDigitalAssetUrl(dnsName, "extensions",
                                bundleName + ".js", deliveryContext);
                        bundleUrlTag = "<script type=\"text/javascript\" src=\"" + bundleUrl + "\"></script>";
                    } else if (contentType.equalsIgnoreCase("text/css")) {
                        bundleUrl = URLComposer.getURLComposer().composeDigitalAssetUrl(dnsName, "extensions",
                                bundleName + ".css", deliveryContext);
                        bundleUrlTag = "<link href=\"" + bundleUrl + "\" rel=\"stylesheet\" type=\"text/css\" />";
                    }

                    if (targetElement.equalsIgnoreCase("head"))
                        this.getTemplateController().getDeliveryContext().getHtmlHeadItems().add(bundleUrlTag);
                    else
                        this.getTemplateController().getDeliveryContext().getHtmlBodyEndItems().add(bundleUrlTag);
                } catch (Exception e) {
                    logger.warn("Error trying  get assetBaseUrl:" + e.getMessage());
                }
            }

        }
        if (logger.isInfoEnabled())
            t.printElapsedTime("Generating bundles took");
    }

    /**
     * This method is used to allow pagecaching on a general level.
     */

    public void cachePage() {

    }

    public final DeliveryContext getDeliveryContext() {
        return deliveryContext;
    }

    public final HttpServletRequest getRequest() {
        return request;
    }

    public final HttpServletResponse getResponse() {
        return response;
    }

    public final TemplateController getTemplateController() {
        return templateController;
    }

    public String getPageString() {
        return pageString;
    }

    public void setPageString(String string) {
        if (string != null && this.deliveryContext.getTrimResponse())
            string = string.trim();

        pageString = string;
    }

    /**
     * Creates and returns a defaultContext, currently with the templateLogic 
     * and if the portal support is enabled the portalLogic object. 
     * (Added to avoid duplication of context creation in the concrete 
     * implementations of pageInvokers)
     * @author robert
     * @return A default context with the templateLogic and portalLogic object in it.
     */

    public Map getDefaultContext() {
        Map context = new HashMap();
        context.put("templateLogic", getTemplateController());

        // -- check if the portal is active
        String portalEnabled = CmsPropertyHandler.getEnablePortal();
        boolean active = ((portalEnabled != null) && portalEnabled.equals("true"));
        if (active) {
            PortalController pController = new PortalController(getRequest(), getResponse(),
                    getTemplateController().getDeliveryContext());
            context.put(PortalController.NAME, pController);
            if (logger.isInfoEnabled()) {
                logger.info("PortalController.NAME:" + PortalController.NAME);
                logger.info("pController:" + pController);
            }
        }

        return context;
    }

}