You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jetspeed-dev@portals.apache.org by wo...@apache.org on 2009/04/03 20:46:58 UTC

svn commit: r761757 - in /portals/jetspeed-2/portal/trunk: applications/jetspeed/src/main/webapp/WEB-INF/ applications/jetspeed/src/main/webapp/javascript/jetspeed/desktop/ components/jetspeed-cm/src/test/java/org/apache/jetspeed/cache/ components/jets...

Author: woonsan
Date: Fri Apr  3 18:46:58 2009
New Revision: 761757

URL: http://svn.apache.org/viewvc?rev=761757&view=rev
Log:
JS2-949: Improved algorithm and fixed problems after peer review with Ate. Thank you very much!
 - Changes the head elements map to list because it is important to keep the insertion order. Portlets definitely want to keep the order of contributed head elements.
 - Moves head element aggregation logic from aggregator to portlet window. This enables pulling mechanism, so even root fragment can contribute head elements as well.
   Also, adds getHeadElements() method in JPT and odifies velocity macro accordingly.
 - Adds comments on DOMUtils' createSerializableElement() method to explain why it is like that.
   Also, provides getOwnerDocument() in the returned object of DOMUtils.createSerializableElement() to allow portlet to append text node which can be created by the document.
 - By the way, desktop script (core.src.js) should be improved to support keeping insertion order.

Added:
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java   (with props)
    portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java   (with props)
Modified:
    portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/WEB-INF/jetspeed_macros.vm
    portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/javascript/jetspeed/desktop/core.src.js
    portals/jetspeed-2/portal/trunk/components/jetspeed-cm/src/test/java/org/apache/jetspeed/cache/TestContentCache.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/AsyncPageAggregatorImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/BaseAggregatorImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PageAggregatorImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletAggregatorImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletContentImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/container/impl/PortletRenderResponseContextImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DOMUtils.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/velocity/JetspeedPowerToolImpl.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/util/TestDOMUtils.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/window/MockPortletWindow.java
    portals/jetspeed-2/portal/trunk/components/jetspeed-registry/src/main/java/org/apache/jetspeed/om/window/impl/PortletWindowImpl.java
    portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/PortalReservedParameters.java
    portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/aggregator/PortletContent.java
    portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/container/PortletWindow.java
    portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/layout/JetspeedPowerTool.java

Modified: portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/WEB-INF/jetspeed_macros.vm
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/WEB-INF/jetspeed_macros.vm?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/WEB-INF/jetspeed_macros.vm (original)
+++ portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/WEB-INF/jetspeed_macros.vm Fri Apr  3 18:46:58 2009
@@ -78,9 +78,9 @@
 #macro (includeHeaderResource)
 #acquireHeaderResource()
 $resourceHeader.getContent()
-#set($headElements = $request.getAttribute("org.apache.jetspeed.markup.head.elements"))
-#foreach($headElement in $headElements)
-#set($headElemHtml = $jetspeed.getElementHtmlString($headElement))
+#set($headElements = $jetspeed.headElements)
+#foreach($kvPair in $headElements)
+#set($headElemHtml = $jetspeed.getElementHtmlString($kvPair.value))
 $headElemHtml
 #end
 #end

Modified: portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/javascript/jetspeed/desktop/core.src.js
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/javascript/jetspeed/desktop/core.src.js?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/javascript/jetspeed/desktop/core.src.js (original)
+++ portals/jetspeed-2/portal/trunk/applications/jetspeed/src/main/webapp/javascript/jetspeed/desktop/core.src.js Fri Apr  3 18:46:58 2009
@@ -490,6 +490,7 @@
 {
     var jsObj = jetspeed;
 
+    // TODO: add insertion ordering feature...
     // supports head contributions with script, link and style tags
     
     if ( headElements.script ) 

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-cm/src/test/java/org/apache/jetspeed/cache/TestContentCache.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-cm/src/test/java/org/apache/jetspeed/cache/TestContentCache.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-cm/src/test/java/org/apache/jetspeed/cache/TestContentCache.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-cm/src/test/java/org/apache/jetspeed/cache/TestContentCache.java Fri Apr  3 18:46:58 2009
@@ -21,7 +21,6 @@
 import java.security.Principal;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 
 import junit.framework.TestCase;
 import net.sf.ehcache.Cache;
@@ -32,6 +31,7 @@
 import org.apache.jetspeed.cache.impl.EhPortletContentCacheImpl;
 import org.apache.jetspeed.cache.impl.JetspeedCacheKeyGenerator;
 import org.apache.jetspeed.mockobjects.request.MockRequestContext;
+import org.apache.jetspeed.util.KeyValue;
 import org.w3c.dom.Element;
 
 import com.mockrunner.mock.web.MockHttpServletRequest;
@@ -400,7 +400,7 @@
         }
 
 
