001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.renderStrategy;
018
019import java.lang.reflect.InvocationTargetException;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.Component;
023import org.apache.wicket.ajax.AjaxRequestTarget;
024import org.apache.wicket.application.HeaderContributorListenerCollection;
025import org.apache.wicket.markup.head.IHeaderResponse;
026import org.apache.wicket.markup.html.IHeaderContributor;
027import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
028import org.apache.wicket.markup.html.internal.HtmlHeaderContainer.HeaderStreamState;
029import org.apache.wicket.markup.html.internal.InlineEnclosure;
030import org.apache.wicket.util.lang.Args;
031import org.apache.wicket.util.visit.IVisit;
032import org.apache.wicket.util.visit.IVisitor;
033
034/**
035 * An abstract implementation of a header render strategy which is only missing the code to traverse
036 * the child hierarchy, since the sequence of that traversal is what will make the difference
037 * between the different header render strategies.
038 * 
039 * Besides the child hierarchy the render sequence by default (may be changed via subclassing) is as
040 * follows:
041 * <ul>
042 * <li>1. application level headers</li>
043 * <li>2. the root component's headers</li>
044 * <li>3. the children hierarchy (to be implemented per subclass)</li>
045 * </ul>
046 * 
047 * @author Juergen Donnerstag
048 */
049public abstract class AbstractHeaderRenderStrategy implements IHeaderRenderStrategy
050{
051        /**
052         * @return Gets the strategy registered with the application
053         */
054        public static IHeaderRenderStrategy get()
055        {
056                // NOT OFFICIALLY SUPPORTED BY WICKET
057                // By purpose it is "difficult" to change to another render strategy.
058                // We don't want it to be modifiable by users, but we needed a way to easily test other
059                // strategies.
060                String className = System.getProperty("Wicket_HeaderRenderStrategy");
061                if (className != null)
062                {
063                        Class<?> clazz;
064                        try
065                        {
066                                clazz = Application.get()
067                                        .getApplicationSettings()
068                                        .getClassResolver()
069                                        .resolveClass(className);
070
071                                if (clazz != null)
072                                {
073                                        return (IHeaderRenderStrategy)clazz.getDeclaredConstructor().newInstance();
074                                }
075                        }
076                        catch (ClassNotFoundException | InstantiationException | IllegalAccessException
077                                        | NoSuchMethodException | InvocationTargetException ex)
078                        {
079                                // ignore
080                        }
081                }
082
083                // Our default header render strategy
084                // Pre 1.5
085                // return new ParentFirstHeaderRenderStrategy();
086
087                // Since 1.5
088                return new ChildFirstHeaderRenderStrategy();
089        }
090
091        /**
092         * Construct.
093         */
094        public AbstractHeaderRenderStrategy()
095        {
096        }
097
098        @Override
099        public void renderHeader(final HtmlHeaderContainer headerContainer,
100                HeaderStreamState headerStreamState, final Component rootComponent)
101        {
102                Args.notNull(headerContainer, "headerContainer");
103                Args.notNull(rootComponent, "rootComponent");
104
105                // First the application level headers
106                renderApplicationLevelHeaders(headerContainer);
107
108                // Then the root component's headers
109                renderRootComponent(headerContainer, headerStreamState, rootComponent);
110
111                // Then its child hierarchy
112                renderChildHeaders(headerContainer, rootComponent);
113        }
114
115        /**
116         * Render the root component (e.g. Page).
117         * 
118         * @param headerContainer
119         * @param headerStreamState
120         * @param rootComponent
121         */
122        protected void renderRootComponent(final HtmlHeaderContainer headerContainer,
123                final HeaderStreamState headerStreamState, final Component rootComponent)
124        {
125                headerContainer.renderHeaderTagBody(headerStreamState);
126
127                rootComponent.internalRenderHead(headerContainer);
128
129                if (rootComponent instanceof InlineEnclosure) {
130                        renderInlineEnclosure(headerContainer, (InlineEnclosure) rootComponent);
131                }
132        }
133
134
135        /**
136         * Searches for the siblings of the given enclosure for the controller of the given enclosure and
137         * renders that controller's header contributions.
138         *
139         * This is done explicitly because when an enclosed component is added to the {@link AjaxRequestTarget}
140         * and is consequently replaced for render by the enclosure, the component's header contributions would not make
141         * it to the response as the enclosure is a sibling of the component in the hierarchy and only children's header contributions
142         * are added to the response.
143         *
144         * Fixes WICKET-6459
145         *
146         * @param container the header container to render the header contributions of the enclosure's controller
147         * @param enclosure the enclosure whose controller's contributions are going to be rendered
148         */
149        protected void renderInlineEnclosure(HtmlHeaderContainer container, InlineEnclosure enclosure){
150
151                final String childId = enclosure.getChildId();
152
153                // Visit the siblings of the enclosure to attempt and find the controller of the enclosure
154                Component enclosureController = enclosure.getParent().visitChildren(new IVisitor<Component, Component>() {
155                        @Override
156                        public void component(Component object, IVisit<Component> visit) {
157                                if (object.getId().equals(childId)){
158                                        visit.stop(object);
159                                } else {
160                                        visit.dontGoDeeper();
161                                }
162                        }
163                });
164
165                if (enclosureController != null){
166                        enclosureController.internalRenderHead(container);
167                }
168        }
169
170        /**
171         * Render the child hierarchy headers.
172         * 
173         * @param headerContainer
174         * @param rootComponent
175         */
176        abstract protected void renderChildHeaders(final HtmlHeaderContainer headerContainer,
177                final Component rootComponent);
178
179        /**
180         * Render the application level headers
181         * 
182         * @param headerContainer
183         */
184        protected final void renderApplicationLevelHeaders(final HtmlHeaderContainer headerContainer)
185        {
186                Args.notNull(headerContainer, "headerContainer");
187
188                if (Application.exists())
189                {
190                        HeaderContributorListenerCollection headerContributorListenerCollection =
191                                        Application.get().getHeaderContributorListeners();
192                        IHeaderResponse headerResponse = headerContainer.getHeaderResponse();
193
194                        for (IHeaderContributor listener : headerContributorListenerCollection)
195                        {
196                                listener.renderHead(headerResponse);
197                        }
198                }
199        }
200}