001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.storage.implementation.database;
011:
012: import java.sql.*;
013: import java.util.*;
014:
015: import org.mmbase.bridge.Field;
016: import org.mmbase.bridge.NodeManager;
017: import org.mmbase.core.CoreField;
018: import org.mmbase.module.core.MMObjectBuilder;
019: import org.mmbase.module.core.MMObjectNode;
020: import org.mmbase.storage.StorageError;
021: import org.mmbase.storage.StorageException;
022: import org.mmbase.storage.util.Index;
023: import org.mmbase.storage.util.Scheme;
024: import org.mmbase.util.logging.Logger;
025: import org.mmbase.util.logging.Logging;
026:
027: /**
028: * @javadoc
029: *
030: * @version $Id: ViewDatabaseStorageManager.java,v 1.11 2007/03/02 21:03:05 nklasens Exp $
031: * @since MMBase-1.8
032: */
033: public class ViewDatabaseStorageManager extends DatabaseStorageManager {
034:
035: private static final Logger log = Logging
036: .getLoggerInstance(ViewDatabaseStorageManager.class);
037:
038: /**
039: * Determine if the basic storage elements exist
040: * Basic storage elements include the 'object' storage (where all objects and their types are registered).
041: * @return <code>true</code> if basic storage elements exist
042: * @throws StorageException if an error occurred while querying the storage
043: */
044: public boolean exists() throws StorageException {
045: return viewExists(factory.getMMBase().getRootBuilder());
046: }
047:
048: /**
049: * Determine if a storage element exists for storing the given builder's objects
050: * @param builder the builder to check
051: * @return <code>true</code> if the storage element exists, false if it doesn't
052: * @throws StorageException if an error occurred while querying the storage
053: */
054: public boolean exists(MMObjectBuilder builder)
055: throws StorageException {
056: return viewExists(builder);
057: }
058:
059: /**
060: * Create the basic elements for this storage
061: * @throws StorageException if an error occurred during the creation of the object storage
062: */
063: public void create() throws StorageException {
064: if (!viewExists(factory.getMMBase().getRootBuilder())) {
065: viewCreate(factory.getMMBase().getRootBuilder());
066: createSequence();
067: }
068: }
069:
070: /**
071: * Create a storage element to store the specified builder's objects.
072: * @param builder the builder to create the storage element for
073: * @throws StorageException if an error occurred during the creation of the storage element
074: */
075: public void create(MMObjectBuilder builder) throws StorageException {
076: if (!viewExists(builder)) {
077: viewCreate(builder);
078: }
079: }
080:
081: public void create(final MMObjectNode node,
082: final MMObjectBuilder builder) throws StorageException {
083: boolean localTransaction = !inTransaction;
084: if (localTransaction) {
085: beginTransaction();
086: }
087: try {
088: if (factory.hasOption("database-supports-insert-triggers")) {
089: // no need for any fancy looping over parents; we just insert everything in this view
090: super .create(node, builder);
091: } else {
092: // insert in parent tables (from parents to childs) (especially because foreign keys on object's number may exist)
093: Iterator<MMObjectBuilder> i = builder.getAncestors()
094: .iterator();
095: while (i.hasNext()) {
096: MMObjectBuilder b = i.next();
097: createObject(node, b);
098: }
099: createObject(node, builder);
100: }
101: if (localTransaction) {
102: commit();
103: }
104: } catch (StorageException se) {
105: if (localTransaction && inTransaction)
106: rollback();
107: throw se;
108: }
109: }
110:
111: /**
112: * This method inserts a new object in a specific builder, and registers the change.
113: * This method makes it easier to implement relational databases, where you may need to update the node
114: * in more than one builder.
115: * Call this method for all involved builders if you use a relational database.
116: * @param node The node to insert. The node already needs to have a (new) number assigned
117: * @param builder the builder to store the node
118: * @throws StorageException if an error occurred during creation
119: */
120: protected void createObject(MMObjectNode node,
121: MMObjectBuilder builder) throws StorageException {
122: List<CoreField> createFields = new ArrayList<CoreField>();
123: List<CoreField> builderFields = builder
124: .getFields(NodeManager.ORDER_CREATE);
125: for (CoreField field : builderFields) {
126: if (field.inStorage()
127: && (!this .isInheritedField(field) || field
128: .getName().equals(
129: this .getNumberField().getName()))) {
130: createFields.add(field);
131: }
132: }
133: String tablename = getTableName(builder);
134: create(node, createFields, tablename);
135: }
136:
137: /**
138: * Changes a node in the passed builder and all its parent builders
139: * @param node The node to change
140: * @param builder the builder to change the node in
141: * @throws StorageException if an error occurred during change
142: */
143: public void change(MMObjectNode node, MMObjectBuilder builder)
144: throws StorageException {
145: boolean localTransaction = !inTransaction;
146: if (localTransaction) {
147: beginTransaction();
148: }
149: try {
150: if (factory.hasOption("database-supports-update-triggers")) {
151: super .change(node, builder);
152: } else {
153: do {
154: changeObject(node, builder);
155: builder = builder.getParentBuilder();
156: } while (builder != null);
157: }
158: if (localTransaction) {
159: commit();
160: }
161: } catch (StorageException se) {
162: if (localTransaction && inTransaction)
163: rollback();
164: throw se;
165: }
166: }
167:
168: private void changeObject(MMObjectNode node, MMObjectBuilder builder) {
169: List<CoreField> changeFields = new ArrayList<CoreField>();
170: // obtain the node's changed fields
171: Collection<String> fieldNames = node.getChanged();
172: for (String key : fieldNames) {
173: CoreField field = builder.getField(key);
174: if ((field != null) && field.inStorage()
175: && !isInheritedField(field)) {
176: changeFields.add(field);
177: }
178: }
179: change(node, builder, getTableName(builder), changeFields);
180: }
181:
182: /**
183: * Deletes a node in the passed builder and all its parent builders.
184: * @param node The node to delete
185: * @param builder the builder to delete the node in
186: * @throws StorageException if an error occurred during delete
187: */
188: public void delete(MMObjectNode node, MMObjectBuilder builder)
189: throws StorageException {
190: boolean localTransaction = !inTransaction;
191: if (localTransaction) {
192: beginTransaction();
193: }
194:
195: try {
196: if (factory.hasOption("database-supports-delete-triggers")) {
197: super .delete(node, builder);
198: } else {
199: do {
200: deleteObject(node, builder);
201: builder = builder.getParentBuilder();
202: } while (builder != null);
203: }
204: if (localTransaction) {
205: commit();
206: }
207: } catch (StorageException se) {
208: if (localTransaction && inTransaction)
209: rollback();
210: throw se;
211: }
212: }
213:
214: private void deleteObject(MMObjectNode node, MMObjectBuilder builder) {
215: List<CoreField> blobFileField = new ArrayList<CoreField>();
216: List<CoreField> builderFields = builder
217: .getFields(NodeManager.ORDER_CREATE);
218: for (CoreField field : builderFields) {
219: if (field.inStorage() && !isInheritedField(field)) {
220: if (factory.hasOption(Attributes.STORES_BINARY_AS_FILE)
221: && (field.getType() == Field.TYPE_BINARY)) {
222: blobFileField.add(field);
223: }
224: }
225: }
226: String tablename = getTableName(builder);
227: super .delete(node, builder, blobFileField, tablename);
228: }
229:
230: public String getFieldName(CoreField field) {
231: return (String) factory.getStorageIdentifier(field);
232: }
233:
234: public boolean isInheritedField(CoreField field) {
235: MMObjectBuilder inheritedBuilder = field.getParent()
236: .getParentBuilder();
237: if (inheritedBuilder == null) {
238: // no parent, thus cannot inherit anything
239: return false;
240: }
241: return (inheritedBuilder.getField(field.getName()) != null);
242: }
243:
244: public CoreField getNumberField() {
245: return factory.getMMBase().getRootBuilder().getField("number");
246: }
247:
248: public String getTableName(MMObjectBuilder builder) {
249: if (builder.getParentBuilder() == null) {
250: return (String) factory.getStorageIdentifier(builder);
251: } else {
252: return getTableName((String) factory
253: .getStorageIdentifier(builder));
254: }
255: }
256:
257: public String getTableName(String viewname) {
258: String id = (String) factory.getStorageIdentifier(viewname
259: + "_table");
260: String toCase = (String) factory
261: .getAttribute(org.mmbase.storage.Attributes.STORAGE_IDENTIFIER_CASE);
262: if ("lower".equals(toCase)) {
263: return id.toLowerCase();
264: } else if ("upper".equals(toCase)) {
265: return id.toUpperCase();
266: } else {
267: return id;
268: }
269: }
270:
271: public String getViewName(MMObjectBuilder builder) {
272: return getViewName((String) factory
273: .getStorageIdentifier(builder));
274: }
275:
276: public String getViewName(String viewname) {
277: return viewname;
278: }
279:
280: /**
281: * Override the default version. An index should only be created if
282: * all the fields are in this builder. Otherwise this will fail horrably.
283: */
284: protected void create(Index index) throws StorageException {
285: for (int i = 0; i < index.size(); i++) {
286: CoreField f = (CoreField) index.get(i);
287: if (!isPartOfBuilderDefinition(f)) {
288: return;
289: }
290: }
291:
292: super .createIndex(index, getTableName(index.getParent()));
293: }
294:
295: protected boolean exists(Index index) throws StorageException {
296: return super .exists(index, getTableName(index.getParent()));
297: }
298:
299: public boolean viewExists(MMObjectBuilder builder) {
300: return exists(getViewName(builder));
301: }
302:
303: public boolean viewCreate(MMObjectBuilder builder) {
304: MMObjectBuilder inheritedBuilder = builder.getParentBuilder();
305: // create the inherited builder first
306: if (inheritedBuilder != null) {
307: if (!viewExists(inheritedBuilder)) {
308: // create the builder we inherit from
309: if (!viewCreate(inheritedBuilder)) {
310: // we could not start create everyting!
311: return false;
312: }
313: }
314: }
315: String tablename = getTableName(builder);
316: List<CoreField> fields = builder
317: .getFields(NodeManager.ORDER_CREATE);
318:
319: if (!super .exists(getTableName(builder))) {
320: List<CoreField> tableFields = new ArrayList<CoreField>();
321: for (CoreField field : fields) {
322: // is it a database field, and not of the parent(except the number field)?
323: if (isPartOfBuilderDefinition(field)
324: || field.getName().equals(
325: getNumberField().getName())) {
326: tableFields.add(field);
327: }
328: }
329: // Create the table
330: createTable(builder, tableFields, tablename);
331:
332: //TODO rewrite verify check with views
333: //verify(builder);
334: }
335:
336: if (builder.getParentBuilder() != null) {
337: createView(builder, inheritedBuilder, fields, tablename);
338: }
339: return true;
340: }
341:
342: private void createView(MMObjectBuilder builder,
343: MMObjectBuilder inheritedBuilder, List<CoreField> fields,
344: String tablename) throws StorageError {
345: log.debug("Creating a view for " + builder);
346: Scheme viewScheme = factory.getScheme(Schemes.CREATE_VIEW,
347: Schemes.CREATE_VIEW_DEFAULT);
348: Scheme createInsertTriggerScheme = null;
349: Scheme createDeleteTriggerScheme = null;
350: Scheme createUpdateTriggerScheme = null;
351: if (factory.hasOption("database-supports-insert-triggers")) {
352: createInsertTriggerScheme = factory.getScheme(
353: Schemes.CREATE_INSERT_TRIGGER,
354: Schemes.CREATE_INSERT_TRIGGER_DEFAULT);
355: if (createInsertTriggerScheme == null) {
356: log
357: .warn("Database supports insert-triggers, but no trigger scheme defined! Ignoring insert-trigger!!");
358: }
359: }
360: if (factory.hasOption("database-supports-delete-triggers")) {
361: createDeleteTriggerScheme = factory.getScheme(
362: Schemes.CREATE_DELETE_TRIGGER,
363: Schemes.CREATE_DELETE_TRIGGER_DEFAULT);
364: if (createDeleteTriggerScheme == null) {
365: log
366: .warn("Database supports delete-triggers, but no trigger scheme defined! Ignoring delete-trigger!!");
367: }
368: }
369: if (factory.hasOption("database-supports-update-triggers")) {
370: createUpdateTriggerScheme = factory.getScheme(
371: Schemes.CREATE_UPDATE_TRIGGER,
372: Schemes.CREATE_UPDATE_TRIGGER_DEFAULT);
373: if (createUpdateTriggerScheme == null) {
374: log
375: .warn("Database supports update-triggers, but no trigger scheme defined! Ignoring update-trigger!!");
376: }
377: }
378:
379: String viewname = getViewName(builder);
380:
381: StringBuilder createViewFields = new StringBuilder();
382: for (CoreField field : fields) {
383: if (field.inStorage()
384: && (field.getType() != Field.TYPE_BINARY || !factory
385: .hasOption(Attributes.STORES_BINARY_AS_FILE))) {
386: if (createViewFields.length() > 0) {
387: createViewFields.append(", ");
388: }
389: createViewFields.append(getFieldName(field));
390: }
391: }
392:
393: StringBuilder createTableFields = new StringBuilder();
394: Vector<String> myFieldNames = new Vector<String>();
395: Vector<String> parentFieldNames = new Vector<String>();
396:
397: for (CoreField field : fields) {
398: if (field.inStorage()
399: && (field.getType() != Field.TYPE_BINARY || !factory
400: .hasOption(Attributes.STORES_BINARY_AS_FILE))) {
401:
402: if (createTableFields.length() > 0) {
403: createTableFields.append(", ");
404: }
405: if (isInheritedField(field)) {
406: if (inheritedBuilder == null)
407: throw new StorageError(
408: "Cannot have a inherited field while we dont extend inherit from a builder!");
409: createTableFields
410: .append(getViewName(inheritedBuilder) + "."
411: + getFieldName(field));
412: parentFieldNames.add(getFieldName(field));
413: } else {
414: createTableFields.append(tablename + "."
415: + getFieldName(field));
416: myFieldNames.add(getFieldName(field));
417: }
418:
419: if (isInheritedField(field)
420: && field.getName().equals(
421: getNumberField().getName())) {
422: myFieldNames.add(getFieldName(field));
423: }
424: }
425: }
426:
427: String query = "";
428: try {
429: getActiveConnection();
430: // create the view
431: query = viewScheme.format(new Object[] { this , viewname,
432: tablename, createViewFields.toString(),
433: createTableFields.toString(),
434: getFieldName(getNumberField()), inheritedBuilder,
435: factory.getDatabaseName() });
436: // remove parenthesis with empty field definitions -
437: // unfortunately Schemes don't take this into account
438: if (factory.hasOption(Attributes.REMOVE_EMPTY_DEFINITIONS)) {
439: query = query.replaceAll("\\(\\s*\\)", "");
440: }
441:
442: long startTime = getLogStartTime();
443: PreparedStatement s = null;
444: try {
445: s = activeConnection.prepareStatement(query);
446: s.executeUpdate();
447: } finally {
448: if (s != null) {
449: s.close();
450: }
451: }
452: logQuery(query, startTime);
453:
454: if (createInsertTriggerScheme != null) {
455: //insert into mm_typedef_t (m_number, otype, owner) values (:NEW.m_number, :NEW.otype, :NEW.owner);
456: //insert into mm_object (m_number, name, description) values (:NEW.m_number, :NEW.name, :NEW.description);
457: StringBuilder myFields = new StringBuilder();
458: StringBuilder myValues = new StringBuilder();
459: StringBuilder parentFields = new StringBuilder();
460: StringBuilder parentValues = new StringBuilder();
461: for (int i = 0; i < myFieldNames.size(); i++) {
462: if (i > 0) {
463: myFields.append(", ");
464: myValues.append(", ");
465: }
466: myFields.append(myFieldNames.get(i));
467: myValues.append(":NEW." + myFieldNames.get(i));
468: }
469: for (int i = 0; i < parentFieldNames.size(); i++) {
470: if (i > 0) {
471: parentFields.append(", ");
472: parentValues.append(", ");
473: }
474: parentFields.append(parentFieldNames.get(i));
475: parentValues.append(":NEW."
476: + parentFieldNames.get(i));
477: }
478: Object triggerName = factory
479: .getStorageIdentifier(viewname + "_INSERT");
480: query = createInsertTriggerScheme.format(new Object[] {
481: this , viewname, tablename, inheritedBuilder,
482: myFields.toString(), myValues.toString(),
483: parentFields.toString(),
484: parentValues.toString(), triggerName });
485:
486: if (factory
487: .hasOption(Attributes.REMOVE_EMPTY_DEFINITIONS)) {
488: query = query.replaceAll("\\(\\s*\\)", "");
489: }
490:
491: long startTime2 = getLogStartTime();
492: PreparedStatement s2 = null;
493: try {
494: s2 = activeConnection.prepareStatement(query);
495: s2.executeUpdate();
496: } finally {
497: if (s2 != null) {
498: s2.close();
499: }
500: }
501:
502: logQuery(query, startTime2);
503: }
504:
505: if (createDeleteTriggerScheme != null) {
506: Object triggerName = factory
507: .getStorageIdentifier(viewname + "_DELETE");
508: query = createDeleteTriggerScheme.format(new Object[] {
509: this , viewname, tablename, inheritedBuilder,
510: getFieldName(getNumberField()), triggerName });
511: if (factory
512: .hasOption(Attributes.REMOVE_EMPTY_DEFINITIONS)) {
513: query = query.replaceAll("\\(\\s*\\)", "");
514: }
515:
516: long startTime2 = getLogStartTime();
517: PreparedStatement s3 = null;
518: try {
519: s3 = activeConnection.prepareStatement(query);
520: s3.executeUpdate();
521: } finally {
522: if (s3 != null) {
523: s3.close();
524: }
525: }
526: logQuery(query, startTime2);
527: }
528:
529: if (createUpdateTriggerScheme != null) {
530: StringBuilder myAssignments = new StringBuilder();
531: StringBuilder parentAssignments = new StringBuilder();
532: for (int i = 0; i < myFieldNames.size(); i++) {
533: if (i > 0) {
534: myAssignments.append(", ");
535: }
536: myAssignments.append(myFieldNames.get(i));
537: myAssignments.append(" = :NEW.");
538: myAssignments.append(myFieldNames.get(i));
539: }
540: for (int i = 0; i < parentFieldNames.size(); i++) {
541: if (i > 0) {
542: parentAssignments.append(", ");
543: }
544: parentAssignments.append(parentFieldNames.get(i));
545: parentAssignments.append(" = :NEW.");
546: parentAssignments.append(parentFieldNames.get(i));
547: }
548: Object triggerName = factory
549: .getStorageIdentifier(viewname + "_UPDATE");
550: query = createUpdateTriggerScheme.format(new Object[] {
551: this , viewname, tablename, inheritedBuilder,
552: myAssignments.toString(),
553: parentAssignments.toString(),
554: getFieldName(getNumberField()), triggerName });
555:
556: if (factory
557: .hasOption(Attributes.REMOVE_EMPTY_DEFINITIONS)) {
558: query = query.replaceAll("\\(\\s*\\)", "");
559: }
560:
561: long startTime2 = getLogStartTime();
562: PreparedStatement s4 = null;
563: try {
564: s4 = activeConnection.prepareStatement(query);
565: s4.executeUpdate();
566: } finally {
567: if (s4 != null) {
568: s4.close();
569: }
570: }
571: logQuery(query, startTime2);
572: }
573:
574: addToTableNameCache(viewname);
575: } catch (SQLException se) {
576: throw new StorageException(se.getMessage() + " in query:"
577: + query, se);
578: } finally {
579: releaseActiveConnection();
580: }
581: }
582:
583: }
|