-        public Map<String, Element> getHeadElements()
+        public List<KeyValue<String, Element>> getHeadElements()
         {
             // TODO Auto-generated method stub
             return null;

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/AsyncPageAggregatorImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/AsyncPageAggregatorImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/AsyncPageAggregatorImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/AsyncPageAggregatorImpl.java Fri Apr  3 18:46:58 2009
@@ -197,9 +197,6 @@
             log.debug("Rendering portlet fragment: [[name, " + f.getName() + "], [id, " + f.getId() + "]]");
         }
         
-        // accumulate all the head contributions from the rendered contents
-        aggregateHeadElements(f, context, null);   
-        
         renderer.renderNow(f, context);
     }
     

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/BaseAggregatorImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/BaseAggregatorImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/BaseAggregatorImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/BaseAggregatorImpl.java Fri Apr  3 18:46:58 2009
@@ -16,9 +16,7 @@
  */
 package org.apache.jetspeed.aggregator.impl;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -29,7 +27,6 @@
 import org.apache.jetspeed.om.page.ContentFragment;
 import org.apache.jetspeed.om.page.ContentPage;
 import org.apache.jetspeed.request.RequestContext;
-import org.w3c.dom.Element;
 
 /**
  * Share common code for all aggregators 
@@ -92,7 +89,6 @@
         try
         {
             renderer.renderNow(window.getFragment(), context);
-            aggregateHeadElements(window.getFragment(), context, null);
             renderer.renderNow(layoutContentFragment, context);
         }
         catch (Exception e)
@@ -101,52 +97,5 @@
             window.getFragment().overrideRenderedContent("Sorry, but we were unable access the requested portlet. Send the following message to your portal admin:  "+  e.getMessage());
         }
     }
-
-    /**
-     * <p>
-     * aggregateHeadElements
-     * </p>
-     * Aggregates all contributed head elements from each content fragment into a map
-     * and store it into a specific request attribute.
-     * 
-     * @param f
-     * @param context
-     * @param headElements
-     */
-    protected void aggregateHeadElements( ContentFragment f, RequestContext context, Map<String, Element> headElements )
-    {
-        boolean isRoot = (headElements == null);
-        
-        if (headElements == null)
-        {
-            headElements = new HashMap<String, Element>();
-        }
-        
-        List<ContentFragment> contentFragments = (List<ContentFragment>) f.getContentFragments();
-        
-        if (contentFragments != null && !contentFragments.isEmpty())
-        {
-            for (ContentFragment child : contentFragments)
-            {
-                if (!"hidden".equals(f.getState()))
-                {
-                    aggregateHeadElements(child, context, headElements);
-                }
-            }
-        }
-
-        PortletContent portletContent = f.getPortletContent();
-
-        // root fragment's portlet content can be null
-        if (portletContent != null)
-        {
-            headElements.putAll(portletContent.getHeadElements());
-        }
-
-        if (isRoot)
-        {
-            context.getRequest().setAttribute(PortalReservedParameters.MARKUP_HEAD_ELEMENTS_ATTRIBUTE, headElements);
-        }
-    }
     
 }

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PageAggregatorImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PageAggregatorImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PageAggregatorImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PageAggregatorImpl.java Fri Apr  3 18:46:58 2009
@@ -74,7 +74,7 @@
         }
         else
         {
-            aggregateAndRender(root, context, page, true);
+            aggregateAndRender(root, context, page);
         }
         
         // write all rendered content
@@ -89,9 +89,10 @@
         releaseBuffers(root, context);        
     }
 
-    protected void aggregateAndRender( ContentFragment f, RequestContext context, ContentPage page, boolean isRoot )
+    @SuppressWarnings("unchecked")
+    protected void aggregateAndRender( ContentFragment f, RequestContext context, ContentPage page )
     {
-        List<ContentFragment> contentFragments = (List<ContentFragment>) f.getContentFragments();
+        List<ContentFragment> contentFragments = f.getContentFragments();
         
         if (contentFragments != null && !contentFragments.isEmpty())
         {
@@ -99,17 +100,11 @@
             {
                 if (!"hidden".equals(f.getState()))
                 {
-                    aggregateAndRender(child, context, page, false);
+                    aggregateAndRender(child, context, page);
                 }
             }
         }
         
-        if (isRoot)
-        {
-            // accumulate all the head contributions from the rendered contents
-            aggregateHeadElements(f, context, null);   
-        }
-        
         renderer.renderNow(f, context);
     }
     

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletAggregatorImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletAggregatorImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletAggregatorImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletAggregatorImpl.java Fri Apr  3 18:46:58 2009
@@ -19,7 +19,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 
 import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.jetspeed.PortalReservedParameters;
@@ -33,6 +33,7 @@
 import org.apache.jetspeed.om.page.Fragment;
 import org.apache.jetspeed.request.RequestContext;
 import org.apache.jetspeed.util.DOMUtils;
