001/* 002 * (C) Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. 003 * 004 * The contents of this file are subject to the terms of either the GNU 005 * General Public License Version 2 only ("GPL") or the Common Development 006 * and Distribution License("CDDL") (collectively, the "License"). You 007 * may not use this file except in compliance with the License. You can 008 * obtain a copy of the License at 009 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html 010 * or packager/legal/LICENSE.txt. See the License for the specific 011 * language governing permissions and limitations under the License. 012 * 013 * When distributing the software, include this License Header Notice in each 014 * file and include the License file at packager/legal/LICENSE.txt. 015 * 016 * GPL Classpath Exception: 017 * Oracle designates this particular file as subject to the "Classpath" 018 * exception as provided by Oracle in the GPL Version 2 section of the License 019 * file that accompanied this code. 020 * 021 * Modifications: 022 * If applicable, add the following below the License Header, with the fields 023 * enclosed by brackets [] replaced by your own identifying information: 024 * "Portions Copyright [year] [name of copyright owner]" 025 * 026 * Contributor(s): 027 * If you wish your version of this file to be governed by only the CDDL or 028 * only the GPL Version 2, indicate your decision by adding "[Contributor] 029 * elects to include this software in this distribution under the [CDDL or GPL 030 * Version 2] license." If you don't indicate a single choice of license, a 031 * recipient has the option to distribute your version of this file under 032 * either the CDDL, the GPL Version 2 or to extend the choice of license to 033 * its licensees as provided above. However, if you add GPL Version 2 code 034 * and therefore, elected the GPL Version 2 license, then the option applies 035 * only if the new code is made subject to such option by the copyright 036 * holder. 037 * 038 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 039 * 040 * Contributors: 041 * Anahide Tchertchian 042 */ 043 044package org.nuxeo.ecm.platform.ui.web.validator; 045 046import java.beans.FeatureDescriptor; 047import java.util.Iterator; 048import java.util.Locale; 049 050import javax.el.ELContext; 051import javax.el.ELException; 052import javax.el.ELResolver; 053import javax.el.FunctionMapper; 054import javax.el.ValueExpression; 055import javax.el.ValueReference; 056import javax.el.VariableMapper; 057import javax.faces.el.CompositeComponentExpressionHolder; 058 059import org.nuxeo.ecm.platform.ui.web.model.ProtectedEditableModel; 060 061/** 062 * Analyzes a {@link ValueExpression} and provides access to the base object and property name to which the expression 063 * maps via the getReference() method. 064 * 065 * @since 7.2 066 */ 067public class ValueExpressionAnalyzer { 068 069 private ValueExpression expression; 070 071 public ValueExpressionAnalyzer(ValueExpression expression) { 072 this.expression = expression; 073 } 074 075 public ValueReference getReference(ELContext elContext) { 076 InterceptingResolver resolver = new InterceptingResolver(elContext.getELResolver()); 077 try { 078 expression.setValue(decorateELContext(elContext, resolver), null); 079 } catch (ELException ele) { 080 return null; 081 } 082 ValueReference reference = resolver.getValueReference(); 083 if (reference != null) { 084 Object base = reference.getBase(); 085 if (base instanceof CompositeComponentExpressionHolder) { 086 Object prop = reference.getProperty(); 087 if (prop instanceof String) { 088 ValueExpression ve = ((CompositeComponentExpressionHolder) base).getExpression((String) prop); 089 if (ve != null) { 090 this.expression = ve; 091 reference = getReference(elContext); 092 } 093 } 094 } 095 } 096 return reference; 097 } 098 099 private ELContext decorateELContext(final ELContext context, final ELResolver resolver) { 100 return new ELContext() { 101 102 // punch in our new ELResolver 103 @Override 104 public ELResolver getELResolver() { 105 return resolver; 106 } 107 108 // The rest of the methods simply delegate to the existing context 109 110 @Override 111 @SuppressWarnings("rawtypes") 112 public Object getContext(Class key) { 113 return context.getContext(key); 114 } 115 116 @Override 117 public Locale getLocale() { 118 return context.getLocale(); 119 } 120 121 @Override 122 public boolean isPropertyResolved() { 123 return context.isPropertyResolved(); 124 } 125 126 @Override 127 @SuppressWarnings("rawtypes") 128 public void putContext(Class key, Object contextObject) { 129 context.putContext(key, contextObject); 130 } 131 132 @Override 133 public void setLocale(Locale locale) { 134 context.setLocale(locale); 135 } 136 137 @Override 138 public void setPropertyResolved(boolean resolved) { 139 context.setPropertyResolved(resolved); 140 } 141 142 @Override 143 public FunctionMapper getFunctionMapper() { 144 return context.getFunctionMapper(); 145 } 146 147 @Override 148 public VariableMapper getVariableMapper() { 149 return context.getVariableMapper(); 150 } 151 }; 152 } 153 154 static class ListItemMapper { 155 156 protected ProtectedEditableModel model; 157 158 protected Object value; 159 160 public ListItemMapper(ProtectedEditableModel model, Object value) { 161 super(); 162 this.model = model; 163 this.value = value; 164 } 165 166 public ProtectedEditableModel getModel() { 167 return model; 168 } 169 170 public Object getValue() { 171 return value; 172 } 173 174 @Override 175 public String toString() { 176 final StringBuilder buf = new StringBuilder(); 177 buf.append("ListItemMapper"); 178 buf.append(" {"); 179 buf.append(" model="); 180 buf.append(model); 181 buf.append(", value="); 182 buf.append(value); 183 buf.append('}'); 184 return buf.toString(); 185 } 186 187 } 188 189 private static class InterceptingResolver extends ELResolver { 190 191 private ELResolver delegate; 192 193 private ValueReference valueReference; 194 195 public InterceptingResolver(ELResolver delegate) { 196 this.delegate = delegate; 197 } 198 199 public ValueReference getValueReference() { 200 return valueReference; 201 } 202 203 // Capture the base and property rather than write the value 204 @Override 205 public void setValue(ELContext context, Object base, Object property, Object value) { 206 if (base != null && property != null) { 207 context.setPropertyResolved(true); 208 valueReference = new ValueReference(base, property.toString()); 209 } 210 } 211 212 // The rest of the methods simply delegate to the existing context 213 214 @Override 215 public Object getValue(ELContext context, Object base, Object property) { 216 Object value = delegate.getValue(context, base, property); 217 if (base != null && ProtectedEditableModel.class.isAssignableFrom(base.getClass()) 218 && "rowData".equals(property)) { 219 // mapping a list element => wrap result 220 return new ListItemMapper((ProtectedEditableModel) base, value); 221 } 222 return value; 223 } 224 225 @Override 226 public Class<?> getType(ELContext context, Object base, Object property) { 227 return delegate.getType(context, base, property); 228 } 229 230 @Override 231 public boolean isReadOnly(ELContext context, Object base, Object property) { 232 return delegate.isReadOnly(context, base, property); 233 } 234 235 @Override 236 public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) { 237 return delegate.getFeatureDescriptors(context, base); 238 } 239 240 @Override 241 public Class<?> getCommonPropertyType(ELContext context, Object base) { 242 return delegate.getCommonPropertyType(context, base); 243 } 244 245 } 246}