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.el.unified.resolver;
20  
21  import java.beans.FeatureDescriptor;
22  import static java.lang.String.format;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Modifier;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.concurrent.ConcurrentHashMap;
31  import static java.util.logging.Level.FINE;
32  import java.util.logging.Logger;
33  import javax.el.ELContext;
34  import javax.el.ELException;
35  import javax.el.ELResolver;
36  import javax.el.PropertyNotFoundException;
37  import javax.el.PropertyNotWritableException;
38  import javax.faces.component.UIImportConstants;
39  import javax.faces.component.UIViewRoot;
40  import javax.faces.context.FacesContext;
41  import javax.faces.view.ViewMetadata;
42  import org.apache.myfaces.shared.util.ClassUtils;
43  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
44  
45  /**
46   *
47   */
48  public final class ImportConstantsELResolver extends ELResolver
49  {
50      private static final String ERROR_MISSING_CLASS = "Cannot find type '%s' in classpath.";
51      private static final String ERROR_FIELD_ACCESS = "Cannot access constant field '%s' of type '%s'.";
52  
53      private static final String IMPORT_CONSTANTS = "oam.importConstants";
54      
55      private Map<String, Map<String, Object> > constantsTypeMap = new ConcurrentHashMap<String, Map<String, Object> >();
56  
57      @Override
58      public Object getValue(final ELContext context, final Object base,
59              final Object property)
60              throws NullPointerException, PropertyNotFoundException, ELException
61      {
62          if (base != null)
63          {
64              return null;
65          }
66          if (property == null)
67          {
68              throw new PropertyNotFoundException();
69          }
70          if (!(property instanceof String))
71          {
72              return null;
73          }
74  
75          final FacesContext facesContext = facesContext(context);
76          if (facesContext == null)
77          {
78              return null;
79          }
80  
81          UIViewRoot viewRoot = facesContext.getViewRoot();
82          if (viewRoot == null)
83          {
84              return null;
85          }
86  
87          Map<String, String> importConstantsMap = (Map<String, String>) 
88                  viewRoot.getTransientStateHelper().getTransient(IMPORT_CONSTANTS);
89          if (importConstantsMap == null)
90          {
91              Collection<UIImportConstants> constants = ViewMetadata.getImportConstants(viewRoot);
92              if (constants != null && !constants.isEmpty())
93              {
94                  importConstantsMap = new HashMap<String, String>();
95                  for (UIImportConstants c : constants)
96                  {
97                      String var = c.getVar();
98                      String type = c.getType();
99                      if (var == null) 
100                     {
101                         int innerClass = type.lastIndexOf('$');
102                         int outerClass = type.lastIndexOf('.');
103                         var = type.substring(Math.max(innerClass, outerClass) + 1);
104                     }                    
105                     importConstantsMap.put(var, type);
106                 }
107             } 
108             else
109             {
110                 importConstantsMap = Collections.emptyMap();
111             }
112             if (!FaceletViewDeclarationLanguage.isBuildingViewMetadata(facesContext))
113             {
114                 viewRoot.getTransientStateHelper().putTransient(IMPORT_CONSTANTS, importConstantsMap);
115             }
116         }
117 
118         if (importConstantsMap != null && !importConstantsMap.isEmpty())
119         {
120             String type = importConstantsMap.get((String)property);
121             if (type != null)
122             {
123                 Map<String, Object> constantsMap = constantsTypeMap.get(type);
124                 if (constantsMap == null)
125                 {
126                     constantsMap = collectConstants(type);
127                     constantsTypeMap.put(type, constantsMap);
128                 }
129                 if (constantsMap != null && !constantsMap.isEmpty())
130                 {
131                     context.setPropertyResolved(true);
132                     return constantsMap;
133                 }
134             }
135         }
136         return null;
137     }
138 
139     @Override
140     public Class<?> getType(final ELContext context, final Object base,
141             final Object property)
142             throws NullPointerException, PropertyNotFoundException, ELException
143     {
144         return null;
145     }
146 
147     @Override
148     public void setValue(ELContext elc, Object o, Object o1, Object o2)
149             throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException
150     {
151         //No op
152     }
153 
154     @Override
155     public boolean isReadOnly(final ELContext context, final Object base,
156             final Object property)
157             throws NullPointerException, PropertyNotFoundException, ELException
158     {
159         return false;
160     }
161 
162     @Override
163     public Iterator<FeatureDescriptor> getFeatureDescriptors(final ELContext context, final Object base)
164     {
165         return null;
166     }
167 
168     @Override
169     public Class<?> getCommonPropertyType(final ELContext context, final Object base)
170     {
171         return base == null ? Object.class : null;
172     }
173 
174     // get the FacesContext from the ELContext
175     private static FacesContext facesContext(final ELContext context)
176     {
177         return (FacesContext) context.getContext(FacesContext.class);
178     }
179 
180     // Helpers --------------------------------------------------------------------------------------------------------
181     /**
182      * Collect constants of the given type. That are, all public static final fields of the given type.
183      *
184      * @param type The fully qualified name of the type to collect constants for.
185      * @return Constants of the given type.
186      */
187     private static Map<String, Object> collectConstants(final String type)
188     {
189         Map<String, Object> constants = new HashMap<String, Object>();
190 
191         for (Field field : toClass(type).getFields())
192         {
193             if (isPublicStaticFinal(field))
194             {
195                 try
196                 {
197                     constants.put(field.getName(), field.get(null));
198                 }
199                 catch (Exception e)
200                 {
201                     throw new IllegalArgumentException(format(ERROR_FIELD_ACCESS, type, field.getName()), e);
202                 }
203             }
204         }
205 
206         return constants;
207     }
208 
209     /**
210      * Convert the given type, which should represent a fully qualified name, to a concrete {@link Class} instance.
211      *
212      * @param type The fully qualified name of the class.
213      * @return The concrete {@link Class} instance.
214      * @throws IllegalArgumentException When it is missing in the classpath.
215      */
216     static Class<?> toClass(String type)
217     {
218         // Package-private so that ImportFunctions can also use it.
219         try
220         {
221             return ClassUtils.classForName(type);
222         } 
223         catch (ClassNotFoundException e)
224         {
225             // Perhaps it's an inner enum which is specified as com.example.SomeClass.SomeEnum.
226             // Let's be lenient on that although the proper type notation should be com.example.SomeClass$SomeEnum.
227             int i = type.lastIndexOf('.');
228 
229             if (i > 0)
230             {
231                 try
232                 {
233                     return toClass(new StringBuilder(type).replace(i, i + 1, "$").toString());
234                 } 
235                 catch (Exception ignore)
236                 {
237                     Logger.getLogger(ImportConstantsELResolver.class.getName()).log(
238                             FINE, "Ignoring thrown exception; previous exception will be rethrown instead.", ignore);
239                     // Just continue to IllegalArgumentException on original ClassNotFoundException.
240                 }
241             }
242 
243             throw new IllegalArgumentException(format(ERROR_MISSING_CLASS, type), e);
244         }
245     }
246 
247     /**
248      * Returns whether the given field is a constant field, that is when it is public, static and final.
249      *
250      * @param field The field to be checked.
251      * @return <code>true</code> if the given field is a constant field, otherwise <code>false</code>.
252      */
253     private static boolean isPublicStaticFinal(Field field)
254     {
255         int modifiers = field.getModifiers();
256         return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
257     }
258 
259 }