+import org.apache.jetspeed.util.KeyValue;
 import org.w3c.dom.Element;
 
 /**
@@ -123,17 +124,16 @@
     
     protected void writeHeadElements(RequestContext context, PortletWindow window) throws IOException
     {
-        Map<String, Element> headElements = window.getFragment().getPortletContent().getHeadElements();
+        List<KeyValue<String, Element>> headElements = window.getHeadElements();
         PrintWriter out = context.getResponse().getWriter();
 
         out.println("<JS_PORTLET_HEAD_ELEMENTS>");
         
         if (!headElements.isEmpty())
         {
-            
-            for (Element element : headElements.values())
+            for (KeyValue<String, Element> kvPair : headElements)
             {
-                out.println(DOMUtils.stringifyElementToHtml(element));
+                out.println(DOMUtils.stringifyElementToHtml(kvPair.getValue()));
             }
         }
         

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletContentImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletContentImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletContentImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/aggregator/impl/PortletContentImpl.java Fri Apr  3 18:46:58 2009
@@ -21,13 +21,15 @@
 import java.io.PrintWriter;
 import java.io.Serializable;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 
+import org.apache.commons.collections.list.TreeList;
 import org.apache.jetspeed.aggregator.PortletContent;
 import org.apache.jetspeed.aggregator.PortletRenderer;
 import org.apache.jetspeed.cache.ContentCacheKey;
 import org.apache.jetspeed.util.DOMUtils;
+import org.apache.jetspeed.util.DefaultKeyValue;
+import org.apache.jetspeed.util.KeyValue;
 import org.w3c.dom.Element;
 
 
@@ -35,13 +37,18 @@
 {
     private CharArrayWriter cw;
     private PrintWriter writer;
-    private boolean complete = false;
+    private boolean complete;
     private ContentCacheKey cacheKey;
-    private int expiration = 0;
+    private int expiration;
     private String title;
     private String contentType;
-    private PortletRenderer renderer = null;
-    private Map<String, Element> headElements = null;
+    private PortletRenderer renderer;
+    
+    /**
+     * The list container for all contributed head elements from this portlet content.
+     * Because the insertion order might be important for web development, this container should be list instead of map.
+     */
+    private List<KeyValue<String, Element>> headElements;
     
     PortletContentImpl()
     {
@@ -185,18 +192,29 @@
         }
     }
 
+    @SuppressWarnings("unchecked")
     public void addHeadElement(Element element, String keyHint) throws NotSerializableException
     {
         if (this.headElements == null)
         {
-            this.headElements = new HashMap<String, Element>();
+            // org.apache.commons.collections.list.TreeList is well-optimized for
+            // fast insertions at any index in the list.
+            // Refer to description in the javadoc for details.
+            this.headElements = new TreeList();
         }
 
         if (element == null)
         {
             if (keyHint != null)
             {
-                this.headElements.remove(keyHint);
+                KeyValue<String, Element> kvPair = new DefaultKeyValue(keyHint, null, true);
+                this.headElements.remove(kvPair);
+            }
+            else
+            {
+                // If element is null and keyHint is null, remove all head elements.
+                // This is complying with the portlet spec.
+                this.headElements.clear();
             }
             
             return;
@@ -209,6 +227,10 @@
         
         if (keyHint == null)
         {
+            // If element is dom4j's element, then we can use dom4j's serializer.
+            // Meanwhile, the element can be a serializable one which is not from dom4j.
+            // In the case, serialize the element to a string as a keyHint.
+            
             if (element instanceof org.dom4j.Element) 
             {
                 keyHint = DOMUtils.stringifyElement((org.dom4j.Element) element);
@@ -219,23 +241,28 @@
             }
         }
 
-        this.headElements.put(keyHint, element);
+        KeyValue<String, Element> kvPair = new DefaultKeyValue(keyHint, element, true);
+        
+        if (!this.headElements.contains(kvPair))
+        {
+            this.headElements.add(kvPair);
+        }
     }
 
-    public Map<String, Element> getHeadElements()
+    public List<KeyValue<String, Element>> getHeadElements()
     {
-        Map<String, Element> headElemMap = null;
+        List<KeyValue<String, Element>> headElems = null;
         
         if (this.headElements != null) 
         {
-            headElemMap = this.headElements;
+            headElems = this.headElements;
         } 
         else 
         {
-            headElemMap = Collections.emptyMap();
+            headElems = Collections.emptyList();
         }
         
-        return headElemMap;
+        return headElems;
     }
     
 }
