View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.view.facelets.tag;
20  
21  import org.apache.myfaces.shared.util.ClassUtils;
22  import org.apache.myfaces.view.facelets.util.ParameterCheck;
23  
24  import javax.faces.view.facelets.FaceletContext;
25  import javax.faces.view.facelets.MetaRule;
26  import javax.faces.view.facelets.MetaRuleset;
27  import javax.faces.view.facelets.Metadata;
28  import javax.faces.view.facelets.MetadataTarget;
29  import javax.faces.view.facelets.Tag;
30  import javax.faces.view.facelets.TagAttribute;
31  import javax.faces.view.facelets.TagException;
32  import java.beans.IntrospectionException;
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.WeakHashMap;
38  import java.util.logging.Level;
39  import java.util.logging.Logger;
40  import org.apache.myfaces.view.facelets.PassthroughRule;
41  import org.apache.myfaces.view.facelets.tag.jsf.PassThroughLibrary;
42  
43  /**
44   * 
45   * @author Jacob Hookom
46   * @version $Id$
47   */
48  public final class MetaRulesetImpl extends MetaRuleset
49  {
50      private final static Metadata NONE = new NullMetadata();
51  
52      //private final static Logger log = Logger.getLogger("facelets.tag.meta");
53      private final static Logger log = Logger.getLogger(MetaRulesetImpl.class.getName());
54  
55      /**
56       * Cache the MetadataTarget instances per ClassLoader using the Class-Name of the type variable
57       * of MetadataTarget.
58       * NOTE that we do it this way, because the only other valid way in order to support a shared
59       * classloader scenario would be to use a WeakHashMap<Class<?>, MetadataTarget>, but this
60       * creates a cyclic reference between the key and the value of the WeakHashMap which will
61       * most certainly cause a memory leak! Furthermore we can manually cleanup the Map when
62       * the webapp is undeployed just by removing the Map for the current ClassLoader. 
63       */
64      private volatile static WeakHashMap<ClassLoader, Map<String, MetadataTarget>> metadata
65              = new WeakHashMap<ClassLoader, Map<String, MetadataTarget>>();
66  
67      /**
68       * Removes the cached MetadataTarget instances in order to prevent a memory leak.
69       */
70      public static void clearMetadataTargetCache()
71      {
72          metadata.remove(ClassUtils.getContextClassLoader());
73      }
74  
75      private static Map<String, MetadataTarget> getMetaData()
76      {
77          ClassLoader cl = ClassUtils.getContextClassLoader();
78          
79          Map<String, MetadataTarget> metadata = (Map<String, MetadataTarget>)
80                  MetaRulesetImpl.metadata.get(cl);
81  
82          if (metadata == null)
83          {
84              // Ensure thread-safe put over _metadata, and only create one map
85              // per classloader to hold metadata.
86              synchronized (MetaRulesetImpl.metadata)
87              {
88                  metadata = createMetaData(cl, metadata);
89              }
90          }
91  
92          return metadata;
93      }
94      
95      private static Map<String, MetadataTarget> createMetaData(ClassLoader cl, Map<String, MetadataTarget> metadata)
96      {
97          metadata = (Map<String, MetadataTarget>) MetaRulesetImpl.metadata.get(cl);
98          if (metadata == null)
99          {
100             metadata = new HashMap<String, MetadataTarget>();
101             MetaRulesetImpl.metadata.put(cl, metadata);
102         }
103         return metadata;
104     }
105 
106     private final static TagAttribute[] EMPTY = new TagAttribute[0];
107     
108     private final Map<String, TagAttribute> _attributes;
109     
110     private final TagAttribute[] _passthroughAttributes;
111 
112     private final List<Metadata> _mappers;
113 
114     private final List<MetaRule> _rules;
115 
116     private final Tag _tag;
117 
118     private final Class<?> _type;
119     
120     private final List<MetaRule> _passthroughRules;
121     
122     public MetaRulesetImpl(Tag tag, Class<?> type)
123     {
124         _tag = tag;
125         _type = type;
126         TagAttribute[] allAttributes = _tag.getAttributes().getAll();
127         // This map is proportional to the number of attributes defined, and usually
128         // the properties with alias are very few, so set an initial size close to
129         // the number of attributes is ok.
130         int initialSize = allAttributes.length > 0 ? (allAttributes.length * 4 + 3) / 3 : 4;
131         _attributes = new HashMap<String, TagAttribute>(initialSize);
132         _mappers = new ArrayList<Metadata>(initialSize);
133         // Usually ComponentTagHandlerDelegate has 5 rules at max
134         // and CompositeComponentResourceTagHandler 6, so 8 is a good number
135         _rules = new ArrayList<MetaRule>(8); 
136         _passthroughRules = new ArrayList<MetaRule>(2);
137 
138         // Passthrough attributes are different from normal attributes, because they
139         // are just rendered into the markup without additional processing from the
140         // renderer. Here it starts attribute processing, so this is the best place 
141         // to find the passthrough attributes.
142         TagAttribute[] passthroughAttribute = _tag.getAttributes().getAll(
143             PassThroughLibrary.NAMESPACE);
144         TagAttribute[] passthroughAttributeAlias = _tag.getAttributes().getAll(
145             PassThroughLibrary.ALIAS_NAMESPACE);
146         
147         if (passthroughAttribute.length > 0 ||
148             passthroughAttributeAlias.length > 0)
149         {
150             _passthroughAttributes = new TagAttribute[passthroughAttribute.length+
151                 passthroughAttributeAlias.length];
152             int i = 0;
153             for (TagAttribute attribute : allAttributes)
154             {
155                 // The fastest check is check if the length is > 0, because
156                 // most attributes usually has no namespace attached.
157                 if (attribute.getNamespace().length() > 0 &&
158                     (PassThroughLibrary.NAMESPACE.equals(attribute.getNamespace()) ||
159                         PassThroughLibrary.ALIAS_NAMESPACE.equals(attribute.getNamespace())))
160                 {
161                     _passthroughAttributes[i] = attribute;
162                     i++;
163                 }
164                 else
165                 {
166                     _attributes.put(attribute.getLocalName(), attribute);
167                 }
168             }
169         }
170         else
171         {
172             _passthroughAttributes = EMPTY;
173             // setup attributes
174             for (TagAttribute attribute : allAttributes)
175             {
176                 _attributes.put(attribute.getLocalName(), attribute);
177             }
178         }
179 
180         // add default rules
181         _rules.add(BeanPropertyTagRule.INSTANCE);
182     }
183 
184     public MetaRuleset add(Metadata mapper)
185     {
186         ParameterCheck.notNull("mapper", mapper);
187 
188         if (!_mappers.contains(mapper))
189         {
190             _mappers.add(mapper);
191         }
192 
193         return this;
194     }
195 
196     public MetaRuleset addRule(MetaRule rule)
197     {
198         ParameterCheck.notNull("rule", rule);
199 
200         if (rule instanceof PassthroughRule)
201         {
202             _passthroughRules.add(rule);
203         }
204         else
205         {
206             _rules.add(rule);
207         }
208 
209         return this;
210     }
211 
212     public MetaRuleset alias(String attribute, String property)
213     {
214         ParameterCheck.notNull("attribute", attribute);
215         ParameterCheck.notNull("property", property);
216 
217         TagAttribute attr = (TagAttribute) _attributes.remove(attribute);
218         if (attr != null)
219         {
220             _attributes.put(property, attr);
221         }
222 
223         return this;
224     }
225 
226     public Metadata finish()
227     {
228         MetadataTarget target = null;
229         
230         assert !_rules.isEmpty();
231         
232         if (!_attributes.isEmpty())
233         {
234             target = this._getMetadataTarget();
235             int ruleEnd = _rules.size() - 1;
236 
237             // now iterate over attributes
238             for (Map.Entry<String, TagAttribute> entry : _attributes.entrySet())
239             {
240                 Metadata data = null;
241 
242                 int i = ruleEnd;
243 
244                 // First loop is always safe
245                 do
246                 {
247                     MetaRule rule = _rules.get(i);
248                     data = rule.applyRule(entry.getKey(), entry.getValue(), target);
249                     i--;
250                 } while (data == null && i >= 0);
251 
252                 if (data == null)
253                 {
254                     if (log.isLoggable(Level.SEVERE))
255                     {
256                         log.severe(entry.getValue() + " Unhandled by MetaTagHandler for type " + _type.getName());
257                     }
258                 }
259                 else
260                 {
261                     _mappers.add(data);
262                 }
263             }
264         }
265 
266         if (_passthroughAttributes.length > 0 &&
267             _passthroughRules.size() > 0)
268         {
269             if (target == null)
270             {
271                 target = this._getMetadataTarget();
272             }
273             int ruleEnd = _passthroughRules.size() - 1;
274 
275             // now iterate over attributes
276             for (TagAttribute passthroughAttribute : _passthroughAttributes)
277             {
278                 Metadata data = null;
279 
280                 int i = ruleEnd;
281 
282                 // First loop is always safe
283                 do
284                 {
285                     MetaRule rule = _passthroughRules.get(i);
286                     data = rule.applyRule(passthroughAttribute.getLocalName(),
287                         passthroughAttribute, target);
288                     i--;
289                 } while (data == null && i >= 0);
290 
291                 if (data == null)
292                 {
293                     if (log.isLoggable(Level.SEVERE))
294                     {
295                         log.severe(passthroughAttribute.getLocalName() + 
296                             " Unhandled by MetaTagHandler for type " + _type.getName());
297                     }
298                 }
299                 else
300                 {
301                     _mappers.add(data);
302                 }
303             }
304         }
305 
306         if (_mappers.isEmpty())
307         {
308             return NONE;
309         }
310         else
311         {
312             return new MetadataImpl(_mappers.toArray(new Metadata[_mappers.size()]));
313         }
314     }
315 
316     public MetaRuleset ignore(String attribute)
317     {
318         ParameterCheck.notNull("attribute", attribute);
319 
320         _attributes.remove(attribute);
321 
322         return this;
323     }
324 
325     public MetaRuleset ignoreAll()
326     {
327         _attributes.clear();
328 
329         return this;
330     }
331 
332     private MetadataTarget _getMetadataTarget()
333     {
334         Map<String, MetadataTarget> metadata = getMetaData();
335         String metaKey = _type.getName();
336 
337         MetadataTarget meta = metadata.get(metaKey);
338         if (meta == null)
339         {
340             try
341             {
342                 meta = new MetadataTargetImpl(_type);
343             }
344             catch (IntrospectionException e)
345             {
346                 throw new TagException(_tag, "Error Creating TargetMetadata", e);
347             }
348 
349             synchronized(metadata)
350             {
351                 // Use a synchronized block to ensure proper operation on concurrent use cases.
352                 // This is a racy single check, because initialization over the same class could happen
353                 // multiple times, but the same result is always calculated. The synchronized block 
354                 // just ensure thread-safety, because only one thread will modify the cache map
355                 // at the same time.
356                 metadata.put(metaKey, meta);
357             }
358         }
359 
360         return meta;
361     }
362 
363     private static class NullMetadata extends Metadata
364     {
365         /**
366          * {@inheritDoc}
367          */
368         @Override
369         public void applyMetadata(FaceletContext ctx, Object instance)
370         {
371             // do nothing
372         }
373     }
374 }