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.parser;
018
019import java.text.ParseException;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.concurrent.atomic.AtomicInteger;
023
024import org.apache.wicket.MetaDataKey;
025import org.apache.wicket.markup.ComponentTag;
026import org.apache.wicket.markup.ContainerInfo;
027import org.apache.wicket.markup.HtmlSpecialTag;
028import org.apache.wicket.markup.Markup;
029import org.apache.wicket.markup.MarkupElement;
030import org.apache.wicket.markup.MarkupParser;
031import org.apache.wicket.markup.MarkupResourceStream;
032import org.apache.wicket.markup.MarkupStream;
033import org.apache.wicket.request.cycle.RequestCycle;
034
035
036/**
037 * Base class for markup filters
038 * 
039 * @author Jonathan Locke
040 * @author Juergen Donnerstag
041 */
042public abstract class AbstractMarkupFilter implements IMarkupFilter
043{
044        /** The markup created by reading the markup file */
045        private final MarkupResourceStream markupResourceStream;
046
047        /** The next MarkupFilter in the chain */
048        private IMarkupFilter parent;
049
050        /**
051         *  A key for a request-relative map of counters.
052         *  As map keys we use the class name of the {@link org.apache.wicket.markup.MarkupResourceStream}'s owner 
053         *  container (see {@link org.apache.wicket.markup.MarkupResourceStream#getContainerInfo()}), 
054         *  meaning that each container has its own counter. 
055         *  The counters are used by {@link #getRequestUniqueId()} to get unique ids for markup tags.
056         * **/
057        protected final static MetaDataKey<Map<String, AtomicInteger>> REQUEST_COUNTER_KEY = new MetaDataKey<>()
058        {
059                private static final long serialVersionUID = 1L;
060        };
061
062        /**
063         * Construct.
064         */
065        public AbstractMarkupFilter()
066        {
067                this(null);
068        }
069
070        public AbstractMarkupFilter(final MarkupResourceStream markupResourceStream)
071        {
072                this.markupResourceStream = markupResourceStream;
073        }
074
075
076        /**
077         * @return The next MarkupFilter in the chain
078         */
079        @Override
080        public IMarkupFilter getNextFilter()
081        {
082                return parent;
083        }
084
085        /**
086         * Set new parent.
087         * 
088         * @param parent
089         *            The parent of this component The next element in the chain
090         */
091        @Override
092        public void setNextFilter(final IMarkupFilter parent)
093        {
094                this.parent = parent;
095        }
096
097        /**
098         * Get the next xml element from the markup. If eof, than retun null. Ignore raw markup. Invoke
099         * nextTag(tag) if a tag was found.
100         */
101        @Override
102        public MarkupElement nextElement() throws ParseException
103        {
104                MarkupElement elem = getNextFilter().nextElement();
105                if (elem != null)
106                {
107                        if (elem instanceof ComponentTag)
108                        {
109                                elem = onComponentTag((ComponentTag)elem);
110                        }
111                        else if (elem instanceof HtmlSpecialTag)
112                        {
113                                elem = onSpecialTag((HtmlSpecialTag)elem);
114                        }
115                }
116                return elem;
117        }
118
119        /**
120         * Invoked when a ComponentTag was found.
121         * <p>
122         * By default this method is also called for WicketTags.
123         * 
124         * @param tag
125         * @return Usually the same as the tag attribute
126         * @throws ParseException
127         */
128        protected abstract MarkupElement onComponentTag(ComponentTag tag) throws ParseException;
129
130        /**
131         * Invoked when a WicketTag was found.
132         * 
133         * @param tag
134         * @return Usually the same as the tag attribute
135         * @throws ParseException
136         */
137
138        /**
139         * Invoked when a tags (e.g. DOCTYPE, PROCESSING_INSTRUCTIION, etc. which have been identified
140         * as special tags by the xml parser.
141         * 
142         * @param tag
143         * @return Usually the same as the tag attribute
144         * @throws ParseException
145         */
146        protected MarkupElement onSpecialTag(final HtmlSpecialTag tag) throws ParseException
147        {
148                return tag;
149        }
150
151        @Override
152        public void postProcess(final Markup markup)
153        {
154        }
155
156        protected MarkupResourceStream getMarkupResourceStream() {
157                return markupResourceStream;
158        }
159
160        /**
161         * Extracts the markup namespace from the MarkupResourceStream
162         * passed at creation time.
163         *
164         * <p>
165         *     There are two versions of this method because most IMarkupFilter's
166         *     have dual personality - {@link IMarkupFilter} (one instance per MarkupParser)
167         *     and {@link org.apache.wicket.markup.resolver.IComponentResolver} (one
168         *     instance per application).
169         * </p>
170         *
171         * @return the namespace of the loaded markup
172         */
173        protected String getWicketNamespace()
174        {
175                return getWicketNamespace(null);
176        }
177
178        /**
179         * Extracts the markup namespace from the passed MarkupStream if available,
180         * or from the MarkupResourceStream passed at creation time.
181         *
182         * <p>
183         *     There are two versions of this method because most IMarkupFilter's
184         *     have dual personality - {@link IMarkupFilter} (one instance per MarkupParser)
185         *     and {@link org.apache.wicket.markup.resolver.IComponentResolver} (one
186         *     instance per application).
187         * </p>
188         *
189         * @param markupStream
190         *      the markup stream
191         * @return namespace extracted from the markup
192         */
193        protected String getWicketNamespace(final MarkupStream markupStream)
194        {
195                String wicketNamespace = MarkupParser.WICKET;
196                if (markupStream != null)
197                {
198                        wicketNamespace = markupStream.getWicketNamespace();
199                }
200                else if (markupResourceStream != null)
201                {
202                        wicketNamespace = markupResourceStream.getWicketNamespace();
203                }
204                return wicketNamespace;
205        }
206
207        /**
208         * Returns an id using the request-relative counter associated with the 
209         * underlying {@link org.apache.wicket.markup.MarkupResourceStream}'s owner container 
210         * (see {@link org.apache.wicket.markup.MarkupResourceStream#getContainerInfo()}). 
211         * This can be useful for autocomponent tags that need to get a tag id.
212         * 
213         * @return
214         *              the request-relative id
215         */
216        protected int getRequestUniqueId()
217        {
218                RequestCycle requestCycle = RequestCycle.get();
219                Map<String, AtomicInteger> markupUniqueCounters = requestCycle.getMetaData(REQUEST_COUNTER_KEY);
220                ContainerInfo containerInfo = getMarkupResourceStream().getContainerInfo();
221                String cacheKey = containerInfo != null ? containerInfo.getContainerClass().getCanonicalName() : null;
222                
223                if (markupUniqueCounters == null)
224                {
225                        markupUniqueCounters = new HashMap<>();
226                        
227                        requestCycle.setMetaData(REQUEST_COUNTER_KEY, markupUniqueCounters);
228                }
229                
230                AtomicInteger counter = markupUniqueCounters.get(cacheKey);
231                
232                if (counter == null)
233                {
234                        counter = new AtomicInteger();
235                        markupUniqueCounters.put(cacheKey, counter);
236                }
237                
238            int cacheHash = cacheKey == null ? 0 : cacheKey.hashCode();
239                
240            //add the counter value to the string hash 
241            //using the same algorithm of String#hashCode() 
242            return  cacheHash * 31 + counter.getAndIncrement();
243        }
244}