\ No newline at end of file

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/container/impl/PortletRenderResponseContextImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/container/impl/PortletRenderResponseContextImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/container/impl/PortletRenderResponseContextImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/container/impl/PortletRenderResponseContextImpl.java Fri Apr  3 18:46:58 2009
@@ -138,13 +138,18 @@
         {
             Element headElement = null;
             
-            if (element instanceof Serializable)
+            // Note that element can be null.
+            // According to the SPEC, the property with this key can be removed with null element.
+            if (element != null)
             {
-                headElement = element;
-            }
-            else
-            {
-                headElement = DOMUtils.convertToSerializableElement(element);
+                if (element instanceof Serializable)
+                {
+                    headElement = element;
+                }
+                else
+                {
+                    headElement = DOMUtils.convertToSerializableElement(element);
+                }
             }
             
             try
@@ -166,14 +171,17 @@
     {
         String value = null;
         
-        if (element.hasAttribute("ID"))
-            value = element.getAttribute("ID");
-        else if (element.hasAttribute("id"))
-            value = element.getAttribute("id");
-        else if (element.hasAttribute("Id"))
-            value = element.getAttribute("Id");
-        else if (element.hasAttribute("iD"))
-            value = element.getAttribute("iD");
+        if (element != null)
+        {
+            if (element.hasAttribute("id"))
+                value = element.getAttribute("id");
+            else if (element.hasAttribute("ID"))
+                value = element.getAttribute("ID");
+            else if (element.hasAttribute("Id"))
+                value = element.getAttribute("Id");
+            else if (element.hasAttribute("iD"))
+                value = element.getAttribute("iD");
+        }
         
         return value;
     }

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DOMUtils.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DOMUtils.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DOMUtils.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DOMUtils.java Fri Apr  3 18:46:58 2009
@@ -21,12 +21,14 @@
 
 import org.dom4j.dom.DOMCDATA;
 import org.dom4j.dom.DOMComment;
+import org.dom4j.dom.DOMDocument;
 import org.dom4j.dom.DOMElement;
 import org.dom4j.dom.DOMText;
 import org.dom4j.io.HTMLWriter;
 import org.dom4j.io.OutputFormat;
 import org.dom4j.io.XMLWriter;
 import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
@@ -51,9 +53,30 @@
     
     public static org.w3c.dom.Element createSerializableElement(String tagName)
     {
+        // Note: Because the DOMElement of dom4j (v1.6.1) is built on DOM Level2 API,
+        // it does not have implementations on setTextContent() and getTextContent() of
+        // org.w3c.dom.Node interface.
+        // However, Jetspeed is supporting Java 1.5 from v2.2, we need to provide this
+        // to allow the methods invocations.
+        // Also, because dom4j DOMElement does not support getOwnerDocument(),
+        // we need to provide the method to allow portlet codes to create nodes with document.
+        
         return new DOMElement(tagName)
         {
             private static final long serialVersionUID = 1L;
+            
+            private Document document;
+            
+            @Override
+            public Document getOwnerDocument() 
+            {
+                if (document == null)
+                {
+                    document = new DOMDocument(this);
+                }
+                
+                return document;
+            }
 
             public void setTextContent(String textContent)
             {
@@ -65,6 +88,7 @@
                 return getText();
             }
         };
+        
     }
     
     public static org.w3c.dom.Element convertToSerializableElement(org.w3c.dom.Element element)

Added: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java?rev=761757&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java (added)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java Fri Apr  3 18:46:58 2009
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.jetspeed.util;
+
+import java.util.Map;
+
+/**
+ * A mutable <code>KeyValue</code> pair.
+ *
+ * @version $Id$
+ */
+public class DefaultKeyValue<K, V> implements KeyValue<K, V>
+{
+    
+    private K key;
+    private V value;
+    private boolean compareByKeyOnly;
+
+    /**
+     * Constructs a new pair with the specified key and given value.
+     * 
+     * @param key
+     *            the key for the entry, may be null
+     * @param value
+     *            the value for the entry, may be null
+     */
+    public DefaultKeyValue(final K key, final V value)
+    {
+        this(key, value, false);
+    }
+    
+    /**
+     * Constructs a new pair with the specified key and given value.
+     * 
+     * @param key
+     *            the key for the entry, may be null
+     * @param value
+     *            the value for the entry, may be null
+     * @param compareByKeyOnly
+     *            flag if equals() depends on key only 
+     */
+    public DefaultKeyValue(final K key, final V value, final boolean compareByKeyOnly)
+    {
+        this.key = key;
+        this.value = value;
+        this.compareByKeyOnly = compareByKeyOnly;
+    }
+
+    /**
+     * Constructs a new pair from the specified <code>KeyValue</code>.
+     * 
+     * @param pair
+     *            the pair to copy, must not be null
+     * @throws NullPointerException
+     *             if the entry is null
+     */
+    public DefaultKeyValue(final KeyValue<K, V> pair)
+    {
+        this(pair, false);
+    }
+    
+    /**
+     * Constructs a new pair from the specified <code>KeyValue</code>.
+     * 
+     * @param pair
+     *            the pair to copy, must not be null
+     * @param compareByKeyOnly
+     *            flag if equals() depends on key only 
+     * @throws NullPointerException
+     *             if the entry is null
+     */
+    public DefaultKeyValue(final KeyValue<K, V> pair, final boolean compareByKeyOnly)
+    {
+        this(pair.getKey(), pair.getValue(), compareByKeyOnly);
+    }
+
+    /**
+     * Constructs a new pair from the specified <code>Map.Entry</code>.
+     * 
+     * @param entry
+     *            the entry to copy, must not be null
+     * @throws NullPointerException
+     *             if the entry is null
+     */
+    public DefaultKeyValue(final Map.Entry<K, V> entry)
+    {
+        this(entry, false);
+    }
+    
+    /**
+     * Constructs a new pair from the specified <code>Map.Entry</code>.
+     * 
+     * @param entry
+     *            the entry to copy, must not be null
+     * @param compareByKeyOnly
+     *            flag if equals() depends on key only 
+     * @throws NullPointerException
+     *             if the entry is null
+     */
+    public DefaultKeyValue(final Map.Entry<K, V> entry, final boolean compareByKeyOnly)
+    {
+        this(entry.getKey(), entry.getValue(), compareByKeyOnly);
+    }
+
+    /**
+     * Gets the key from the pair.
+     * 
+     * @return the key
+     */
+    public K getKey()
+    {
+        return key;
+    }
+
+    /**
+     * Gets the value from the pair.
+     * 
+     * @return the value
+     */
+    public V getValue()
+    {
+        return value;
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Sets the key.
+     * 
+     * @param key
+     *            the new key
+     * @return the old key
+     * @throws IllegalArgumentException
+     *             if key is this object
+     */
+    public K setKey(final K key)
+    {
+        if (key == this)
+        {
+            throw new IllegalArgumentException("DefaultKeyValue may not contain itself as a key.");
+        }
+        
+        final K old = this.key;
+        this.key = key;
+        
+        return old;
+    }
+
+    /**
+     * Sets the value.
+     * 
+     * @return the old value of the value
+     * @param value
+     *            the new value
+     * @throws IllegalArgumentException
+     *             if value is this object
+     */
+    public V setValue(final V value)
+    {
+        if (value == this)
+        {
+            throw new IllegalArgumentException("DefaultKeyValue may not contain itself as a value.");
+        }
+        
+        final V old = this.value;
+        this.value = value;
+        
+        return old;
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Compares this <code>KeyValue</code> with another <code>KeyValue</code>.
+     * <p> Returns true if the compared object is also a <code>DefaultKeyValue</code>,
+     * and its key and value are equal to this object's key and value.
+     * 
+     * @param obj
+     *            the object to compare to
+     * @return true if equal key and value
+     */
+    @SuppressWarnings("unchecked")
+    public boolean equals(final Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        
+        if (obj instanceof DefaultKeyValue == false)
+        {
+            return false;
+        }
+        
+        DefaultKeyValue<K,V> other = (DefaultKeyValue<K,V>) obj;
+        
+        if (this.compareByKeyOnly)
+        {
+            return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey()));
+        }
+        else
+        {
+            return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) &&
+                   (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
+        }
+    }
+
+    /**
+     * Gets a hashCode compatible with the equals method.
+     * 
+     * @return a suitable hash code
+     */
+    public int hashCode()
+    {
+        return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode());
+    }
+    
+}

