001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software 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 software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.cmp.jdbc.metadata;
023:
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.ArrayList;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030:
031: import org.jboss.deployment.DeploymentException;
032: import org.jboss.metadata.MetaData;
033: import org.jboss.metadata.RelationshipRoleMetaData;
034: import org.w3c.dom.Element;
035:
036: /**
037: * Imutable class which represents one ejb-relationship-role element found in
038: * the ejb-jar.xml file's ejb-relation elements.
039: *
040: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
041: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
042: * @version $Revision: 57209 $
043: */
044: public final class JDBCRelationshipRoleMetaData {
045: /** Relation to which this role belongs. */
046: private final JDBCRelationMetaData relationMetaData;
047:
048: /** Role name */
049: private final String relationshipRoleName;
050:
051: /** Is the multiplicity one? If not, multiplicity is many. */
052: private final boolean multiplicityOne;
053:
054: /** Should this role have a foreign key constraint? */
055: private final boolean foreignKeyConstraint;
056:
057: /** Should this entity be deleted when related entity is deleted. */
058: private final boolean cascadeDelete;
059:
060: /** Should the cascade-delete be batched. */
061: private final boolean batchCascadeDelete;
062:
063: /** The entity that has this role. */
064: private final JDBCEntityMetaData entity;
065:
066: /** Name of the entity's cmr field for this role. */
067: private final String cmrFieldName;
068:
069: /** true if this side is navigable */
070: private final boolean navigable;
071:
072: /** Type of the cmr field (i.e., collection or set) */
073: private final String cmrFieldType;
074:
075: private boolean genIndex;
076:
077: /** Type of the cmr field (i.e., collection or set) */
078: private final JDBCReadAheadMetaData readAhead;
079:
080: /** The other role in this relationship. */
081: private JDBCRelationshipRoleMetaData relatedRole;
082:
083: /** The key fields used by this role by field name. */
084: private Map keyFields;
085:
086: public JDBCRelationshipRoleMetaData(
087: JDBCRelationMetaData relationMetaData,
088: JDBCApplicationMetaData application,
089: RelationshipRoleMetaData role) throws DeploymentException {
090: this .relationMetaData = relationMetaData;
091:
092: relationshipRoleName = role.getRelationshipRoleName();
093: multiplicityOne = role.isMultiplicityOne();
094: cascadeDelete = role.isCascadeDelete();
095: batchCascadeDelete = false;
096: foreignKeyConstraint = false;
097: readAhead = null;
098:
099: String fieldName = loadCMRFieldName(role);
100: if (fieldName == null) {
101: cmrFieldName = generateNonNavigableCMRName(role);
102: navigable = false;
103: } else {
104: cmrFieldName = fieldName;
105: navigable = true;
106: }
107: cmrFieldType = role.getCMRFieldType();
108:
109: // get the entity for this role
110: entity = application.getBeanByEjbName(role.getEntityName());
111: if (entity == null) {
112: throw new DeploymentException("Entity: "
113: + role.getEntityName()
114: + " not found for relation: "
115: + role.getRelationMetaData().getRelationName());
116: }
117: }
118:
119: public JDBCRelationshipRoleMetaData(
120: JDBCRelationMetaData relationMetaData,
121: JDBCApplicationMetaData application, Element element,
122: JDBCRelationshipRoleMetaData defaultValues)
123: throws DeploymentException {
124:
125: this .relationMetaData = relationMetaData;
126: this .entity = application.getBeanByEjbName(defaultValues
127: .getEntity().getName());
128:
129: relationshipRoleName = defaultValues.getRelationshipRoleName();
130: multiplicityOne = defaultValues.isMultiplicityOne();
131: cascadeDelete = defaultValues.isCascadeDelete();
132:
133: cmrFieldName = defaultValues.getCMRFieldName();
134: navigable = defaultValues.isNavigable();
135: cmrFieldType = defaultValues.getCMRFieldType();
136:
137: // foreign key constraint? If not provided, keep default.
138: String fkString = MetaData.getOptionalChildContent(element,
139: "fk-constraint");
140: if (fkString != null) {
141: foreignKeyConstraint = Boolean.valueOf(fkString)
142: .booleanValue();
143: } else {
144: foreignKeyConstraint = defaultValues
145: .hasForeignKeyConstraint();
146: }
147:
148: // read-ahead
149: Element readAheadElement = MetaData.getOptionalChild(element,
150: "read-ahead");
151: if (readAheadElement != null) {
152: readAhead = new JDBCReadAheadMetaData(readAheadElement,
153: entity.getReadAhead());
154: } else {
155: readAhead = entity.getReadAhead();
156: }
157:
158: batchCascadeDelete = MetaData.getOptionalChild(element,
159: "batch-cascade-delete") != null;
160: if (batchCascadeDelete) {
161: if (!cascadeDelete)
162: throw new DeploymentException(
163: relationMetaData.getRelationName()
164: + '/'
165: + relationshipRoleName
166: + " has batch-cascade-delete in jbosscmp-jdbc.xml but has no cascade-delete in ejb-jar.xml");
167:
168: if (relationMetaData.isTableMappingStyle()) {
169: throw new DeploymentException(
170: "Relationship "
171: + relationMetaData.getRelationName()
172: + " with relation-table-mapping style was setup for batch cascade-delete."
173: + " Batch cascade-delete supported only for foreign key mapping style.");
174: }
175: }
176: }
177:
178: public void init(JDBCRelationshipRoleMetaData relatedRole)
179: throws DeploymentException {
180: init(relatedRole, null);
181: }
182:
183: public void init(JDBCRelationshipRoleMetaData relatedRole,
184: Element element) throws DeploymentException {
185: this .relatedRole = relatedRole;
186: if (element == null || "defaults".equals(element.getTagName())) {
187: keyFields = loadKeyFields();
188: } else {
189: keyFields = loadKeyFields(element);
190: }
191: }
192:
193: private static String loadCMRFieldName(RelationshipRoleMetaData role) {
194: return role.getCMRFieldName();
195: }
196:
197: private static String generateNonNavigableCMRName(
198: RelationshipRoleMetaData role) {
199: RelationshipRoleMetaData relatedRole = role
200: .getRelatedRoleMetaData();
201: return relatedRole.getEntityName() + "_"
202: + relatedRole.getCMRFieldName();
203: }
204:
205: /**
206: * Gets the relation to which this role belongs.
207: */
208: public JDBCRelationMetaData getRelationMetaData() {
209: return relationMetaData;
210: }
211:
212: /**
213: * Gets the name of this role.
214: */
215: public String getRelationshipRoleName() {
216: return relationshipRoleName;
217: }
218:
219: /**
220: * Should this role use a foreign key constraint.
221: * @return true if the store mananager will execute an ALTER TABLE ADD
222: * CONSTRAINT statement to add a foreign key constraint.
223: */
224: public boolean hasForeignKeyConstraint() {
225: return foreignKeyConstraint;
226: }
227:
228: /**
229: * Checks if the multiplicity is one.
230: */
231: public boolean isMultiplicityOne() {
232: return multiplicityOne;
233: }
234:
235: /**
236: * Checks if the multiplicity is many.
237: */
238: public boolean isMultiplicityMany() {
239: return !multiplicityOne;
240: }
241:
242: /**
243: * Should this entity be deleted when related entity is deleted.
244: */
245: public boolean isCascadeDelete() {
246: return cascadeDelete;
247: }
248:
249: public boolean isBatchCascadeDelete() {
250: return batchCascadeDelete;
251: }
252:
253: /**
254: * Gets the name of the entity that has this role.
255: */
256: public JDBCEntityMetaData getEntity() {
257: return entity;
258: }
259:
260: /**
261: * Gets the name of the entity's cmr field for this role.
262: */
263: public String getCMRFieldName() {
264: return cmrFieldName;
265: }
266:
267: private boolean isNavigable() {
268: return navigable;
269: }
270:
271: /**
272: * Gets the type of the cmr field (i.e., collection or set)
273: */
274: private String getCMRFieldType() {
275: return cmrFieldType;
276: }
277:
278: /**
279: * Gets the related role's jdbc meta data.
280: */
281: public JDBCRelationshipRoleMetaData getRelatedRole() {
282: return relationMetaData.getOtherRelationshipRole(this );
283: }
284:
285: /**
286: * Gets the read ahead meta data
287: */
288: public JDBCReadAheadMetaData getReadAhead() {
289: return readAhead;
290: }
291:
292: /**
293: * Gets the key fields of this role.
294: * @return an unmodifiable collection of JDBCCMPFieldMetaData objects
295: */
296: public Collection getKeyFields() {
297: return Collections.unmodifiableCollection(keyFields.values());
298: }
299:
300: public boolean isIndexed() {
301: return genIndex;
302: }
303:
304: /**
305: * Loads the key fields for this role based on the primary keys of the
306: * this entity.
307: */
308: private Map loadKeyFields() {
309: // with foreign key mapping, foreign key fields are no added if
310: // - it is the many side of one-to-many relationship
311: // - it is the one side of one-to-one relationship and related side is not navigable
312: if (relationMetaData.isForeignKeyMappingStyle()) {
313: if (isMultiplicityMany())
314: return Collections.EMPTY_MAP;
315: else if (getRelatedRole().isMultiplicityOne()
316: && !getRelatedRole().isNavigable())
317: return Collections.EMPTY_MAP;
318: }
319:
320: // get all of the pk fields
321: ArrayList pkFields = new ArrayList();
322: for (Iterator i = entity.getCMPFields().iterator(); i.hasNext();) {
323: JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) i
324: .next();
325: if (cmpField.isPrimaryKeyMember()) {
326: pkFields.add(cmpField);
327: }
328: }
329:
330: // generate a new key field for each pk field
331: Map fields = new HashMap(pkFields.size());
332: for (Iterator i = pkFields.iterator(); i.hasNext();) {
333: JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) i
334: .next();
335:
336: String columnName;
337: if (relationMetaData.isTableMappingStyle()) {
338: if (entity.equals(relatedRole.getEntity()))
339: columnName = getCMRFieldName();
340: else
341: columnName = entity.getName();
342: } else {
343: columnName = relatedRole.getCMRFieldName();
344: }
345:
346: if (pkFields.size() > 1) {
347: columnName += "_" + cmpField.getFieldName();
348: }
349:
350: cmpField = new JDBCCMPFieldMetaData(entity, cmpField,
351: columnName, false, relationMetaData
352: .isTableMappingStyle(), relationMetaData
353: .isReadOnly(), relationMetaData
354: .getReadTimeOut(), relationMetaData
355: .isTableMappingStyle());
356: fields.put(cmpField.getFieldName(), cmpField);
357: }
358: return Collections.unmodifiableMap(fields);
359: }
360:
361: /**
362: * Loads the key fields for this role based on the primary keys of the
363: * this entity and the override data from the xml element.
364: */
365: private Map loadKeyFields(Element element)
366: throws DeploymentException {
367: Element keysElement = MetaData.getOptionalChild(element,
368: "key-fields");
369:
370: // no field overrides, we're done
371: if (keysElement == null) {
372: return loadKeyFields();
373: }
374:
375: // load overrides
376: Iterator iter = MetaData.getChildrenByTagName(keysElement,
377: "key-field");
378:
379: // if key-fields element empty, no key should be used
380: if (!iter.hasNext()) {
381: return Collections.EMPTY_MAP;
382: } else if (relationMetaData.isForeignKeyMappingStyle()
383: && isMultiplicityMany()) {
384: throw new DeploymentException(
385: "Role: "
386: + relationshipRoleName
387: + " with multiplicity many using "
388: + "foreign-key mapping is not allowed to have key-fields");
389: }
390:
391: // load the default field values
392: Map defaultFields = getPrimaryKeyFields();
393:
394: // load overrides
395: Map fields = new HashMap(defaultFields.size());
396: while (iter.hasNext()) {
397: Element keyElement = (Element) iter.next();
398: String fieldName = MetaData.getUniqueChildContent(
399: keyElement, "field-name");
400:
401: JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) defaultFields
402: .remove(fieldName);
403: if (cmpField == null) {
404: throw new DeploymentException("Role '"
405: + relationshipRoleName + "' on Entity Bean '"
406: + entity.getName()
407: + "' : CMP field for key not found: field "
408: + "name='" + fieldName + "'");
409: }
410: String isIndexedtmp = MetaData.getOptionalChildContent(
411: keyElement, "dbindex");
412: boolean isIndexed;
413:
414: if (isIndexedtmp != null)
415: isIndexed = true;
416: else
417: isIndexed = false;
418: genIndex = isIndexed;
419:
420: cmpField = new JDBCCMPFieldMetaData(entity, keyElement,
421: cmpField, false, relationMetaData
422: .isTableMappingStyle(), relationMetaData
423: .isReadOnly(), relationMetaData
424: .getReadTimeOut(), relationMetaData
425: .isTableMappingStyle());
426: fields.put(cmpField.getFieldName(), cmpField);
427: }
428:
429: // all fields must be overriden
430: if (!defaultFields.isEmpty()) {
431: throw new DeploymentException(
432: "Mappings were not provided for all "
433: + "fields: unmaped fields="
434: + defaultFields.keySet() + " in role="
435: + relationshipRoleName);
436: }
437: return Collections.unmodifiableMap(fields);
438: }
439:
440: /**
441: * Returns the primary key fields of the entity mapped by field name.
442: */
443: private Map getPrimaryKeyFields() {
444: Map pkFields = new HashMap();
445: for (Iterator cmpFieldsIter = entity.getCMPFields().iterator(); cmpFieldsIter
446: .hasNext();) {
447: JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) cmpFieldsIter
448: .next();
449: if (cmpField.isPrimaryKeyMember())
450: pkFields.put(cmpField.getFieldName(), cmpField);
451: }
452: return pkFields;
453: }
454: }
|