001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
003     * agreements. See the NOTICE file distributed with this work for additional information regarding
004     * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
005     * "License"); you may not use this file except in compliance with the License. You may obtain a
006     * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
007     * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
008     * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
009     * for the specific language governing permissions and limitations under the License.
010     */
011    package javax.portlet.faces.component;
012    
013    import java.io.Serializable;
014    
015    import javax.faces.context.FacesContext;
016    import javax.faces.component.NamingContainer;
017    import javax.faces.component.UIViewRoot;
018    import javax.faces.context.ExternalContext;
019    
020    import javax.portlet.faces.Bridge;
021    import javax.portlet.faces.BridgeUtil;
022    import javax.portlet.faces.annotation.PortletNamingContainer;
023    
024    /**
025     * <code>UIViewRoot</code> that implements portlet specific <code>NamingContainer</code>
026     * that ensures the consumer's unique portlet Id is encoded in all tree components.
027     * The class is annotated by <code>javax.portlet.faces.annotation.PortletNamingContainer</code>
028     * allowing the bridge to recognize that this specific <code>UIViewRoot</code>
029     * implements the behavior.
030     */
031    @PortletNamingContainer
032    public class PortletNamingContainerUIViewRoot extends UIViewRoot implements Serializable, NamingContainer
033    {
034    
035      //TODO: This should be regenerated each time this is modified.  Can this be added to maven?
036      private static final long   serialVersionUID = -4524288011655837711L;
037      private static final String PORTLET_ENCODED_NAMESPACE_ID = Bridge.BRIDGE_PACKAGE_PREFIX + "PortletEncodedNamespaceId";
038      
039      // Assumes that first use of the UIViewRoot always occurs in a render as portlet namespace
040      // is only available during a render.  If this isn't the case we disable use of the
041      // NamingContainer
042      public PortletNamingContainerUIViewRoot()
043      {
044        super();
045        
046        getAttributes().put(PORTLET_ENCODED_NAMESPACE_ID, Boolean.FALSE);
047      }
048    
049      /**
050       * NamingContainer semantics worked generically (serviced by subclasses) as long as the class
051       * is marked as implementing NamingContainer and we use the portletNamespace Id as
052       * (part of) the component's id.
053       */
054    
055      @Override
056      public String getContainerClientId(FacesContext context)
057      {
058        if (BridgeUtil.isPortletRequest() && ((Boolean) this.getAttributes().get(PORTLET_ENCODED_NAMESPACE_ID)).equals(Boolean.TRUE))
059        {
060          return super.getContainerClientId(context);
061        }
062        else
063        {
064          return null;
065        }
066      }
067      
068      @Override
069      public void setId(String id)
070      {
071        if (BridgeUtil.isPortletRequest()) 
072        {
073          Boolean encoded = Boolean.FALSE;
074          /* Can only calculate the namespace during a render (in portlet 1.0) */
075          if (BridgeUtil.getPortletRequestPhase() == Bridge.PortletPhase.RENDER_PHASE)
076          {
077            // Turns out some Faces impls (the RI) , on restoreView, manually sets the id from 
078            // the extracted state prior to telling the component to restore itself from this state.
079            // (At which point the self restore overwrites any id set prior.).  As this manual
080            // set is using the already encoded (saved) value we could end up with a doubly
081            // encoded id until the restore overwrites.  To avoid this -- first test if
082            // its already encoded and don't re-encode.
083            ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
084            String namespace = ec.encodeNamespace("");
085            if (!id.startsWith(namespace))
086            {
087              id = namespace + "_" + id;
088            }
089            // now indicate that this id is encoded
090            encoded = Boolean.TRUE;
091          }
092          
093          getAttributes().put(PORTLET_ENCODED_NAMESPACE_ID, encoded);
094        }
095        super.setId(id);
096      }
097      
098    }