Propchange: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java
------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/util/DefaultKeyValue.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/velocity/JetspeedPowerToolImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/velocity/JetspeedPowerToolImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/velocity/JetspeedPowerToolImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/main/java/org/apache/jetspeed/velocity/JetspeedPowerToolImpl.java Fri Apr  3 18:46:58 2009
@@ -56,6 +56,7 @@
 import org.apache.jetspeed.request.RequestContext;
 import org.apache.jetspeed.util.ArgUtil;
 import org.apache.jetspeed.util.DOMUtils;
+import org.apache.jetspeed.util.KeyValue;
 import org.apache.jetspeed.util.Path;
 import org.apache.velocity.context.Context;
 import org.w3c.dom.Element;
@@ -844,5 +845,15 @@
     {
         return DOMUtils.stringifyElementToHtml(element);
     }
+
+    public List<KeyValue<String, Element>> getHeadElements(ContentFragment f) throws Exception
+    {
+        return getPortletWindow(f).getHeadElements();
+    }
+
+    public List<KeyValue<String, Element>> getHeadElements() throws Exception
+    {
+        return getHeadElements(getCurrentFragment());
+    }
     
 }

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/util/TestDOMUtils.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/util/TestDOMUtils.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/util/TestDOMUtils.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/util/TestDOMUtils.java Fri Apr  3 18:46:58 2009
@@ -33,6 +33,9 @@
 
     public void testW3CDOMElement() throws Exception
     {
+        
+        // Tests with element created from document builder...
+        
         DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
         DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
         Document doc = docBuilder.newDocument();
@@ -54,10 +57,14 @@
         assertTrue("the text content is wrong.", stringified.contains("Hello, World!"));
         assertTrue("the child element is wrong.", stringified.contains("<source id=\"my-test-javascript-source\">source is available.</source>"));
         
+        // Tests with element created by conversion from standard element to serializable element
+        
         Element converted = (Element) DOMUtils.convertElement(element);
         assertTrue("converted element is not serializable!", converted instanceof Serializable);
         assertEquals("converted element name is wrong.", element.getNodeName(), converted.getNodeName());
         
+        // Tests with element deserialized after serialization
+        
         Element deserialized = (Element) SerializationUtils.clone((Serializable) converted);
         assertEquals("deserialized element name is wrong.", converted.getNodeName(), deserialized.getNodeName());
         stringified = DOMUtils.stringifyElement((org.dom4j.Element) deserialized);
@@ -78,6 +85,9 @@
         assertTrue("the text content is wrong.", stringified.contains("Hello, World!"));
         assertTrue("the child element is wrong.", stringified.contains("<source id=\"my-test-javascript-source\">source is available.</source>"));
         
+        // Tests with element having CDATA child node
+        // setTextContent() should replace the CDATA node.
+        
         element = doc.createElement("script");
         element.setAttribute("id", "my-test-javascript");
         element.setAttribute("type", "text/javascript");
@@ -97,6 +107,9 @@
 
     public void testDOM4JElement() throws Exception
     {
+        // Tests if setTextContent() is working 
+        // with our serializable element implementation which is based on dom4j
+        
         org.w3c.dom.Element element = DOMUtils.createSerializableElement("script");
         element.setAttribute("id", "my-test-javascript");
         element.setAttribute("type", "text/javascript");
@@ -110,6 +123,22 @@
         assertTrue("type attribute does not exist.", stringified.contains("type=\"text/javascript\""));
         assertTrue("the text content is wrong.", stringified.contains("alert("));
         assertTrue("the text content is wrong.", stringified.contains("Hello, World!"));
+        
+        // Tests if getOwnerDocument() is working and it is possible to append text node
+        
+        element = DOMUtils.createSerializableElement("script");
+        element.setAttribute("id", "my-test-javascript");
+        element.setAttribute("type", "text/javascript");
+        element.appendChild(element.getOwnerDocument().createTextNode("alert('<Hello, World!>');"));
+        
+        stringified = DOMUtils.stringifyElement((org.dom4j.Element) element);
+        
+        System.out.println("stringified: " + stringified);
+        assertTrue("element name is different.", stringified.contains("<script "));
+        assertTrue("id attribute does not exist.", stringified.contains("id=\"my-test-javascript\""));
+        assertTrue("type attribute does not exist.", stringified.contains("type=\"text/javascript\""));
+        assertTrue("the text content is wrong.", stringified.contains("alert("));
+        assertTrue("the text content is wrong.", stringified.contains("Hello, World!"));
     }
     
 }

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/window/MockPortletWindow.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/window/MockPortletWindow.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/window/MockPortletWindow.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-portal/src/test/java/org/apache/jetspeed/window/MockPortletWindow.java Fri Apr  3 18:46:58 2009
@@ -17,6 +17,7 @@
 
 package org.apache.jetspeed.window;
 
