View Javadoc

1   /*******************************************************************************
2    * Portions created by Sebastian Thomschke are copyright (c) 2005-2013 Sebastian
3    * Thomschke.
4    *
5    * All Rights Reserved. This program and the accompanying materials
6    * are made available under the terms of the Eclipse Public License v1.0
7    * which accompanies this distribution, and is available at
8    * http://www.eclipse.org/legal/epl-v10.html
9    *
10   * Contributors:
11   *     Sebastian Thomschke - initial implementation.
12   *******************************************************************************/
13  package net.sf.oval.ogn;
14  
15  import java.lang.reflect.AccessibleObject;
16  import java.util.Locale;
17  
18  import net.sf.oval.exception.InvalidConfigurationException;
19  import net.sf.oval.internal.util.Assert;
20  import net.sf.oval.internal.util.ReflectionUtils;
21  
22  import org.apache.commons.jxpath.JXPathBeanInfo;
23  import org.apache.commons.jxpath.JXPathContext;
24  import org.apache.commons.jxpath.JXPathIntrospector;
25  import org.apache.commons.jxpath.JXPathNotFoundException;
26  import org.apache.commons.jxpath.Pointer;
27  import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
28  import org.apache.commons.jxpath.ri.QName;
29  import org.apache.commons.jxpath.ri.model.NodePointer;
30  import org.apache.commons.jxpath.ri.model.beans.BeanPointer;
31  import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory;
32  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
33  import org.apache.commons.jxpath.ri.model.beans.NullPropertyPointer;
34  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
35  
36  /**
37   * JXPath {@link "http://commons.apache.org/jxpath/"} based object graph navigator implementation.
38   * @author Sebastian Thomschke
39   */
40  public class ObjectGraphNavigatorJXPathImpl implements ObjectGraphNavigator
41  {
42  	protected static final class BeanPointerEx extends BeanPointer
43  	{
44  		private static final long serialVersionUID = 1L;
45  
46  		private final JXPathBeanInfo beanInfo;
47  
48  		public BeanPointerEx(final NodePointer parent, final QName name, final Object bean, final JXPathBeanInfo beanInfo)
49  		{
50  			super(parent, name, bean, beanInfo);
51  			this.beanInfo = beanInfo;
52  		}
53  
54  		public BeanPointerEx(final QName name, final Object bean, final JXPathBeanInfo beanInfo, final Locale locale)
55  		{
56  			super(name, bean, beanInfo, locale);
57  			this.beanInfo = beanInfo;
58  		}
59  
60  		@Override
61  		public boolean equals(final Object obj)
62  		{
63  			if (this == obj) return true;
64  			if (!super.equals(obj)) return false;
65  			if (getClass() != obj.getClass()) return false;
66  			final BeanPointerEx other = (BeanPointerEx) obj;
67  			if (beanInfo == null)
68  			{
69  				if (other.beanInfo != null) return false;
70  			}
71  			else if (!beanInfo.equals(other.beanInfo)) return false;
72  			return true;
73  		}
74  
75  		@Override
76  		public boolean isValidProperty(final QName name)
77  		{
78  			if (!super.isValidProperty(name)) return false;
79  
80  			// JXPath's default implementation returns true, even if the given property does not exit
81  			if (beanInfo.getPropertyDescriptor(name.getName()) == null)
82  				throw new JXPathNotFoundException("No pointer for xpath: " + toString() + "/" + name);
83  
84  			return true;
85  		}
86  	}
87  
88  	protected static final class BeanPointerFactoryEx extends BeanPointerFactory
89  	{
90  		@Override
91  		public NodePointer createNodePointer(final NodePointer parent, final QName name, final Object bean)
92  		{
93  			if (bean == null) return new NullPointer(parent, name);
94  
95  			final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(bean.getClass());
96  			return new BeanPointerEx(parent, name, bean, bi);
97  		}
98  
99  		@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 }