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.html.panel;
018
019import java.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022
023import org.apache.wicket.Component;
024import org.apache.wicket.MarkupContainer;
025import org.apache.wicket.markup.ComponentTag;
026import org.apache.wicket.markup.IMarkupFragment;
027import org.apache.wicket.markup.MarkupException;
028import org.apache.wicket.markup.MarkupStream;
029import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
030import org.apache.wicket.markup.parser.XmlTag.TagType;
031import org.apache.wicket.markup.resolver.IComponentResolver;
032import org.apache.wicket.util.lang.Classes;
033import org.apache.wicket.util.visit.IVisit;
034import org.apache.wicket.util.visit.IVisitor;
035
036/**
037 * Implements boilerplate as needed by many markup sourcing strategies.
038 * 
039 * @author Juergen Donnerstag
040 */
041public abstract class AbstractMarkupSourcingStrategy implements IMarkupSourcingStrategy
042{
043        /**
044         * Construct.
045         */
046        public AbstractMarkupSourcingStrategy()
047        {
048        }
049
050        @Override
051        public abstract IMarkupFragment getMarkup(final MarkupContainer container, final Component child);
052
053        /**
054         * If the child has not been directly added to the container, but via a
055         * TransparentWebMarkupContainer, then we are in trouble. In general Wicket iterates over the
056         * markup elements and searches for associated components, not the other way around. Because of
057         * TransparentWebMarkupContainer (or more generally resolvers), there is no "synchronous" search
058         * possible.
059         * 
060         * @param container
061         *            the parent container.
062         * @param
063         *                containerMarkup
064         *                        the markup of the parent container.
065         * @param child
066         *            The component to find the markup for.
067         * @return the markup fragment for the child, or {@code null}.
068         */
069        protected IMarkupFragment searchMarkupInTransparentResolvers(MarkupContainer container,
070                IMarkupFragment containerMarkup, Component child)
071        {
072                IMarkupFragment childMarkupFound = null;
073                Iterator<Component> childrenIterator = container.iterator();
074                final List<MarkupContainer> componentResolvers = new ArrayList<>();
075                
076                //collect all "transparent" (i.e. component resolvers) children
077                container.visitChildren(IComponentResolver.class, new IVisitor<MarkupContainer, Void>()
078                {
079                        @Override
080                        public void component(MarkupContainer child, IVisit<Void> visit)
081                        {
082                                componentResolvers.add(child);
083                        }
084                });
085                                
086                while (childrenIterator.hasNext() && childMarkupFound == null)
087                {
088                        Component sibling = childrenIterator.next();
089
090                        if (sibling == child || !sibling.isVisible() || !(sibling instanceof MarkupContainer))
091                        {
092                                continue;
093                        }
094                        
095                        IMarkupFragment siblingMarkup = containerMarkup.find(sibling.getId());
096                        
097                        if (siblingMarkup != null)
098                        {
099                                if (sibling instanceof IComponentResolver)
100                                {
101                                        childMarkupFound = searchInNestedTransparentResolvers(containerMarkup, child, componentResolvers);
102                                }
103                                else 
104                                {
105                                        childMarkupFound = searchMarkupInTransparentResolvers((MarkupContainer)sibling, siblingMarkup, child);
106                                }
107                        }
108                }
109                
110                return childMarkupFound;
111        }
112        
113        /**
114         * 
115         * Search for the markup of a child that might be nested inside
116         * transparent siblings. For example:
117         * 
118         * <pre>
119         * &lt;div wicket:id=&quot;outerTransparent&quot;&gt;
120         *      &lt;div wicket:id=&quot;innerTransparent&quot;&gt;
121         *       &lt;span wicket:id=&quot;childComponent&quot;&gt;&lt;/span&gt;
122         *      &lt;/div&gt;
123         * &lt;/div&gt;
124         * </pre>
125         * 
126         * @param
127         *                containerMarkup
128         *                        the markup of the parent container.
129         * @param child
130         *            The component to find the markup for.
131         * @param componentResolvers
132         *                        the transparent siblings           
133         *
134         * @return the markup fragment for the child, or {@code null}.
135         */
136        protected IMarkupFragment searchInNestedTransparentResolvers(IMarkupFragment containerMarkup, Component child, 
137                List<MarkupContainer> componentResolvers)
138        {
139                IMarkupFragment childMarkupFound = null;
140                
141                for (MarkupContainer componentResolver : componentResolvers)
142                {
143                        IMarkupFragment resolverMarkup = containerMarkup.find(componentResolver.getId());
144                        IMarkupFragment childMarkup = resolverMarkup != null ? resolverMarkup.find(child.getId()) : null;
145                        
146                        if (childMarkup != null)
147                        {
148                        IComponentResolver resolverContainer = (IComponentResolver)componentResolver;
149                                MarkupStream stream = new MarkupStream(childMarkup);
150                                ComponentTag tag = stream.getTag();
151                                
152                                Component resolvedComponent = componentResolver.get(tag.getId());
153                                if (resolvedComponent == null)
154                                {
155                                        resolvedComponent = resolverContainer.resolve(componentResolver, stream, tag);
156                                }
157                                
158                                if (child == resolvedComponent)
159                                {
160                                        childMarkupFound = childMarkup;
161                                }
162                        }
163                        else if (resolverMarkup != null)
164                        {
165                                List<MarkupContainer> otherResolvers = new ArrayList<>(componentResolvers);
166                                
167                                otherResolvers.remove(componentResolver);
168                                
169                                childMarkupFound = searchInNestedTransparentResolvers(resolverMarkup, child, otherResolvers);
170                        }
171
172                    if (childMarkupFound != null)
173                        {
174                                break;
175                        }
176                }
177                
178                return childMarkupFound;
179        }
180
181        /**
182         * Make sure we open up open-close tags to open-body-close
183         */
184        @Override
185        public void onComponentTag(final Component component, final ComponentTag tag)
186        {
187                if (tag.isOpenClose())
188                {
189                        tag.setType(TagType.OPEN);
190                }
191        }
192
193        /**
194         * Skip the components body which is expected to be raw markup only (no wicket components). It
195         * will be replaced by the associated markup.
196         */
197        @Override
198        public void onComponentTagBody(final Component component, final MarkupStream markupStream,
199                final ComponentTag openTag)
200        {
201                // Skip the components body. It will be replaced by the associated markup or fragment
202                if (markupStream.getPreviousTag().isOpen())
203                {
204                        markupStream.skipRawMarkup();
205                        if (markupStream.get().closes(openTag) == false)
206                        {
207                                throw new MarkupException(
208                                        markupStream,
209                                        "Close tag not found for tag: " +
210                                                openTag.toString() +
211                                                ". For " +
212                                                        Classes.simpleName(component.getClass()) +
213                                                                " Components only raw markup is allow in between the tags but not other Wicket Component." +
214                                                                ". Component: " + component.toString());
215                        }
216                }
217        }
218
219        /**
220         * Empty. Nothing to be added to the response by default.
221         */
222        @Override
223        public void renderHead(final Component component, HtmlHeaderContainer container)
224        {
225        }
226}