+import java.util.List;
 import java.util.Map;
 
 import javax.portlet.PortletMode;
@@ -30,9 +31,11 @@
 import org.apache.jetspeed.om.page.ContentFragment;
 import org.apache.jetspeed.om.portlet.PortletDefinition;
 import org.apache.jetspeed.request.RequestContext;
+import org.apache.jetspeed.util.KeyValue;
 import org.apache.pluto.container.PortletEntity;
 import org.apache.pluto.container.PortletRequestContext;
 import org.apache.pluto.container.PortletResponseContext;
+import org.w3c.dom.Element;
 
 /**
  * @version $Id$
@@ -234,4 +237,10 @@
         // TODO Auto-generated method stub
         return false;
     }
+
+    public List<KeyValue<String, Element>> getHeadElements()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
 }

Modified: portals/jetspeed-2/portal/trunk/components/jetspeed-registry/src/main/java/org/apache/jetspeed/om/window/impl/PortletWindowImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/components/jetspeed-registry/src/main/java/org/apache/jetspeed/om/window/impl/PortletWindowImpl.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/components/jetspeed-registry/src/main/java/org/apache/jetspeed/om/window/impl/PortletWindowImpl.java (original)
+++ portals/jetspeed-2/portal/trunk/components/jetspeed-registry/src/main/java/org/apache/jetspeed/om/window/impl/PortletWindowImpl.java Fri Apr  3 18:46:58 2009
@@ -18,6 +18,7 @@
 
 import java.io.Serializable;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.portlet.PortletMode;
@@ -25,16 +26,20 @@
 import javax.portlet.PortletResponse;
 import javax.portlet.WindowState;
 
+import org.apache.commons.collections.list.TreeList;
+import org.apache.jetspeed.aggregator.PortletContent;
 import org.apache.jetspeed.aggregator.RenderTrackable;
-import org.apache.pluto.container.PortletEntity;
-import org.apache.pluto.container.PortletRequestContext;
-import org.apache.pluto.container.PortletResponseContext;
 import org.apache.jetspeed.container.PortletWindow;
 import org.apache.jetspeed.container.PortletWindowID;
 import org.apache.jetspeed.factory.PortletInstance;
 import org.apache.jetspeed.om.page.ContentFragment;
 import org.apache.jetspeed.om.portlet.PortletDefinition;
 import org.apache.jetspeed.request.RequestContext;
+import org.apache.jetspeed.util.KeyValue;
+import org.apache.pluto.container.PortletEntity;
+import org.apache.pluto.container.PortletRequestContext;
+import org.apache.pluto.container.PortletResponseContext;
+import org.w3c.dom.Element;
 
 /**
  * <P>
@@ -69,6 +74,7 @@
     private transient PortletRequestContext portletRequestContext;
     private transient PortletResponse portletResponse;
     private transient PortletInstance portletInstance;
+    private transient List<KeyValue<String, Element>> headElements;
 
     private boolean valid;
     
@@ -291,4 +297,73 @@
     {
         return portletInstance;
     }
+
+    @SuppressWarnings("unchecked")
+    public List<KeyValue<String, Element>> getHeadElements()
+    {
+        if (headElements == null)
+        {
+            // org.apache.commons.collections.list.TreeList is well-optimized for
+            // fast insertions at any index in the list.
+            // Refer to description in the javadoc for details.
+            headElements = new TreeList();
+            aggregateHeadElements(getFragment(), headElements);
+        }
+        
+        return headElements;
+    }
+    
+    private static void aggregateHeadElements( ContentFragment f, List<KeyValue<String, Element>> aggregatedHeadElements )
+    {
+        // TODO: Question: Does the aggregation order direction (top-down or bottom-up) need to be configurable?
+        
+        List<ContentFragment> contentFragments = (List<ContentFragment>) f.getContentFragments();
+        
+        if (contentFragments != null && !contentFragments.isEmpty())
+        {
+            for (ContentFragment child : contentFragments)
+            {
+                if (!"hidden".equals(f.getState()))
+                {
+                    aggregateHeadElements(child, aggregatedHeadElements);
+                }
+            }
+        }
+
+        PortletContent portletContent = f.getPortletContent();
+        
+        // portletContent can be null if this method is invoked before the portlet window starts rendering
+        if (portletContent != null)
+        {
+            // Brief explanation on head element aggregation algorithm (Thanks to Ate for the brilliant ideas!):
+            // - Precondition: start from the zero as insertion index.
+            // - Rule1: if there already exists an element with the key, 
+            //              set the insertion index to the matching index + 1.
+            // - Rule2: if there's no existing element with the key, 
+            //              insert the element at the current insertion index 
+            //              and increase the insertion index.
+            
+            List<KeyValue<String, Element>> contentHeadElements = f.getPortletContent().getHeadElements();
+            
+            if (!contentHeadElements.isEmpty())
+            {
+                int insertionIndex = 0;
+                
+                for (KeyValue<String, Element> kvPair : contentHeadElements)
+                {
+                    int offset = aggregatedHeadElements.indexOf(kvPair);
+                    
+                    if (offset != -1)
+                    {
+                        insertionIndex = offset + 1;
+                    }
+                    else
+                    {
+                        aggregatedHeadElements.add(insertionIndex++, kvPair);
+                    }
+                }
+            }
+        }
+    }
+
 }

Modified: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/PortalReservedParameters.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/PortalReservedParameters.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/PortalReservedParameters.java (original)
+++ portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/PortalReservedParameters.java Fri Apr  3 18:46:58 2009
@@ -58,7 +58,6 @@
     public static final String HEADER_NAMED_RESOURCE_ATTRIBUTE = "org.apache.jetspeed.headernamedresource";
     public static final String HEADER_NAMED_RESOURCE_ADDED_FRAGMENTS_ATTRIBUTE = "org.apache.jetspeed.headernamedresourceaddedfragments";
     public static final String HEADER_NAMED_RESOURCE_REGISTRY_ATTRIBUTE = "org.apache.jetspeed.headernamedresourceregistry";
-    public static final String MARKUP_HEAD_ELEMENTS_ATTRIBUTE = "org.apache.jetspeed.markup.head.elements";
     public static final String PATH_ATTRIBUTE = "org.apache.jetspeed.Path";
     public static final String PARAMETER_ALREADY_DECODED_ATTRIBUTE = "org.apache.jetspeed.parameterAlreadyDecoded";
     public static final String RESOVLER_CACHE_ATTR = "org.apache.jetspeed.resovler.cache";

Modified: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/aggregator/PortletContent.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/aggregator/PortletContent.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/aggregator/PortletContent.java (original)
+++ portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/aggregator/PortletContent.java Fri Apr  3 18:46:58 2009
@@ -18,9 +18,10 @@
 
 import java.io.NotSerializableException;
 import java.io.PrintWriter;
-import java.util.Map;
+import java.util.List;
 
 import org.apache.jetspeed.cache.ContentCacheKey;
+import org.apache.jetspeed.util.KeyValue;
 import org.w3c.dom.Element;
 
 /**
@@ -132,11 +133,12 @@
     void addHeadElement(Element element, String keyHint) throws NotSerializableException;
     
     /**
-     * Retrieves header element map to be contributed to the page.
+     * Retrieves header element key value pairs to be contributed to the page.
+     * Because the insertion order might be important for web development, it should be list instead of map.
      * 
      * @return
      */
