001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free Software Foundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.amber.table;
030:
031: import com.caucho.amber.entity.AmberCompletion;
032: import com.caucho.amber.entity.Entity;
033: import com.caucho.amber.manager.AmberConnection;
034: import com.caucho.amber.type.EntityType;
035: import com.caucho.ejb.EJBExceptionWrapper;
036: import com.caucho.util.CharBuffer;
037: import com.caucho.util.L10N;
038: import com.caucho.util.Log;
039:
040: import java.sql.PreparedStatement;
041: import java.sql.ResultSet;
042: import java.sql.SQLException;
043: import java.util.ArrayList;
044: import java.util.logging.Logger;
045:
046: /**
047: * Represents a many-to-one link from one table to another.
048: */
049: public class LinkColumns {
050: private static final L10N L = new L10N(LinkColumns.class);
051: private static final Logger log = Log.open(LinkColumns.class);
052:
053: private static final int NO_CASCADE_DELETE = 0;
054: private static final int SOURCE_CASCADE_DELETE = 1;
055: private static final int TARGET_CASCADE_DELETE = 2;
056:
057: private Table _sourceTable;
058: private Table _targetTable;
059:
060: private ArrayList<ForeignColumn> _columns;
061:
062: private int _cascadeDelete;
063:
064: private AmberCompletion _tableDeleteCompletion;
065: private AmberCompletion _tableUpdateCompletion;
066:
067: /**
068: * Creates the table link.
069: */
070: public LinkColumns(Table sourceTable, Table targetTable,
071: ArrayList<ForeignColumn> columns) {
072: _sourceTable = sourceTable;
073: _targetTable = targetTable;
074:
075: _columns = columns;
076:
077: _tableDeleteCompletion = sourceTable.getDeleteCompletion();
078: _tableUpdateCompletion = sourceTable.getUpdateCompletion();
079:
080: _sourceTable.addOutgoingLink(this );
081: _targetTable.addIncomingLink(this );
082: }
083:
084: /**
085: * Sets the cascade-delete of the source when the target is deleted,
086: * i.e. a one-to-many cascaded delete like an identifying relation.
087: */
088: public void setSourceCascadeDelete(boolean isCascadeDelete) {
089: if (isCascadeDelete) {
090: assert (_cascadeDelete != TARGET_CASCADE_DELETE);
091:
092: _cascadeDelete = SOURCE_CASCADE_DELETE;
093: } else if (_cascadeDelete == SOURCE_CASCADE_DELETE)
094: _cascadeDelete = NO_CASCADE_DELETE;
095: }
096:
097: /**
098: * Sets the cascade-delete of the target when the source is deleted.
099: */
100: public void setTargetCascadeDelete(boolean isCascadeDelete) {
101: if (isCascadeDelete) {
102: assert (_cascadeDelete != SOURCE_CASCADE_DELETE);
103:
104: _cascadeDelete = TARGET_CASCADE_DELETE;
105: } else if (_cascadeDelete == TARGET_CASCADE_DELETE)
106: _cascadeDelete = NO_CASCADE_DELETE;
107: }
108:
109: /**
110: * Return true if the source is deleted when the target is deleted.
111: */
112: public boolean isSourceCascadeDelete() {
113: return _cascadeDelete == SOURCE_CASCADE_DELETE;
114: }
115:
116: /**
117: * Return true if the source is deleted when the target is deleted.
118: */
119: public boolean isTargetCascadeDelete() {
120: return _cascadeDelete == TARGET_CASCADE_DELETE;
121: }
122:
123: /**
124: * Returns the source table.
125: */
126: public Table getSourceTable() {
127: return _sourceTable;
128: }
129:
130: /**
131: * Returns the target table.
132: */
133: public Table getTargetTable() {
134: return _targetTable;
135: }
136:
137: /**
138: * Returns the component list.
139: */
140: public ArrayList<ForeignColumn> getColumns() {
141: return _columns;
142: }
143:
144: /**
145: * Returns the sql column for the source corresponding to the target key.
146: */
147:
148: /**
149: * Generates the linking for a link
150: */
151: public ForeignColumn getSourceColumn(Column targetKey) {
152: for (int i = _columns.size() - 1; i >= 0; i--) {
153: ForeignColumn column = _columns.get(i);
154:
155: if (column.getTargetColumn() == targetKey)
156: return column;
157: }
158:
159: return null;
160: }
161:
162: /**
163: * Generates SQL select.
164: */
165: public String generateSelectSQL(String table) {
166: CharBuffer cb = new CharBuffer();
167:
168: for (int i = 0; i < _columns.size(); i++) {
169: if (i != 0)
170: cb.append(", ");
171:
172: if (table != null) {
173: cb.append(table);
174: cb.append(".");
175: }
176:
177: cb.append(_columns.get(i).getName());
178: }
179:
180: return cb.toString();
181: }
182:
183: /**
184: * Generates SQL insert.
185: */
186: public void generateInsert(ArrayList<String> columns) {
187: for (int i = 0; i < _columns.size(); i++)
188: columns.add(_columns.get(i).getName());
189: }
190:
191: /**
192: * Generates SQL select.
193: */
194: public String generateUpdateSQL() {
195: CharBuffer cb = new CharBuffer();
196:
197: for (int i = 0; i < _columns.size(); i++) {
198: if (i != 0)
199: cb.append(", ");
200:
201: cb.append(_columns.get(i).getName() + "=?");
202: }
203:
204: return cb.toString();
205: }
206:
207: /**
208: * Generates SQL match.
209: */
210: public String generateMatchArgSQL(String table) {
211: CharBuffer cb = new CharBuffer();
212:
213: for (int i = 0; i < _columns.size(); i++) {
214: if (i != 0)
215: cb.append(" and ");
216:
217: if (table != null) {
218: cb.append(table);
219: cb.append(".");
220: }
221:
222: cb.append(_columns.get(i).getName());
223: cb.append("=?");
224: }
225:
226: return cb.toString();
227: }
228:
229: /**
230: * Generates the linking for a join
231: *
232: * @param sourceTable the SQL table name for the source
233: * @param targetTable the SQL table name for the target
234: */
235: public String generateJoin(String sourceTable, String targetTable) {
236: return generateJoin(sourceTable, targetTable, false);
237: }
238:
239: /**
240: * Generates the linking for a join
241: *
242: * @param sourceTable the SQL table name for the source
243: * @param targetTable the SQL table name for the target
244: * @param isArg true if targetTable is an argument "?"
245: */
246: public String generateJoin(String sourceTable, String targetTable,
247: boolean isArg) {
248: CharBuffer cb = new CharBuffer();
249:
250: cb.append('(');
251:
252: for (int i = 0; i < _columns.size(); i++) {
253: ForeignColumn column = _columns.get(i);
254:
255: if (i != 0)
256: cb.append(" and ");
257:
258: cb.append(sourceTable);
259: cb.append('.');
260: cb.append(column.getName());
261:
262: cb.append(" = ");
263:
264: cb.append(targetTable);
265:
266: if (isArg)
267: continue;
268:
269: cb.append('.');
270: cb.append(column.getTargetColumn().getName());
271: }
272:
273: cb.append(')');
274:
275: return cb.toString();
276: }
277:
278: /**
279: * Generates the many-to-many linking.
280: * This join is the one-to-many join and the other
281: * join is passed in as an argument used to link
282: * the two source tables that are pointing to the
283: * same target table.
284: *
285: * @param join the many-to-one join
286: * @param sourceTable1 the SQL table name for the 1st source
287: * @param sourceTable2 the SQL table name for the 2nd source
288: */
289: public String generateJoin(LinkColumns manyToOneJoin,
290: String sourceTable1, String sourceTable2) {
291: // Implemented for jpa/10cb
292:
293: if (manyToOneJoin._columns.size() != _columns.size())
294: return "";
295:
296: CharBuffer cb = new CharBuffer();
297:
298: cb.append('(');
299:
300: for (int i = 0; i < _columns.size(); i++) {
301: ForeignColumn column = _columns.get(i);
302: ForeignColumn otherColumn = manyToOneJoin._columns.get(i);
303:
304: if (i != 0)
305: cb.append(" and ");
306:
307: cb.append(sourceTable1);
308: cb.append('.');
309: cb.append(column.getName());
310:
311: cb.append(" = ");
312:
313: cb.append(sourceTable2);
314:
315: cb.append('.');
316: cb.append(otherColumn.getName());
317: }
318:
319: cb.append(')');
320:
321: return cb.toString();
322: }
323:
324: /**
325: * Generates the linking for a where clause
326: *
327: * @param sourceTable the SQL table name for the source
328: * @param targetTable the SQL table name for the target
329: */
330: public String generateWhere(String sourceTable, String targetTable) {
331: CharBuffer cb = new CharBuffer();
332:
333: cb.append('(');
334:
335: for (int i = 0; i < _columns.size(); i++) {
336: ForeignColumn column = _columns.get(i);
337:
338: if (i != 0)
339: cb.append(" and ");
340:
341: if (!column.isNotNull()) {
342:
343: if (sourceTable == null)
344: cb.append('?');
345: else {
346: cb.append(sourceTable);
347: cb.append('.');
348: cb.append(column.getName());
349: }
350:
351: cb.append(" is not null ");
352: }
353:
354: cb.append(" and ");
355:
356: // jpa/10c9
357: if (sourceTable == null) {
358: cb.append('?');
359: } else {
360: cb.append(sourceTable);
361: cb.append('.');
362: cb.append(column.getName());
363: }
364:
365: cb.append(" = ");
366:
367: cb.append(targetTable);
368: cb.append('.');
369: cb.append(column.getTargetColumn().getName());
370: }
371:
372: cb.append(')');
373:
374: return cb.toString();
375: }
376:
377: /**
378: * Cleans up any fields from a delete.
379: */
380: public void beforeTargetDelete(AmberConnection aConn, Entity entity)
381: throws SQLException {
382: PreparedStatement pstmt = null;
383: ResultSet rs = null;
384: String sql = null;
385:
386: try {
387: // commented out: jpa/0h25
388: // aConn.flushNoChecks();
389:
390: String sourceTable = _sourceTable.getName();
391:
392: ArrayList<LinkColumns> outgoingLinks = _sourceTable
393: .getOutgoingLinks();
394:
395: boolean isOwner = false;
396:
397: // jpa/0s2d: only deletes a relationship if the owner is deleted.
398: if (outgoingLinks != null && outgoingLinks.size() > 0) {
399: // XXX: assume link columns are introspected and ordered
400: // with owning side first.
401: // XXX: also, many-to-many bidirectional either side may be
402: // the owning side.
403: LinkColumns linkColumns = outgoingLinks.get(0);
404:
405: if (linkColumns._targetTable == entity
406: .__caucho_getEntityType().getTable())
407: isOwner = true;
408: }
409:
410: boolean isJPA = aConn.getPersistenceUnit().isJPA();
411:
412: // ejb/06c5 vs jpa/0h60
413: // jpa/0h60, the application should be responsible for deleting
414: // the incoming links even when there are FK constraints.
415: if (!(isJPA || isSourceCascadeDelete())) {
416: CharBuffer cb = new CharBuffer();
417:
418: cb.append("update " + sourceTable + " set ");
419:
420: ArrayList<ForeignColumn> columns = getColumns();
421:
422: for (int i = 0; i < columns.size(); i++) {
423: if (i != 0)
424: cb.append(", ");
425:
426: cb.append(columns.get(i).getName() + "=null");
427: }
428:
429: cb.append(" where ");
430:
431: for (int i = 0; i < columns.size(); i++) {
432: if (i != 0)
433: cb.append(" and ");
434:
435: cb.append(columns.get(i).getName() + "=?");
436: }
437:
438: // See catch (Exception) below.
439: sql = cb.toString();
440:
441: pstmt = aConn.prepareStatement(sql);
442:
443: entity.__caucho_setKey(pstmt, 1);
444:
445: pstmt.executeUpdate();
446:
447: aConn.addCompletion(_sourceTable.getUpdateCompletion());
448: } else if (_sourceTable.isCascadeDelete()) {
449: // if the link cascades deletes to the source and the source
450: // table also has cascade deletes, then we need to load the
451: // target entities and delete them recursively
452: //
453: // in theory, this could cause a loop, but we're ignoring that
454: // case for now
455:
456: EntityType entityType = (EntityType) _sourceTable
457: .getType();
458:
459: CharBuffer cb = new CharBuffer();
460:
461: cb.append("select ");
462: cb.append(entityType.getId().generateSelect("o"));
463: cb.append(" from " + sourceTable + " o");
464: cb.append(" where ");
465:
466: ArrayList<ForeignColumn> columns = getColumns();
467:
468: for (int i = 0; i < columns.size(); i++) {
469: if (i != 0)
470: cb.append(" and ");
471:
472: cb.append(columns.get(i).getName() + "=?");
473: }
474:
475: // See catch (Exception) below.
476: sql = cb.toString();
477:
478: pstmt = aConn.prepareStatement(sql);
479:
480: entity.__caucho_setKey(pstmt, 1);
481:
482: ArrayList<Object> proxyList = new ArrayList<Object>();
483:
484: rs = pstmt.executeQuery();
485: while (rs.next()) {
486: proxyList.add(entityType.getHome().loadLazy(aConn,
487: rs, 1));
488: }
489: rs.close();
490:
491: for (Object obj : proxyList) {
492: entityType.getHome().getEntityFactory().delete(
493: aConn, obj);
494: }
495: } // jpa/0i5e vs. jpa/0h25, jpa/0s2d
496: else if ((!isJPA)
497: || (isOwner && (_sourceTable.getType() == null))) {
498: CharBuffer cb = new CharBuffer();
499:
500: cb.append("delete from " + sourceTable + " where ");
501:
502: ArrayList<ForeignColumn> columns = getColumns();
503:
504: for (int i = 0; i < columns.size(); i++) {
505: if (i != 0)
506: cb.append(" and ");
507:
508: cb.append(columns.get(i).getName() + "=?");
509: }
510:
511: // See catch (Exception) below.
512: sql = cb.toString();
513:
514: pstmt = aConn.prepareStatement(sql);
515:
516: entity.__caucho_setKey(pstmt, 1);
517:
518: pstmt.executeUpdate();
519:
520: aConn.addCompletion(_sourceTable.getDeleteCompletion());
521: }
522:
523: aConn.expire();
524: } catch (Exception e) {
525: // Close statements only on exception.
526: // See com.caucho.amber.manager.AmberConnection for statement caching.
527: if (pstmt != null)
528: aConn.closeStatement(sql);
529:
530: if (e instanceof SQLException)
531: throw (SQLException) e;
532:
533: if (e instanceof RuntimeException)
534: throw (RuntimeException) e;
535:
536: throw new EJBExceptionWrapper(e);
537: } finally {
538: if (rs != null)
539: rs.close();
540: }
541: }
542:
543: /**
544: * Cleans up any fields from a delete.
545: */
546: public void afterSourceDelete(AmberConnection aConn, Entity entity)
547: throws SQLException {
548: // this should be handled programmatically
549: }
550:
551: public String toString() {
552: return "[" + _sourceTable + ", " + _targetTable + ", "
553: + _columns + "]";
554: }
555: }
|