1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.type;
26
27 import java.io.Serializable;
28 import java.lang.reflect.Method;
29 import java.sql.PreparedStatement;
30 import java.sql.ResultSet;
31 import java.sql.SQLException;
32 import java.util.HashMap;
33 import java.util.Map;
34
35 import org.dom4j.Element;
36 import org.dom4j.Node;
37 import org.hibernate.EntityMode;
38 import org.hibernate.FetchMode;
39 import org.hibernate.HibernateException;
40 import org.hibernate.MappingException;
41 import org.hibernate.engine.CascadeStyle;
42 import org.hibernate.engine.Mapping;
43 import org.hibernate.engine.SessionFactoryImplementor;
44 import org.hibernate.engine.SessionImplementor;
45 import org.hibernate.tuple.component.ComponentTuplizer;
46 import org.hibernate.tuple.component.ComponentMetamodel;
47 import org.hibernate.tuple.StandardProperty;
48 import org.hibernate.tuple.EntityModeToTuplizerMapping;
49 import org.hibernate.util.ArrayHelper;
50 import org.hibernate.util.StringHelper;
51
52 /**
53 * Handles "component" mappings
54 *
55 * @author Gavin King
56 */
57 public class ComponentType extends AbstractType implements AbstractComponentType {
58
59 private final String[] propertyNames;
60 private final Type[] propertyTypes;
61 private final boolean[] propertyNullability;
62 protected final int propertySpan;
63 private final CascadeStyle[] cascade;
64 private final FetchMode[] joinedFetch;
65 private final boolean isKey;
66
67 protected final EntityModeToTuplizerMapping tuplizerMapping;
68
69 public ComponentType(ComponentMetamodel metamodel) {
70 // for now, just "re-flatten" the metamodel since this is temporary stuff anyway (HHH-1907)
71 this.isKey = metamodel.isKey();
72 this.propertySpan = metamodel.getPropertySpan();
73 this.propertyNames = new String[ propertySpan ];
74 this.propertyTypes = new Type[ propertySpan ];
75 this.propertyNullability = new boolean[ propertySpan ];
76 this.cascade = new CascadeStyle[ propertySpan ];
77 this.joinedFetch = new FetchMode[ propertySpan ];
78
79 for ( int i = 0; i < propertySpan; i++ ) {
80 StandardProperty prop = metamodel.getProperty( i );
81 this.propertyNames[i] = prop.getName();
82 this.propertyTypes[i] = prop.getType();
83 this.propertyNullability[i] = prop.isNullable();
84 this.cascade[i] = prop.getCascadeStyle();
85 this.joinedFetch[i] = prop.getFetchMode();
86 }
87
88 this.tuplizerMapping = metamodel.getTuplizerMapping();
89 }
90
91 public int[] sqlTypes(Mapping mapping) throws MappingException {
92 //Not called at runtime so doesn't matter if its slow :)
93 int[] sqlTypes = new int[getColumnSpan( mapping )];
94 int n = 0;
95 for ( int i = 0; i < propertySpan; i++ ) {
96 int[] subtypes = propertyTypes[i].sqlTypes( mapping );
97 for ( int j = 0; j < subtypes.length; j++ ) {
98 sqlTypes[n++] = subtypes[j];
99 }
100 }
101 return sqlTypes;
102 }
103
104 public int getColumnSpan(Mapping mapping) throws MappingException {
105 int span = 0;
106 for ( int i = 0; i < propertySpan; i++ ) {
107 span += propertyTypes[i].getColumnSpan( mapping );
108 }
109 return span;
110 }
111
112 public final boolean isComponentType() {
113 return true;
114 }
115
116 public Class getReturnedClass() {
117 return tuplizerMapping.getTuplizer( EntityMode.POJO ).getMappedClass(); //TODO
118 }
119
120 public boolean isSame(Object x, Object y, EntityMode entityMode) throws HibernateException {
121 if ( x == y ) {
122 return true;
123 }
124 if ( x == null || y == null ) {
125 return false;
126 }
127 Object[] xvalues = getPropertyValues( x, entityMode );
128 Object[] yvalues = getPropertyValues( y, entityMode );
129 for ( int i = 0; i < propertySpan; i++ ) {
130 if ( !propertyTypes[i].isSame( xvalues[i], yvalues[i], entityMode ) ) {
131 return false;
132 }
133 }
134 return true;
135 }
136
137 public boolean isEqual(Object x, Object y, EntityMode entityMode)
138 throws HibernateException {
139 if ( x == y ) {
140 return true;
141 }
142 if ( x == null || y == null ) {
143 return false;
144 }
145 Object[] xvalues = getPropertyValues( x, entityMode );
146 Object[] yvalues = getPropertyValues( y, entityMode );
147 for ( int i = 0; i < propertySpan; i++ ) {
148 if ( !propertyTypes[i].isEqual( xvalues[i], yvalues[i], entityMode ) ) {
149 return false;
150 }
151 }
152 return true;
153 }
154
155 public boolean isEqual(Object x, Object y, EntityMode entityMode, SessionFactoryImplementor factory)
156 throws HibernateException {
157 if ( x == y ) {
158 return true;
159 }
160 if ( x == null || y == null ) {
161 return false;
162 }
163 Object[] xvalues = getPropertyValues( x, entityMode );
164 Object[] yvalues = getPropertyValues( y, entityMode );
165 for ( int i = 0; i < propertySpan; i++ ) {
166 if ( !propertyTypes[i].isEqual( xvalues[i], yvalues[i], entityMode, factory ) ) {
167 return false;
168 }
169 }
170 return true;
171 }
172
173 public int compare(Object x, Object y, EntityMode entityMode) {
174 if ( x == y ) {
175 return 0;
176 }
177 Object[] xvalues = getPropertyValues( x, entityMode );
178 Object[] yvalues = getPropertyValues( y, entityMode );
179 for ( int i = 0; i < propertySpan; i++ ) {
180 int propertyCompare = propertyTypes[i].compare( xvalues[i], yvalues[i], entityMode );
181 if ( propertyCompare != 0 ) {
182 return propertyCompare;
183 }
184 }
185 return 0;
186 }
187
188 public boolean isMethodOf(Method method) {
189 return false;
190 }
191
192 public int getHashCode(Object x, EntityMode entityMode) {
193 int result = 17;
194 Object[] values = getPropertyValues( x, entityMode );
195 for ( int i = 0; i < propertySpan; i++ ) {
196 Object y = values[i];
197 result *= 37;
198 if ( y != null ) {
199 result += propertyTypes[i].getHashCode( y, entityMode );
200 }
201 }
202 return result;
203 }
204
205 public int getHashCode(Object x, EntityMode entityMode, SessionFactoryImplementor factory) {
206 int result = 17;
207 Object[] values = getPropertyValues( x, entityMode );
208 for ( int i = 0; i < propertySpan; i++ ) {
209 Object y = values[i];
210 result *= 37;
211 if ( y != null ) {
212 result += propertyTypes[i].getHashCode( y, entityMode, factory );
213 }
214 }
215 return result;
216 }
217
218 public boolean isDirty(Object x, Object y, SessionImplementor session)
219 throws HibernateException {
220 if ( x == y ) {
221 return false;
222 }
223 if ( x == null || y == null ) {
224 return true;
225 }
226 EntityMode entityMode = session.getEntityMode();
227 Object[] xvalues = getPropertyValues( x, entityMode );
228 Object[] yvalues = getPropertyValues( y, entityMode );
229 for ( int i = 0; i < xvalues.length; i++ ) {
230 if ( propertyTypes[i].isDirty( xvalues[i], yvalues[i], session ) ) {
231 return true;
232 }
233 }
234 return false;
235 }
236
237 public boolean isDirty(Object x, Object y, boolean[] checkable, SessionImplementor session)
238 throws HibernateException {
239 if ( x == y ) {
240 return false;
241 }
242 if ( x == null || y == null ) {
243 return true;
244 }
245 EntityMode entityMode = session.getEntityMode();
246 Object[] xvalues = getPropertyValues( x, entityMode );
247 Object[] yvalues = getPropertyValues( y, entityMode );
248 int loc = 0;
249 for ( int i = 0; i < xvalues.length; i++ ) {
250 int len = propertyTypes[i].getColumnSpan( session.getFactory() );
251 if ( len <= 1 ) {
252 final boolean dirty = ( len == 0 || checkable[loc] ) &&
253 propertyTypes[i].isDirty( xvalues[i], yvalues[i], session );
254 if ( dirty ) {
255 return true;
256 }
257 }
258 else {
259 boolean[] subcheckable = new boolean[len];
260 System.arraycopy( checkable, loc, subcheckable, 0, len );
261 final boolean dirty = propertyTypes[i].isDirty( xvalues[i], yvalues[i], subcheckable, session );
262 if ( dirty ) {
263 return true;
264 }
265 }
266 loc += len;
267 }
268 return false;
269 }
270
271 public boolean isModified(Object old, Object current, boolean[] checkable, SessionImplementor session)
272 throws HibernateException {
273
274 if ( current == null ) {
275 return old != null;
276 }
277 if ( old == null ) {
278 return current != null;
279 }
280 Object[] currentValues = getPropertyValues( current, session );
281 Object[] oldValues = ( Object[] ) old;
282 int loc = 0;
283 for ( int i = 0; i < currentValues.length; i++ ) {
284 int len = propertyTypes[i].getColumnSpan( session.getFactory() );
285 boolean[] subcheckable = new boolean[len];
286 System.arraycopy( checkable, loc, subcheckable, 0, len );
287 if ( propertyTypes[i].isModified( oldValues[i], currentValues[i], subcheckable, session ) ) {
288 return true;
289 }
290 loc += len;
291 }
292 return false;
293
294 }
295
296 public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
297 throws HibernateException, SQLException {
298 return resolve( hydrate( rs, names, session, owner ), session, owner );
299 }
300
301 public void nullSafeSet(PreparedStatement st, Object value, int begin, SessionImplementor session)
302 throws HibernateException, SQLException {
303
304 Object[] subvalues = nullSafeGetValues( value, session.getEntityMode() );
305
306 for ( int i = 0; i < propertySpan; i++ ) {
307 propertyTypes[i].nullSafeSet( st, subvalues[i], begin, session );
308 begin += propertyTypes[i].getColumnSpan( session.getFactory() );
309 }
310 }
311
312 public void nullSafeSet(
313 PreparedStatement st,
314 Object value,
315 int begin,
316 boolean[] settable,
317 SessionImplementor session)
318 throws HibernateException, SQLException {
319
320 Object[] subvalues = nullSafeGetValues( value, session.getEntityMode() );
321
322 int loc = 0;
323 for ( int i = 0; i < propertySpan; i++ ) {
324 int len = propertyTypes[i].getColumnSpan( session.getFactory() );
325 if ( len == 0 ) {
326 //noop
327 }
328 else if ( len == 1 ) {
329 if ( settable[loc] ) {
330 propertyTypes[i].nullSafeSet( st, subvalues[i], begin, session );
331 begin++;
332 }
333 }
334 else {
335 boolean[] subsettable = new boolean[len];
336 System.arraycopy( settable, loc, subsettable, 0, len );
337 propertyTypes[i].nullSafeSet( st, subvalues[i], begin, subsettable, session );
338 begin += ArrayHelper.countTrue( subsettable );
339 }
340 loc += len;
341 }
342 }
343
344 private Object[] nullSafeGetValues(Object value, EntityMode entityMode) throws HibernateException {
345 if ( value == null ) {
346 return new Object[propertySpan];
347 }
348 else {
349 return getPropertyValues( value, entityMode );
350 }
351 }
352
353 public Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner)
354 throws HibernateException, SQLException {
355
356 return nullSafeGet( rs, new String[] {name}, session, owner );
357 }
358
359 public Object getPropertyValue(Object component, int i, SessionImplementor session)
360 throws HibernateException {
361 return getPropertyValue( component, i, session.getEntityMode() );
362 }
363
364 public Object getPropertyValue(Object component, int i, EntityMode entityMode)
365 throws HibernateException {
366 return tuplizerMapping.getTuplizer( entityMode ).getPropertyValue( component, i );
367 }
368
369 public Object[] getPropertyValues(Object component, SessionImplementor session)
370 throws HibernateException {
371 return getPropertyValues( component, session.getEntityMode() );
372 }
373
374 public Object[] getPropertyValues(Object component, EntityMode entityMode)
375 throws HibernateException {
376 return tuplizerMapping.getTuplizer( entityMode ).getPropertyValues( component );
377 }
378
379 public void setPropertyValues(Object component, Object[] values, EntityMode entityMode)
380 throws HibernateException {
381 tuplizerMapping.getTuplizer( entityMode ).setPropertyValues( component, values );
382 }
383
384 public Type[] getSubtypes() {
385 return propertyTypes;
386 }
387
388 public String getName() {
389 return "component" + ArrayHelper.toString( propertyNames );
390 }
391
392 public String toLoggableString(Object value, SessionFactoryImplementor factory)
393 throws HibernateException {
394 if ( value == null ) {
395 return "null";
396 }
397 Map result = new HashMap();
398 EntityMode entityMode = tuplizerMapping.guessEntityMode( value );
399 if ( entityMode == null ) {
400 throw new ClassCastException( value.getClass().getName() );
401 }
402 Object[] values = getPropertyValues( value, entityMode );
403 for ( int i = 0; i < propertyTypes.length; i++ ) {
404 result.put( propertyNames[i], propertyTypes[i].toLoggableString( values[i], factory ) );
405 }
406 return StringHelper.unqualify( getName() ) + result.toString();
407 }
408
409 public String[] getPropertyNames() {
410 return propertyNames;
411 }
412
413 public Object deepCopy(Object component, EntityMode entityMode, SessionFactoryImplementor factory)
414 throws HibernateException {
415 if ( component == null ) {
416 return null;
417 }
418
419 Object[] values = getPropertyValues( component, entityMode );
420 for ( int i = 0; i < propertySpan; i++ ) {
421 values[i] = propertyTypes[i].deepCopy( values[i], entityMode, factory );
422 }
423
424 Object result = instantiate( entityMode );
425 setPropertyValues( result, values, entityMode );
426
427 //not absolutely necessary, but helps for some
428 //equals()/hashCode() implementations
429 ComponentTuplizer ct = ( ComponentTuplizer ) tuplizerMapping.getTuplizer( entityMode );
430 if ( ct.hasParentProperty() ) {
431 ct.setParent( result, ct.getParent( component ), factory );
432 }
433
434 return result;
435 }
436
437 public Object replace(
438 Object original,
439 Object target,
440 SessionImplementor session,
441 Object owner,
442 Map copyCache)
443 throws HibernateException {
444
445 if ( original == null ) {
446 return null;
447 }
448 //if ( original == target ) return target;
449
450 final Object result = target == null
451 ? instantiate( owner, session )
452 : target;
453
454 final EntityMode entityMode = session.getEntityMode();
455 Object[] values = TypeFactory.replace(
456 getPropertyValues( original, entityMode ),
457 getPropertyValues( result, entityMode ),
458 propertyTypes,
459 session,
460 owner,
461 copyCache
462 );
463
464 setPropertyValues( result, values, entityMode );
465 return result;
466 }
467
468 public Object replace(
469 Object original,
470 Object target,
471 SessionImplementor session,
472 Object owner,
473 Map copyCache,
474 ForeignKeyDirection foreignKeyDirection)
475 throws HibernateException {
476
477 if ( original == null ) {
478 return null;
479 }
480 //if ( original == target ) return target;
481
482 final Object result = target == null ?
483 instantiate( owner, session ) :
484 target;
485
486 final EntityMode entityMode = session.getEntityMode();
487 Object[] values = TypeFactory.replace(
488 getPropertyValues( original, entityMode ),
489 getPropertyValues( result, entityMode ),
490 propertyTypes,
491 session,
492 owner,
493 copyCache,
494 foreignKeyDirection
495 );
496
497 setPropertyValues( result, values, entityMode );
498 return result;
499 }
500
501 /**
502 * This method does not populate the component parent
503 */
504 public Object instantiate(EntityMode entityMode) throws HibernateException {
505 return tuplizerMapping.getTuplizer( entityMode ).instantiate();
506 }
507
508 public Object instantiate(Object parent, SessionImplementor session)
509 throws HibernateException {
510
511 Object result = instantiate( session.getEntityMode() );
512
513 ComponentTuplizer ct = ( ComponentTuplizer ) tuplizerMapping.getTuplizer( session.getEntityMode() );
514 if ( ct.hasParentProperty() && parent != null ) {
515 ct.setParent(
516 result,
517 session.getPersistenceContext().proxyFor( parent ),
518 session.getFactory()
519 );
520 }
521
522 return result;
523 }
524
525 public CascadeStyle getCascadeStyle(int i) {
526 return cascade[i];
527 }
528
529 public boolean isMutable() {
530 return true;
531 }
532
533 public Serializable disassemble(Object value, SessionImplementor session, Object owner)
534 throws HibernateException {
535
536 if ( value == null ) {
537 return null;
538 }
539 else {
540 Object[] values = getPropertyValues( value, session.getEntityMode() );
541 for ( int i = 0; i < propertyTypes.length; i++ ) {
542 values[i] = propertyTypes[i].disassemble( values[i], session, owner );
543 }
544 return values;
545 }
546 }
547
548 public Object assemble(Serializable object, SessionImplementor session, Object owner)
549 throws HibernateException {
550
551 if ( object == null ) {
552 return null;
553 }
554 else {
555 Object[] values = ( Object[] ) object;
556 Object[] assembled = new Object[values.length];
557 for ( int i = 0; i < propertyTypes.length; i++ ) {
558 assembled[i] = propertyTypes[i].assemble( ( Serializable ) values[i], session, owner );
559 }
560 Object result = instantiate( owner, session );
561 setPropertyValues( result, assembled, session.getEntityMode() );
562 return result;
563 }
564 }
565
566 public FetchMode getFetchMode(int i) {
567 return joinedFetch[i];
568 }
569
570 public Object hydrate(
571 final ResultSet rs,
572 final String[] names,
573 final SessionImplementor session,
574 final Object owner)
575 throws HibernateException, SQLException {
576
577 int begin = 0;
578 boolean notNull = false;
579 Object[] values = new Object[propertySpan];
580 for ( int i = 0; i < propertySpan; i++ ) {
581 int length = propertyTypes[i].getColumnSpan( session.getFactory() );
582 String[] range = ArrayHelper.slice( names, begin, length ); //cache this
583 Object val = propertyTypes[i].hydrate( rs, range, session, owner );
584 if ( val == null ) {
585 if ( isKey ) {
586 return null; //different nullability rules for pk/fk
587 }
588 }
589 else {
590 notNull = true;
591 }
592 values[i] = val;
593 begin += length;
594 }
595
596 return notNull ? values : null;
597 }
598
599 public Object resolve(Object value, SessionImplementor session, Object owner)
600 throws HibernateException {
601
602 if ( value != null ) {
603 Object result = instantiate( owner, session );
604 Object[] values = ( Object[] ) value;
605 Object[] resolvedValues = new Object[values.length]; //only really need new array during semiresolve!
606 for ( int i = 0; i < values.length; i++ ) {
607 resolvedValues[i] = propertyTypes[i].resolve( values[i], session, owner );
608 }
609 setPropertyValues( result, resolvedValues, session.getEntityMode() );
610 return result;
611 }
612 else {
613 return null;
614 }
615 }
616
617 public Object semiResolve(Object value, SessionImplementor session, Object owner)
618 throws HibernateException {
619 //note that this implementation is kinda broken
620 //for components with many-to-one associations
621 return resolve( value, session, owner );
622 }
623
624 public boolean[] getPropertyNullability() {
625 return propertyNullability;
626 }
627
628 public boolean isXMLElement() {
629 return true;
630 }
631
632 public Object fromXMLNode(Node xml, Mapping factory) throws HibernateException {
633 return xml;
634 }
635
636 public void setToXMLNode(Node node, Object value, SessionFactoryImplementor factory) throws HibernateException {
637 replaceNode( node, ( Element ) value );
638 }
639
640 public boolean[] toColumnNullness(Object value, Mapping mapping) {
641 boolean[] result = new boolean[ getColumnSpan( mapping ) ];
642 if ( value == null ) {
643 return result;
644 }
645 Object[] values = getPropertyValues( value, EntityMode.POJO ); //TODO!!!!!!!
646 int loc = 0;
647 for ( int i = 0; i < propertyTypes.length; i++ ) {
648 boolean[] propertyNullness = propertyTypes[i].toColumnNullness( values[i], mapping );
649 System.arraycopy( propertyNullness, 0, result, loc, propertyNullness.length );
650 loc += propertyNullness.length;
651 }
652 return result;
653 }
654
655 public boolean isEmbedded() {
656 return false;
657 }
658
659 }