-    Map<String, Element> getHeadElements(); 
+    List<KeyValue<String, Element>> getHeadElements(); 
     
     void reset();
     

Modified: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/container/PortletWindow.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/container/PortletWindow.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/container/PortletWindow.java (original)
+++ portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/container/PortletWindow.java Fri Apr  3 18:46:58 2009
@@ -18,6 +18,7 @@
 package org.apache.jetspeed.container;
 
 import java.io.Serializable;
+import java.util.List;
 import java.util.Map;
 
 import javax.portlet.PortletRequest;
@@ -27,8 +28,10 @@
 import org.apache.jetspeed.om.page.ContentFragment;
 import org.apache.jetspeed.om.portlet.PortletDefinition;
 import org.apache.jetspeed.request.RequestContext;
+import org.apache.jetspeed.util.KeyValue;
 import org.apache.pluto.container.PortletRequestContext;
 import org.apache.pluto.container.PortletResponseContext;
+import org.w3c.dom.Element;
 
 /**
  * @version $Id$
@@ -36,6 +39,7 @@
  */
 public interface PortletWindow extends org.apache.pluto.container.PortletWindow, Serializable
 {
+    
     boolean isValid();
     PortletWindowID getId();
     String getWindowId();
@@ -60,4 +64,16 @@
     PortletRequestContext getPortletRequestContext();
     PortletResponseContext getPortletResponseContext();
     PortletInstance getPortletInstance();
+    
+    /**
+     * Returns all the contributed head elements which were aggregated from this window content
+     * and all the child window contents.
+     * <P>
+     * The head elements are sorted by the insertion order.
+     * </P>
+     * 
+     * @return
+     */
+    List<KeyValue<String, Element>> getHeadElements();
+    
 }

