001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.persistence.engines.jdbcengine.proxy;
051:
052: import java.lang.reflect.InvocationHandler;
053: import java.lang.reflect.InvocationTargetException;
054: import java.lang.reflect.Method;
055: import java.util.*;
056: import org.jaffa.datatypes.*;
057: import org.jaffa.metadata.*;
058: import org.jaffa.rules.RulesEngine;
059: import org.jaffa.persistence.exceptions.*;
060: import org.jaffa.exceptions.FrameworkException;
061: import org.jaffa.persistence.IPersistent;
062: import org.jaffa.persistence.engines.jdbcengine.configservice.ClassMetaData;
063: import org.jaffa.persistence.engines.jdbcengine.configservice.ConfigurationService;
064: import org.jaffa.persistence.util.PersistentHelper;
065:
066: /** This is the InvocationHandler implemenation for the proxy class generated by the PersistentInstanceFactory.
067: * It delegates all IPersistent calls to an instance of the PersistentInstanceDelegate.
068: * It maintains all the fields of the actual persistent class in a Map and all the getters and setters simply manipulate that map.
069: */
070: class PersistentInstanceInvocationHandler implements InvocationHandler {
071:
072: private Class m_actualPersistentClass = null;
073: private PersistentDelegate m_persistentDelegate = new PersistentDelegate();
074: private Map m_fields = new HashMap();
075:
076: /** Creates an instance of the PersistentInstanceInvocationHandler.
077: * @param actualPersistentClass The actual class for which the Proxy is being generated.
078: */
079: PersistentInstanceInvocationHandler(Class actualPersistentClass) {
080: m_actualPersistentClass = actualPersistentClass;
081: }
082:
083: /** Processes a method invocation on a proxy instance and returns the result.
084: * @param proxy the proxy instance that the method was invoked on.
085: * @param method the Method instance corresponding to the interface method invoked on the proxy instance.
086: * @param args an array of objects containing the values of the arguments passed in the method invocation on the proxy instance.
087: * @return the value to return from the method invocation on the proxy instance.
088: * @throws Throwable the exception to throw from the method invocation on the proxy instance.
089: */
090: public Object invoke(Object proxy, Method method, Object[] args)
091: throws Throwable {
092: try {
093: Object output = null;
094: stampProxyOnPersistentDelegate(proxy);
095: Class declaringClass = method.getDeclaringClass();
096: if (declaringClass == IPersistent.class) {
097: // Invoke methods on the Persistent instance
098: output = proxyPersistent(method, args);
099: } else if (declaringClass == Object.class) {
100: // Handle the non-final methods of the Object class
101: String methodName = method.getName();
102: if (methodName.equals("toString")) {
103: output = proxyToString();
104: } else if (methodName.equals("hashCode")) {
105: output = proxyHashCode();
106: } else if (methodName.equals("equals")
107: && args.length == 1) {
108: output = proxyEquals(proxy, args[0]);
109: } else {
110: throw new RuntimeException(
111: "Cannot handle the method " + method);
112: }
113: } else {
114: String methodName = method.getName();
115: if (methodName.startsWith("get")) {
116: String fieldName = methodName.substring("get"
117: .length());
118: output = proxyGet(fieldName);
119: } else if (methodName.startsWith("is")) {
120: String fieldName = methodName.substring("is"
121: .length());
122: output = proxyGet(fieldName);
123: } else if (methodName.startsWith("set")
124: && args.length == 1) {
125: String fieldName = methodName.substring("set"
126: .length());
127: proxySet(fieldName, args[0], (IPersistent) proxy);
128: } else if (methodName.startsWith("update")
129: && args.length == 1) {
130: String fieldName = methodName.substring("update"
131: .length());
132: proxyUpdate(fieldName, args[0], (IPersistent) proxy);
133: } else if (methodName.startsWith("validate")
134: && args.length == 1) {
135: String fieldName = methodName.substring("validate"
136: .length());
137: proxyValidate(fieldName, args[0],
138: (IPersistent) proxy);
139: } else {
140: throw new RuntimeException(
141: "Cannot handle the method " + method);
142: }
143: }
144: return output;
145: } catch (InvocationTargetException e) {
146: throw e.getCause() != null ? e.getCause() : e;
147: }
148: }
149:
150: /** Stamps the proxy instance on the persistent delegate. */
151: private void stampProxyOnPersistentDelegate(Object proxy) {
152: if (m_persistentDelegate.m_proxy == null)
153: m_persistentDelegate.m_proxy = (IPersistent) proxy;
154: }
155:
156: // *****************************************************************
157: // ******** PROXY METHODS FOR THE IPersistent Interface ************
158: // *****************************************************************
159: /** Invokes calls on the persistent delegate. */
160: private Object proxyPersistent(Method method, Object[] args)
161: throws Throwable {
162: return method.invoke(m_persistentDelegate, args);
163: }
164:
165: // *****************************************************************
166: // ******** PROXY METHODS FOR THE Object CLASS *********************
167: // *****************************************************************
168: /** The handler for the toString() method. */
169: private String proxyToString() {
170: StringBuffer buf = new StringBuffer();
171: buf.append("<DomainClassName>");
172: buf.append(m_actualPersistentClass.getName());
173: buf.append("</DomainClassName>");
174: buf.append(m_persistentDelegate.toString());
175: buf.append(m_fields);
176: return buf.toString();
177: }
178:
179: /** The handler for the hashCode() method. */
180: private Integer proxyHashCode() {
181: return new Integer(m_actualPersistentClass.hashCode()
182: + m_persistentDelegate.hashCode() + m_fields.hashCode());
183: }
184:
185: /** The handler for the equals() method. */
186: private Boolean proxyEquals(Object proxy, Object other) {
187: return proxy == other ? Boolean.TRUE : Boolean.FALSE;
188: }
189:
190: // *****************************************************************
191: // ******** PROXY METHODS FOR THE ACTUAL PERSISTENT CLASS **********
192: // *****************************************************************
193: /** The handler for the getXyz() method. */
194: private Object proxyGet(String fieldName) {
195: return m_fields.get(fieldName);
196: }
197:
198: /** The handler for the setXyz() method. */
199: private void proxySet(String fieldName, Object value,
200: IPersistent persistentObject) throws ValidationException,
201: UpdatePrimaryKeyException, ReadOnlyObjectException,
202: AlreadyLockedObjectException, FrameworkException {
203: // ignore, if the current value and new value are the same
204: Object currentValue = proxyGet(fieldName);
205: if (currentValue == null ? value == null : currentValue
206: .equals(value))
207: return;
208:
209: // do not update a key field, if its a database occurence.
210: if (isKeyField(fieldName, persistentObject)
211: && m_persistentDelegate.isDatabaseOccurence())
212: throw new UpdatePrimaryKeyException();
213:
214: proxyValidate(fieldName, value, persistentObject);
215: m_persistentDelegate.invokeUpdate();
216: m_persistentDelegate.invokeAddInitialValue(fieldName,
217: currentValue);
218: m_fields.put(fieldName, value);
219: }
220:
221: /** The handler for the updateXyz() method.
222: * This is for backwards compatibilty and merely invokes the proxySet() method.
223: */
224: private void proxyUpdate(String fieldName, Object value,
225: IPersistent persistentObject) throws ValidationException,
226: UpdatePrimaryKeyException, ReadOnlyObjectException,
227: AlreadyLockedObjectException, FrameworkException {
228: proxySet(fieldName, value, persistentObject);
229: }
230:
231: /** The handler for the validateXyz() method. */
232: private void proxyValidate(String fieldName, Object value,
233: IPersistent persistentObject) throws ValidationException,
234: FrameworkException {
235: // Invokes the validate method on the FieldValidator
236: invokeFieldValidator(fieldName, value, persistentObject);
237:
238: // Invoke the Dynamic Rules Engine
239: RulesEngine.doAllValidationsForDomainField(
240: m_actualPersistentClass.getName(), fieldName, value,
241: m_persistentDelegate.getUOW());
242: }
243:
244: /** Returns true if the field is a key */
245: private boolean isKeyField(String fieldName,
246: IPersistent persistentObject) {
247: ClassMetaData classMetaData = ConfigurationService
248: .getInstance().getMetaData(
249: PersistentInstanceFactory
250: .getActualPersistentClass(
251: persistentObject).getName());
252: return classMetaData.getAllKeyFieldNames().containsKey(
253: fieldName) ? true : false;
254: }
255:
256: /** Invokes FieldValidator.validate(value, MetaClass.META_FIELD_NAME, true).
257: */
258: private void invokeFieldValidator(String fieldName, Object value,
259: IPersistent persistentObject) throws ValidationException,
260: FrameworkException {
261: Class domainClass = PersistentInstanceFactory
262: .getActualPersistentClass(persistentObject);
263:
264: try {
265: // Determine the datatype for the fieldName from the ClassMetaData
266: // First Look at the regular-field map. If that doesn't work, then look at the key-field map
267: ClassMetaData classMetaData = ConfigurationService
268: .getInstance().getMetaData(domainClass.getName());
269: String type = classMetaData.getType(fieldName);
270: if (type == null)
271: type = (String) classMetaData.getAllKeyFieldNames()
272: .get(fieldName);
273: Class clazz = type.equals("byte[]") ? byte[].class : Class
274: .forName(type);
275:
276: // Determine the FieldMetaData class
277: FieldMetaData fieldMetaData = PersistentHelper
278: .getFieldMetaData(domainClass.getName(), fieldName);
279:
280: // Get a handle on the method 'public static void validate(Object, FieldMetaData, boolean)' of the FieldValidator class
281: Method m = FieldValidator.class.getMethod("validate",
282: new Class[] { clazz, fieldMetaData.getClass(),
283: Boolean.TYPE });
284:
285: // Now invoke the method
286: m.invoke(null, new Object[] { value, fieldMetaData,
287: Boolean.TRUE });
288: } catch (ClassNotFoundException e) {
289: // MetaClass doesn't exist. Do nothing
290: } catch (NoSuchMethodException e) {
291: // Method 'public static FieldMetaData getFieldMetaData(String fieldName)' doesn't exist. Do nothing
292: } catch (InvocationTargetException e) {
293: if (e.getCause() != null) {
294: if (e.getCause() instanceof ValidationException)
295: throw (ValidationException) e.getCause();
296: else if (e.getCause() instanceof FrameworkException)
297: throw (FrameworkException) e.getCause();
298: }
299: throw new ProxyFieldValidatoRuntimeException(
300: "Exception thrown when validating " + fieldName
301: + " having the value " + value, e);
302: } catch (Exception e) {
303: throw new ProxyFieldValidatoRuntimeException(
304: "Exception thrown when validating " + fieldName
305: + " having the value " + value, e);
306: }
307: }
308:
309: /** This is exclusively for use by the PersistentInstanceFactory.
310: * This will merely add the attribute/value to the Map.
311: * This will bypass the validations performed during invocation of the setXyz() method on the persistent object.
312: * @param attributeName The attribute whose value will be set.
313: * @param value The value to be set.
314: */
315: void addAttributeValue(String attributeName, Object value) {
316: m_fields.put(attributeName, value);
317: }
318:
319: }
|