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.head;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024
025import org.apache.wicket.model.IModel;
026import org.apache.wicket.model.Model;
027import org.apache.wicket.request.Response;
028import org.apache.wicket.util.lang.Args;
029import org.apache.wicket.util.string.Strings;
030import org.apache.wicket.util.value.ValueMap;
031
032/**
033 * {@link HeaderItem} for meta information such as <meta> tags or
034 * canonical <link>
035 * 
036 * @author andrea del bene
037 * @since 6.17.0
038 */
039public class MetaDataHeaderItem extends HeaderItem
040{
041        private static final long serialVersionUID = 1L;
042
043        /**
044         * The meta tag name
045         */
046        public static final String META_TAG = "meta";
047        /**
048         * the link tag name
049         */
050        public static final String LINK_TAG = "link";
051
052        private final Map<String, Object> tagAttributes;
053        private final List<String> tagMinimizedAttributes;
054        private final String tagName;
055
056        /**
057         * Build a new {@link MetaDataHeaderItem} having {@code tagName} as tag.
058         * 
059         * @param tagName
060         *              the name of the tag
061         */
062        public MetaDataHeaderItem(String tagName)
063        {
064                this.tagName = Args.notEmpty(tagName, "tagName");
065                this.tagAttributes = new ValueMap();
066                this.tagMinimizedAttributes = new ArrayList<>();
067        }
068
069        /**
070         * Add a tag attribute to the item. If the attribute value is a {@link IModel}, 
071         * the object wrapped inside the model is used as actual value.
072         * 
073         * @param attributeName
074         *              the attribute name
075         * @param attributeValue
076         *              the attribute value
077         * @return
078         *              The current item.
079         */
080        public MetaDataHeaderItem addTagAttribute(String attributeName, Object attributeValue)
081        {
082                Args.notEmpty(attributeName, "attributeName");
083                Args.notNull(attributeValue, "attributeValue");
084
085                tagAttributes.put(attributeName, attributeValue);
086                return this;
087        }
088        
089        /**
090         * Add a minimized tag attribute to the item. The attribute has no value and 
091         * only its name is rendered (for example 'async')
092         * 
093         * @param attributeName
094         *              the attribute name
095         * @return
096         *              The current item.
097         */
098        public MetaDataHeaderItem addTagAttribute(String attributeName)
099        {
100                Args.notEmpty(attributeName, "attributeName");
101                
102                tagMinimizedAttributes.add(attributeName);
103                return this;
104        }
105
106        /**
107         * Generate the string representation for the current item.
108         * 
109         * @return
110         *              The string representation for the current item.
111         */
112        public String generateString()
113        {
114                StringBuilder buffer = new StringBuilder();
115
116                buffer.append('<').append(tagName);
117
118                for (Map.Entry<String, Object> entry : tagAttributes.entrySet())
119                {
120                        Object value = entry.getValue();
121
122                        if (value instanceof IModel)
123                        {
124                                value = ((IModel<?>)value).getObject();
125                        }
126
127                        buffer.append(' ')
128                                .append(Strings.escapeMarkup(entry.getKey()));
129
130                        if (value != null)
131                        {
132                                buffer.append('=')
133                                        .append('"')
134                                        .append(Strings.replaceAll(value.toString(), "\"", "\\\""))
135                                        .append('"');
136                        }
137                }
138                
139                for (String attrName : tagMinimizedAttributes)
140                {
141                        buffer.append(' ')
142                                .append(Strings.escapeMarkup(attrName));
143                }
144
145                buffer.append(" />\n");
146
147                return buffer.toString();
148        }
149
150        @Override
151        public Iterable<?> getRenderTokens()
152        {
153                return Collections.singletonList(generateString());
154        }
155
156        @Override
157        public void render(Response response)
158        {
159                response.write(generateString());
160        }
161
162        /**
163         * Factory method to create &lt;meta&gt; tag.
164         * 
165         * @param httpEquiv
166         *              the 'httpEquiv' attribute of the tag
167         * @param content
168         *              the 'content' attribute of the tag
169         * @return
170         *              A new {@link MetaDataHeaderItem}
171         */
172        public static MetaDataHeaderItem forHttpEquiv(String httpEquiv, String content)
173        {
174                return forHttpEquiv(Model.of(httpEquiv), Model.of(content));
175        }
176
177        /**
178         * Factory method to create &lt;meta&gt; tag.
179         * 
180         * @param httpEquiv
181         *              the 'httpEquiv' attribute of the tag
182         * @param content
183         *              the 'content' attribute of the tag
184         * @return
185         *              A new {@link MetaDataHeaderItem}
186         */
187        public static MetaDataHeaderItem forHttpEquiv(IModel<String> httpEquiv, IModel<String> content)
188        {
189                MetaDataHeaderItem headerItem = new MetaDataHeaderItem(META_TAG);
190
191                headerItem.addTagAttribute("http-equiv", httpEquiv);
192                headerItem.addTagAttribute("content", content);
193
194                return headerItem;
195        }
196
197        /**
198         * Factory method to create &lt;meta&gt; tag.
199         * 
200         * @param name
201         *              the 'name' attribute of the tag
202         * @param content
203         *              the 'content' attribute of the tag
204         * @return
205         *              A new {@link MetaDataHeaderItem}
206         */
207        public static MetaDataHeaderItem forMetaTag(String name, String content)
208        {
209                return forMetaTag(Model.of(name), Model.of(content));
210        }
211
212        /**
213         * Factory method to create &lt;meta&gt; tag.
214         * 
215         * @param name
216         *              the 'name' attribute of the tag as String model
217         * @param content
218         *              the 'content' attribute of the tag as String model
219         * @return
220         *              A new {@link MetaDataHeaderItem}
221         */
222        public static MetaDataHeaderItem forMetaTag(IModel<String> name, IModel<String> content)
223        {
224                MetaDataHeaderItem headerItem = new MetaDataHeaderItem(META_TAG);
225
226                headerItem.addTagAttribute("name", name);
227                headerItem.addTagAttribute("content", content);
228
229                return headerItem;
230        }
231
232        /**
233         * Factory method to create &lt;link&gt; tag.
234         *  
235         * @param rel
236         *              the 'rel' attribute of the tag
237         * @param href
238         *              the 'href' attribute of the tag
239         * @return
240         *              A new {@link MetaDataHeaderItem}
241         */
242        public static MetaDataHeaderItem forLinkTag(String rel, String href)
243        {
244                return forLinkTag(Model.of(rel), Model.of(href));
245        }
246        
247        /**
248         * Factory method to create &lt;link&gt; tag.
249         *  
250         * @param rel
251         *              the 'rel' attribute of the tag as String model
252         * @param href
253         *              the 'href' attribute of the tag as String model
254         * @return
255         *              A new {@link MetaDataHeaderItem}
256         */
257        public static MetaDataHeaderItem forLinkTag(IModel<String> rel, IModel<String> href)
258        {
259                MetaDataHeaderItem headerItem = new MetaDataHeaderItem(LINK_TAG);
260
261                headerItem.addTagAttribute("rel", rel);
262                headerItem.addTagAttribute("href", href);
263
264                return headerItem;
265        }
266
267        @Override
268        public boolean equals(Object o)
269        {
270                if (this == o) return true;
271                if (o == null || getClass() != o.getClass()) return false;
272                MetaDataHeaderItem that = (MetaDataHeaderItem) o;
273                return Objects.equals(tagAttributes, that.tagAttributes) &&
274                                Objects.equals(tagMinimizedAttributes, that.tagMinimizedAttributes) &&
275                                Objects.equals(tagName, that.tagName);
276        }
277
278        @Override
279        public int hashCode()
280        {
281                // Not using `Objects.hash` for performance reasons
282                int result = tagAttributes != null ? tagAttributes.hashCode() : 0;
283                result = 31 * result + (tagMinimizedAttributes != null ? tagMinimizedAttributes.hashCode() : 0);
284                result = 31 * result + (tagName != null ? tagName.hashCode() : 0);
285                return result;
286        }
287}