Modified: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/layout/JetspeedPowerTool.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/layout/JetspeedPowerTool.java?rev=761757&r1=761756&r2=761757&view=diff
==============================================================================
--- portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/layout/JetspeedPowerTool.java (original)
+++ portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/layout/JetspeedPowerTool.java Fri Apr  3 18:46:58 2009
@@ -28,6 +28,7 @@
 import org.apache.jetspeed.locator.TemplateLocatorException;
 import org.apache.jetspeed.om.page.ContentFragment;
 import org.apache.jetspeed.om.page.Page;
+import org.apache.jetspeed.util.KeyValue;
 import org.w3c.dom.Element;
 
 /**
@@ -239,6 +240,27 @@
     
     String renderPortletWindow(String windowId, String portletId);
     
+    /**
+     * Returns stringified one from the element
+     * 
+     * @param element
+     * @return
+     */
     String getElementHtmlString(Element element);
     
+    /**
+     * Returns all the contributed head elements from the fragment and its child fragments.
+     * @param f
+     * @return
+     * @throws Exception
+     */
+    List<KeyValue<String, Element>> getHeadElements(ContentFragment f) throws Exception;;
+
+    /**
+     * Returns all the contributed head elements from the current fragment and its child fragments.
+     * @return
+     * @throws Exception
+     */
+    List<KeyValue<String, Element>> getHeadElements() throws Exception;;
+    
 }
\ No newline at end of file

Added: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java?rev=761757&view=auto
==============================================================================
--- portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java (added)
+++ portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java Fri Apr  3 18:46:58 2009
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.jetspeed.util;
+
+/**
+ * Defines a simple key value pair.
+ * A Map Entry has considerable additional semantics over and above a simple key-value pair.
+ * This interface defines the minimum key value, with just the two get methods.
+ * 
+ * @version $Id$
+ */
+public interface KeyValue<K,V>
+{
+    
+    /**
+     * Returns the key corresponding to the pair.
+     *
+     * @return the key corresponding to the pair
+     */
+    K getKey();
+
+    /**
+     * Returns the value corresponding to the pair.
+     *
+     * @return the value corresponding to the pair
+     */
+    V getValue();
+    
+}

Propchange: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java
------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: portals/jetspeed-2/portal/trunk/jetspeed-api/src/main/java/org/apache/jetspeed/util/KeyValue.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: jetspeed-dev-unsubscribe@portals.apache.org
For additional commands, e-mail: jetspeed-dev-help@portals.apache.org