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  
41  /**
42   * 
43   * @author Jacob Hookom
44   * @version $Id: MetaRulesetImpl.java 1232196 2012-01-16 22:16:07Z lu4242 $
45   */
46  public final class MetaRulesetImpl extends MetaRuleset
47  {
48      private final static Metadata NONE = new NullMetadata();
49  
50      //private final static Logger log = Logger.getLogger("facelets.tag.meta");
51      private final static Logger log = Logger.getLogger(MetaRulesetImpl.class.getName());
52  
53      /**
54       * Cache the MetadataTarget instances per ClassLoader using the Class-Name of the type variable
55       * of MetadataTarget.
56       * NOTE that we do it this way, because the only other valid way in order to support a shared
57       * classloader scenario would be to use a WeakHashMap<Class<?>, MetadataTarget>, but this
58       * creates a cyclic reference between the key and the value of the WeakHashMap which will
59       * most certainly cause a memory leak! Furthermore we can manually cleanup the Map when
60       * the webapp is undeployed just by removing the Map for the current ClassLoader. 
61       */
62      private volatile static WeakHashMap<ClassLoader, Map<String, MetadataTarget>> metadata
63              = new WeakHashMap<ClassLoader, Map<String, MetadataTarget>>();
64  
65      /**
66       * Removes the cached MetadataTarget instances in order to prevent a memory leak.
67       */
68      public static void clearMetadataTargetCache()
69      {
70          metadata.remove(ClassUtils.getContextClassLoader());
71      }
72  
73      private static Map<String, MetadataTarget> getMetaData()
74      {
75          ClassLoader cl = ClassUtils.getContextClassLoader();
76          
77          Map<String, MetadataTarget> metadata = (Map<String, MetadataTarget>)
78                  MetaRulesetImpl.metadata.get(cl);
79  
80          if (metadata == null)
81          {
82              // Ensure thread-safe put over _metadata, and only create one map
83              // per classloader to hold metadata.
84              synchronized (MetaRulesetImpl.metadata)
85              {
86                  metadata = createMetaData(cl, metadata);
87              }
88          }
89  
90          return metadata;
91      }
92      
93      private static Map<String, MetadataTarget> createMetaData(ClassLoader cl, Map<String, MetadataTarget> metadata)
94      {
95          metadata = (Map<String, MetadataTarget>) MetaRulesetImpl.metadata.get(cl);
96          if (metadata == null)
97          {
98              metadata = new HashMap<String, MetadataTarget>();
99              MetaRulesetImpl.metadata.put(cl, metadata);
100         }
101         return metadata;
102     }
103 
104     private final Map<String, TagAttribute> _attributes;
105 
106     private final List<Metadata> _mappers;
107 
108     private final List<MetaRule> _rules;
109 
110     private final Tag _tag;
111 
112     private final Class<?> _type;
113 
114     public MetaRulesetImpl(Tag tag, Class<?> type)
115     {
116         _tag = tag;
117         _type = type;
118         TagAttribute[] allAttributes = _tag.getAttributes().getAll();
119         // This map is proportional to the number of attributes defined, and usually
120         // the properties with alias are very few, so set an initial size close to
121         // the number of attributes is ok.
122         int initialSize = allAttributes.length > 0 ? (allAttributes.length * 4 + 3) / 3 : 4;
123         _attributes = new HashMap<String, TagAttribute>(initialSize);
124         _mappers = new ArrayList<Metadata>(initialSize);
125         // Usually ComponentTagHandlerDelegate has 5 rules at max
126         // and CompositeComponentResourceTagHandler 6, so 8 is a good number
127         _rules = new ArrayList<MetaRule>(8); 
128 
129         // setup attributes
130         for (TagAttribute attribute : allAttributes)
131         {
132             _attributes.put(attribute.getLocalName(), attribute);
133         }
134 
135         // add default rules
136         _rules.add(BeanPropertyTagRule.Instance);
137     }
138 
139     public MetaRuleset add(Metadata mapper)
140     {
141         ParameterCheck.notNull("mapper", mapper);
142 
143         if (!_mappers.contains(mapper))
144         {
145             _mappers.add(mapper);
146         }
147 
148         return this;
149     }
150 
151     public MetaRuleset addRule(MetaRule rule)
152     {
153         ParameterCheck.notNull("rule", rule);
154 
155         _rules.add(rule);
156 
157         return this;
158     }
159 
160     public MetaRuleset alias(String attribute, String property)
161     {
162         ParameterCheck.notNull("attribute", attribute);
163         ParameterCheck.notNull("property", property);
164 
165         TagAttribute attr = (TagAttribute) _attributes.remove(attribute);
166         if (attr != null)
167         {
168             _attributes.put(property, attr);
169         }
170 
171         return this;
172     }
173 
174     public Metadata finish()
175     {
176         assert !_rules.isEmpty();
177         
178         if (!_attributes.isEmpty())
179         {
180             MetadataTarget target = this._getMetadataTarget();
181             int ruleEnd = _rules.size() - 1;
182 
183             // now iterate over attributes
184             for (Map.Entry<String, TagAttribute> entry : _attributes.entrySet())
185             {
186                 Metadata data = null;
187 
188                 int i = ruleEnd;
189 
190                 // First loop is always safe
191                 do
192                 {
193                     MetaRule rule = _rules.get(i);
194                     data = rule.applyRule(entry.getKey(), entry.getValue(), target);
195                     i--;
196                 } while (data == null && i >= 0);
197 
198                 if (data == null)
199                 {
200                     if (log.isLoggable(Level.SEVERE))
201                     {
202                         log.severe(entry.getValue() + " Unhandled by MetaTagHandler for type " + _type.getName());
203                     }
204                 }
205                 else
206                 {
207                     _mappers.add(data);
208                 }
209             }
210         }
211 
212         if (_mappers.isEmpty())
213         {
214             return NONE;
215         }
216         else
217         {
218             return new MetadataImpl(_mappers.toArray(new Metadata[_mappers.size()]));
219         }
220     }
221 
222     public MetaRuleset ignore(String attribute)
223     {
224         ParameterCheck.notNull("attribute", attribute);
225 
226         _attributes.remove(attribute);
227 
228         return this;
229     }
230 
231     public MetaRuleset ignoreAll()
232     {
233         _attributes.clear();
234 
235         return this;
236     }
237 
238     private final MetadataTarget _getMetadataTarget()
239     {
240         Map<String, MetadataTarget> metadata = getMetaData();
241         String metaKey = _type.getName();
242 
243         MetadataTarget meta = metadata.get(metaKey);
244         if (meta == null)
245         {
246             try
247             {
248                 meta = new MetadataTargetImpl(_type);
249             }
250             catch (IntrospectionException e)
251             {
252                 throw new TagException(_tag, "Error Creating TargetMetadata", e);
253             }
254 
255             synchronized(metadata)
256             {
257                 // Use a synchronized block to ensure proper operation on concurrent use cases.
258                 // This is a racy single check, because initialization over the same class could happen
259                 // multiple times, but the same result is always calculated. The synchronized block 
260                 // just ensure thread-safety, because only one thread will modify the cache map
261                 // at the same time.
262                 metadata.put(metaKey, meta);
263             }
264         }
265 
266         return meta;
267     }
268 
269     private static class NullMetadata extends Metadata
270     {
271         /**
272          * {@inheritDoc}
273          */
274         @Override
275         public void applyMetadata(FaceletContext ctx, Object instance)
276         {
277             // do nothing
278         }
279     }
280 }