001    package org.apache.ojb.broker.core;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.util.Map;
019    
020    import org.apache.ojb.broker.Identity;
021    import org.apache.ojb.broker.IdentityFactory;
022    import org.apache.ojb.broker.OJBRuntimeException;
023    import org.apache.ojb.broker.PersistenceBroker;
024    import org.apache.ojb.broker.PersistenceBrokerException;
025    import org.apache.ojb.broker.PBStateListener;
026    import org.apache.ojb.broker.PBStateEvent;
027    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
028    import org.apache.ojb.broker.core.proxy.ProxyHelper;
029    import org.apache.ojb.broker.metadata.ClassDescriptor;
030    import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
031    import org.apache.ojb.broker.metadata.FieldDescriptor;
032    import org.apache.ojb.broker.util.BrokerHelper;
033    import org.apache.ojb.broker.util.sequence.SequenceManager;
034    import org.apache.ojb.broker.util.sequence.SequenceManagerTransientImpl;
035    import org.apache.commons.lang.SystemUtils;
036    import org.apache.commons.lang.ArrayUtils;
037    import org.apache.commons.lang.exception.ExceptionUtils;
038    import org.apache.commons.collections.map.ReferenceIdentityMap;
039    
040    /**
041     * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
042     * @version $Id: IdentityFactoryImpl.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
043     * @see org.apache.ojb.broker.IdentityFactory
044     */
045    public class IdentityFactoryImpl implements IdentityFactory, PBStateListener
046    {
047        private PersistenceBroker broker;
048        //private boolean activeTx;
049        private Map objectToIdentityMap;
050        private SequenceManager transientSequenceManager;
051    
052        public IdentityFactoryImpl(PersistenceBroker broker)
053        {
054            this.broker = broker;
055            this.objectToIdentityMap = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD, true);
056            this.transientSequenceManager = new SequenceManagerTransientImpl(broker);
057            broker.addListener(this, true);
058        }
059    
060        /**
061         * This methods creates a new transient (if at least one PK field is 'null') or persistent
062         * (if the PK fields are populated) {@link org.apache.ojb.broker.Identity} instance. If the specified object
063         * is transient and former call for the same object returns already a transient Identity, the same transient
064         * Identity object will be returned.
065         */
066        protected Identity createTransientOrRealIdentity(ClassDescriptor cld, Object objOrProxy)
067        {
068            if(objOrProxy == null) throw new OJBRuntimeException("Can't create Identity for 'null'-object");
069            Identity result = null;
070            Class topLevelClass = null;
071            Class realClass = null;
072            Object[] pks = null;
073            try
074            {
075                final IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objOrProxy);
076    
077                synchronized(objOrProxy)
078                {
079                    if(handler != null)
080                    {
081                        result = handler.getIdentity();
082                    }
083                    else
084                    {
085                        // now we are sure that the specified object is not a proxy
086                        realClass = objOrProxy.getClass();
087                        topLevelClass = broker.getTopLevelClass(objOrProxy.getClass());
088                        if(cld == null)
089                        {
090                            cld = broker.getClassDescriptor(objOrProxy.getClass());
091                        }
092                        BrokerHelper helper = broker.serviceBrokerHelper();
093    
094                        FieldDescriptor[] fields = cld.getPkFields();
095                        pks = new Object[fields.length];
096                        FieldDescriptor fld;
097                        for(int i = 0; i < fields.length; i++)
098                        {
099                            fld = fields[i];
100                            /*
101                            we check all PK fields for 'null'-values
102                            */
103                            Object value = fld.getPersistentField().get(objOrProxy);
104                            if(helper.representsNull(fld, value))
105                            {
106                                result = (Identity) objectToIdentityMap.get(objOrProxy);
107                                if(result == null)
108                                {
109                                    pks[i] = transientSequenceManager.getUniqueValue(fld);
110                                    result = new Identity(realClass, topLevelClass, pks, true);
111                                    //if(activeTx) objectToIdentityMap.put(objOrProxy, result);
112                                    objectToIdentityMap.put(objOrProxy, result);
113                                }
114                                break;
115                            }
116                            else
117                            {
118                                pks[i] = value;
119                            }
120                        }
121                        if(result == null)
122                        {
123                            result = new Identity(realClass, topLevelClass, pks, false);
124                        }
125                    }
126                }
127            }
128            catch(ClassNotPersistenceCapableException e)
129            {
130                throw e;
131            }
132            catch(Exception e)
133            {
134                throw createException(e, "Can not init Identity for given object.", objOrProxy, topLevelClass, realClass, pks);
135            }
136            return result;
137        }
138    
139        /** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Object) */
140        public Identity buildIdentity(Object obj)
141        {
142            return createTransientOrRealIdentity(broker.getClassDescriptor(ProxyHelper.getRealClass(obj)), obj);
143        }
144    
145        /** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Object) */
146        public Identity buildIdentity(ClassDescriptor cld, Object obj)
147        {
148            return createTransientOrRealIdentity(cld, obj);
149        }
150    
151        /** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, Class, String[], Object[]) */
152        public Identity buildIdentity(Class realClass, Class topLevelClass, String[] pkFieldNames, Object[] pkValues)
153        {
154            Object[] orderedPKValues = pkValues;
155            if(pkValues == null)
156            {
157                throw new NullPointerException("Given primary key value array can't be null");
158            }
159            if(pkValues.length == 1 && (pkFieldNames == null || pkFieldNames.length == 1))
160            {
161                /*
162                we assume only a single PK field is defined and do no further checks,
163                we have nothing to do
164                */
165            }
166            else
167            {
168                // in other more complex cases we do several check
169                FieldDescriptor[] flds = broker.getClassDescriptor(realClass).getPkFields();
170                if(!isOrdered(flds, pkFieldNames))
171                {
172                    orderedPKValues = reorderFieldValues(flds, pkFieldNames, pkValues);
173                }
174            }
175            return new Identity(realClass, topLevelClass, orderedPKValues);
176        }
177    
178        /**
179         * This method orders the specified field values based on the
180         * specified {@link org.apache.ojb.broker.metadata.FieldDescriptor}.
181         *
182         * @param flds The {@link org.apache.ojb.broker.metadata.FieldDescriptor} array.
183         * @param fieldNames The field names.
184         * @param fieldValues The field values.
185         * @return The ordered field values.
186         */
187        private Object[] reorderFieldValues(FieldDescriptor[] flds, String[] fieldNames, Object[] fieldValues)
188        {
189            String fieldName;
190            Object[] orderedValues = new Object[flds.length];
191            for(int i = 0; i < flds.length; i++)
192            {
193                fieldName = flds[i].getPersistentField().getName();
194                int realPosition = findIndexForName(fieldNames, fieldName);
195                orderedValues[i] = fieldValues[realPosition];
196            }
197            return orderedValues;
198        }
199    
200        /**
201         * Find the index of the specified name in field name array.
202         */
203        private int findIndexForName(String[] fieldNames, String searchName)
204        {
205            for(int i = 0; i < fieldNames.length; i++)
206            {
207                if(searchName.equals(fieldNames[i]))
208                {
209                    return i;
210                }
211            }
212            throw new PersistenceBrokerException("Can't find field name '" + searchName +
213                    "' in given array of field names");
214        }
215    
216        /** Checks length and compare order of field names with declared PK fields in metadata. */
217        private boolean isOrdered(FieldDescriptor[] flds, String[] pkFieldNames)
218        {
219            if((flds.length > 1 && pkFieldNames == null) || flds.length != pkFieldNames.length)
220            {
221                throw new PersistenceBrokerException("pkFieldName length does not match number of defined PK fields." +
222                        " Expected number of PK fields is " + flds.length + ", given number was " +
223                        (pkFieldNames != null ? pkFieldNames.length : 0));
224            }
225            boolean result = true;
226            for(int i = 0; i < flds.length; i++)
227            {
228                FieldDescriptor fld = flds[i];
229                result = result && fld.getPersistentField().getName().equals(pkFieldNames[i]);
230            }
231            return result;
232        }
233    
234        /** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, String[], Object[]) */
235        public Identity buildIdentity(Class realClass, String[] pkFieldNames, Object[] pkValues)
236        {
237            return buildIdentity(realClass, broker.getTopLevelClass(realClass), pkFieldNames, pkValues);
238        }
239    
240        /** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, String[], Object[]) */
241        public Identity buildIdentity(Class realClass, Class topLevelClass, Object[] pkValues)
242        {
243            return new Identity(realClass, topLevelClass, pkValues);
244        }
245    
246        /** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, Object) */
247        public Identity buildIdentity(Class realClass, Object pkValue)
248        {
249            return buildIdentity(realClass, (String[]) null, new Object[]{pkValue});
250        }
251    
252        /**
253         * Helper method which supports creation of proper error messages.
254         *
255         * @param ex An exception to include or <em>null</em>.
256         * @param message The error message or <em>null</em>.
257         * @param objectToIdentify The current used object or <em>null</em>.
258         * @param topLevelClass The object top-level class or <em>null</em>.
259         * @param realClass The object real class or <em>null</em>.
260         * @param pks The associated PK values of the object or <em>null</em>.
261         * @return The generated exception.
262         */
263        private PersistenceBrokerException createException(final Exception ex, String message, final Object objectToIdentify, Class topLevelClass, Class realClass, Object[] pks)
264        {
265            final String eol = SystemUtils.LINE_SEPARATOR;
266            StringBuffer msg = new StringBuffer();
267            if(message == null)
268            {
269                msg.append("Unexpected error: ");
270            }
271            else
272            {
273                msg.append(message).append(" :");
274            }
275            if(topLevelClass != null) msg.append(eol).append("objectTopLevelClass=").append(topLevelClass.getName());
276            if(realClass != null) msg.append(eol).append("objectRealClass=").append(realClass.getName());
277            if(pks != null) msg.append(eol).append("pkValues=").append(ArrayUtils.toString(pks));
278            if(objectToIdentify != null) msg.append(eol).append("object to identify: ").append(objectToIdentify);
279            if(ex != null)
280            {
281                // add causing stack trace
282                Throwable rootCause = ExceptionUtils.getRootCause(ex);
283                if(rootCause != null)
284                {
285                    msg.append(eol).append("The root stack trace is --> ");
286                    String rootStack = ExceptionUtils.getStackTrace(rootCause);
287                    msg.append(eol).append(rootStack);
288                }
289    
290                return new PersistenceBrokerException(msg.toString(), ex);
291            }
292            else
293            {
294                return new PersistenceBrokerException(msg.toString());
295            }
296        }
297    
298        //===================================================================
299        // PBStateListener interface
300        //===================================================================
301        public void afterBegin(PBStateEvent event)
302        {
303        }
304    
305        public void afterCommit(PBStateEvent event)
306        {
307            if(objectToIdentityMap.size() > 0) objectToIdentityMap.clear();
308        }
309    
310        public void afterRollback(PBStateEvent event)
311        {
312            if(objectToIdentityMap.size() > 0) objectToIdentityMap.clear();
313        }
314    
315        public void beforeClose(PBStateEvent event)
316        {
317            if(objectToIdentityMap.size() > 0) objectToIdentityMap.clear();
318        }
319    
320        public void beforeRollback(PBStateEvent event)
321        {
322        }
323        public void afterOpen(PBStateEvent event)
324        {
325        }
326        public void beforeBegin(PBStateEvent event)
327        {
328        }
329        public void beforeCommit(PBStateEvent event)
330        {
331        }
332    }