001 /******************************************************************************* 002 * Portions created by Sebastian Thomschke are copyright (c) 2005-2013 Sebastian 003 * Thomschke. 004 * 005 * All Rights Reserved. This program and the accompanying materials 006 * are made available under the terms of the Eclipse Public License v1.0 007 * which accompanies this distribution, and is available at 008 * http://www.eclipse.org/legal/epl-v10.html 009 * 010 * Contributors: 011 * Sebastian Thomschke - initial implementation. 012 *******************************************************************************/ 013 package net.sf.oval.ogn; 014 015 import java.lang.reflect.AccessibleObject; 016 import java.util.Locale; 017 018 import net.sf.oval.exception.InvalidConfigurationException; 019 import net.sf.oval.internal.util.Assert; 020 import net.sf.oval.internal.util.ReflectionUtils; 021 022 import org.apache.commons.jxpath.JXPathBeanInfo; 023 import org.apache.commons.jxpath.JXPathContext; 024 import org.apache.commons.jxpath.JXPathIntrospector; 025 import org.apache.commons.jxpath.JXPathNotFoundException; 026 import org.apache.commons.jxpath.Pointer; 027 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl; 028 import org.apache.commons.jxpath.ri.QName; 029 import org.apache.commons.jxpath.ri.model.NodePointer; 030 import org.apache.commons.jxpath.ri.model.beans.BeanPointer; 031 import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory; 032 import org.apache.commons.jxpath.ri.model.beans.NullPointer; 033 import org.apache.commons.jxpath.ri.model.beans.NullPropertyPointer; 034 import org.apache.commons.jxpath.ri.model.beans.PropertyPointer; 035 036 /** 037 * JXPath {@link "http://commons.apache.org/jxpath/"} based object graph navigator implementation. 038 * @author Sebastian Thomschke 039 */ 040 public class ObjectGraphNavigatorJXPathImpl implements ObjectGraphNavigator 041 { 042 protected static final class BeanPointerEx extends BeanPointer 043 { 044 private static final long serialVersionUID = 1L; 045 046 private final JXPathBeanInfo beanInfo; 047 048 public BeanPointerEx(final NodePointer parent, final QName name, final Object bean, final JXPathBeanInfo beanInfo) 049 { 050 super(parent, name, bean, beanInfo); 051 this.beanInfo = beanInfo; 052 } 053 054 public BeanPointerEx(final QName name, final Object bean, final JXPathBeanInfo beanInfo, final Locale locale) 055 { 056 super(name, bean, beanInfo, locale); 057 this.beanInfo = beanInfo; 058 } 059 060 @Override 061 public boolean equals(final Object obj) 062 { 063 if (this == obj) return true; 064 if (!super.equals(obj)) return false; 065 if (getClass() != obj.getClass()) return false; 066 final BeanPointerEx other = (BeanPointerEx) obj; 067 if (beanInfo == null) 068 { 069 if (other.beanInfo != null) return false; 070 } 071 else if (!beanInfo.equals(other.beanInfo)) return false; 072 return true; 073 } 074 075 @Override 076 public boolean isValidProperty(final QName name) 077 { 078 if (!super.isValidProperty(name)) return false; 079 080 // JXPath's default implementation returns true, even if the given property does not exit 081 if (beanInfo.getPropertyDescriptor(name.getName()) == null) 082 throw new JXPathNotFoundException("No pointer for xpath: " + toString() + "/" + name); 083 084 return true; 085 } 086 } 087 088 protected static final class BeanPointerFactoryEx extends BeanPointerFactory 089 { 090 @Override 091 public NodePointer createNodePointer(final NodePointer parent, final QName name, final Object bean) 092 { 093 if (bean == null) return new NullPointer(parent, name); 094 095 final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(bean.getClass()); 096 return new BeanPointerEx(parent, name, bean, bi); 097 } 098 099 @Override 100 public NodePointer createNodePointer(final QName name, final Object bean, final Locale locale) 101 { 102 final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(bean.getClass()); 103 return new BeanPointerEx(name, bean, bi, locale); 104 } 105 106 @Override 107 public int getOrder() 108 { 109 return BeanPointerFactory.BEAN_POINTER_FACTORY_ORDER - 1; 110 } 111 } 112 113 static 114 { 115 /* 116 * JXPath currently does not distinguish between invalid object graph paths, e.g. by referencing a non-existing property on a Java Bean, 117 * and incomplete object graph paths because of null-values. 118 * In both cases a JXPathNotFoundException is thrown if JXPathContext.lenient is <code>false</code>, and in both cases a NullPropertyPointer is returned if 119 * JXPathContext.lenient is <code>true</code>. 120 * 121 * Therefore we install a patched BeanPointerFactory that checks the existence of properties and throws a JXPathNotFoundException if it does not exist, no matter 122 * to which setting JXPathContext.lenient is set. 123 */ 124 JXPathContextReferenceImpl.addNodePointerFactory(new BeanPointerFactoryEx()); 125 } 126 127 public ObjectGraphNavigationResult navigateTo(final Object root, final String xpath) throws InvalidConfigurationException 128 { 129 Assert.argumentNotNull("root", root); 130 Assert.argumentNotNull("xpath", xpath); 131 132 try 133 { 134 final JXPathContext ctx = JXPathContext.newContext(root); 135 ctx.setLenient(true); // do not throw an exception if object graph is incomplete, e.g. contains null-values 136 final Pointer pointer = ctx.getPointer(xpath); 137 138 if (pointer instanceof NullPropertyPointer) return null; 139 140 if (pointer instanceof PropertyPointer) 141 { 142 final PropertyPointer pp = (PropertyPointer) pointer; 143 final Class< ? > beanClass = pp.getBean().getClass(); 144 AccessibleObject accessor = ReflectionUtils.getField(beanClass, pp.getPropertyName()); 145 if (accessor == null) accessor = ReflectionUtils.getGetter(beanClass, pp.getPropertyName()); 146 return new ObjectGraphNavigationResult(root, xpath, pp.getBean(), accessor, pointer.getValue()); 147 } 148 149 return new ObjectGraphNavigationResult(root, xpath, pointer.getNode(), null, pointer.getValue()); 150 } 151 catch (final JXPathNotFoundException ex) 152 { 153 // thrown if the xpath is invalid 154 throw new InvalidConfigurationException(ex); 155 } 156 } 157 }