1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.kernel;
20
21 import java.sql.Connection;
22 import java.sql.SQLException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Iterator;
26
27 import org.apache.openjpa.jdbc.schema.ForeignKey;
28 import org.apache.openjpa.jdbc.sql.PrimaryRow;
29 import org.apache.openjpa.jdbc.sql.Row;
30 import org.apache.openjpa.jdbc.sql.RowImpl;
31 import org.apache.openjpa.jdbc.sql.RowManager;
32 import org.apache.openjpa.jdbc.sql.RowManagerImpl;
33 import org.apache.openjpa.jdbc.sql.SQLExceptions;
34 import org.apache.openjpa.kernel.OpenJPAStateManager;
35
36 /**
37 * Update manager that writes SQL in object-level operation order.
38 *
39 * @author Abe White
40 */
41 public class OperationOrderUpdateManager
42 extends AbstractUpdateManager {
43
44 public boolean orderDirty() {
45 return true;
46 }
47
48 protected RowManager newRowManager() {
49 return new RowManagerImpl(true);
50 }
51
52 protected PreparedStatementManager newPreparedStatementManager
53 (JDBCStore store, Connection conn) {
54 return new PreparedStatementManagerImpl(store, conn);
55 }
56
57 protected Collection flush(RowManager rowMgr,
58 PreparedStatementManager psMgr, Collection exceps) {
59 RowManagerImpl rmimpl = (RowManagerImpl) rowMgr;
60
61 // first take care of all secondary table deletes and 'all row' deletes
62 // (which are probably secondary table deletes), since no foreign
63 // keys ever rely on secondary table pks
64 flush(rmimpl.getAllRowDeletes(), psMgr);
65 flush(rmimpl.getSecondaryDeletes(), psMgr);
66
67 // now do any 'all row' updates, which typically null keys
68 flush(rmimpl.getAllRowUpdates(), psMgr);
69
70 // gather any updates we need to avoid fk constraints on deletes
71 Collection constraintUpdates = null;
72 for (Iterator itr = rmimpl.getDeletes().iterator(); itr.hasNext();) {
73 try {
74 constraintUpdates = analyzeDeleteConstraints(rmimpl,
75 (PrimaryRow) itr.next(), constraintUpdates);
76 } catch (SQLException se) {
77 exceps = addException(exceps, SQLExceptions.getStore
78 (se, dict));
79 }
80 }
81 if (constraintUpdates != null) {
82 flush(constraintUpdates, psMgr);
83 constraintUpdates.clear();
84 }
85
86 // flush primary rows in order
87 for (Iterator itr = rmimpl.getOrdered().iterator(); itr.hasNext();) {
88 try {
89 constraintUpdates = flushPrimaryRow(rmimpl, (PrimaryRow)
90 itr.next(), psMgr, constraintUpdates);
91 } catch (SQLException se) {
92 exceps = addException(exceps, SQLExceptions.getStore
93 (se, dict));
94 }
95 }
96 if (constraintUpdates != null)
97 flush(constraintUpdates, psMgr);
98
99 // take care of all secondary table inserts and updates last, since
100 // they may rely on previous inserts or updates, but nothing relies
101 // on them
102 flush(rmimpl.getSecondaryUpdates(), psMgr);
103
104 // flush any left over prepared statements
105 psMgr.flush();
106 return exceps;
107 }
108
109 /**
110 * Analyze the delete constraints on the given row, gathering necessary
111 * updates to null fks before deleting.
112 */
113 private Collection analyzeDeleteConstraints(RowManagerImpl rowMgr,
114 PrimaryRow row, Collection updates)
115 throws SQLException {
116 if (!row.isValid())
117 return updates;
118
119 ForeignKey[] fks = row.getTable().getForeignKeys();
120 OpenJPAStateManager sm;
121 PrimaryRow rel;
122 RowImpl update;
123 for (int i = 0; i < fks.length; i++) {
124 // when deleting ref fks we set the where value instead
125 sm = row.getForeignKeySet(fks[i]);
126 if (sm == null)
127 sm = row.getForeignKeyWhere(fks[i]);
128 if (sm == null)
129 continue;
130
131 // only need an update if we have an fk to a row that's being
132 // deleted before we are
133 rel = (PrimaryRow) rowMgr.getRow(fks[i].getPrimaryKeyTable(),
134 Row.ACTION_DELETE, sm, false);
135 if (rel == null || !rel.isValid()
136 || rel.getIndex() >= row.getIndex())
137 continue;
138
139 // create an update to null the offending fk before deleting. use
140 // a primary row to be sure to copy delayed-flush pks/fks
141 update = new PrimaryRow(row.getTable(), Row.ACTION_UPDATE, null);
142 row.copyInto(update, true);
143 update.setForeignKey(fks[i], row.getForeignKeyIO(fks[i]), null);
144 if (updates == null)
145 updates = new ArrayList();
146 updates.add(update);
147 }
148 return updates;
149 }
150
151 /**
152 * Flush the given row, creating deferred updates for dependencies.
153 */
154 private Collection flushPrimaryRow(RowManagerImpl rowMgr, PrimaryRow row,
155 PreparedStatementManager psMgr, Collection updates)
156 throws SQLException {
157 if (!row.isValid())
158 return updates;
159
160 // already analyzed deletes
161 if (row.getAction() == Row.ACTION_DELETE) {
162 psMgr.flush(row);
163 return updates;
164 }
165
166 ForeignKey[] fks = row.getTable().getForeignKeys();
167 OpenJPAStateManager sm;
168 PrimaryRow rel;
169 PrimaryRow update;
170 for (int i = 0; i < fks.length; i++) {
171 sm = row.getForeignKeySet(fks[i]);
172 if (sm == null)
173 continue;
174
175 // only need an update if we have an fk to a row that's being
176 // inserted after we are; if row is dependent on itself and no
177 // fk, must be an auto-inc because otherwise we wouldn't have
178 // recorded it
179 rel = (PrimaryRow) rowMgr.getRow(fks[i].getPrimaryKeyTable(),
180 Row.ACTION_INSERT, sm, false);
181 if (rel == null || !rel.isValid()
182 || rel.getIndex() < row.getIndex()
183 || (rel == row && !fks[i].isDeferred() && !fks[i].isLogical()))
184 continue;
185
186 // don't insert or update with the given fk; create a deferred
187 // update for after the rel row has been inserted; use a primary row
188 // to prevent setting values until after flush to get auto-inc
189 update = new PrimaryRow(row.getTable(), Row.ACTION_UPDATE, null);
190 if (row.getAction() == Row.ACTION_INSERT)
191 update.wherePrimaryKey(row.getPrimaryKey());
192 else
193 row.copyInto(update, true);
194 update.setForeignKey(fks[i], row.getForeignKeyIO(fks[i]), sm);
195 row.clearForeignKey(fks[i]);
196
197 if (updates == null)
198 updates = new ArrayList();
199 updates.add(update);
200 }
201
202 if (row.isValid()) // if update, maybe no longer needed
203 psMgr.flush(row);
204 return updates;
205 }
206
207 /**
208 * Flush the given collection of secondary rows.
209 */
210 protected void flush(Collection rows, PreparedStatementManager psMgr) {
211 if (rows.isEmpty())
212 return;
213
214 RowImpl row;
215 for (Iterator itr = rows.iterator(); itr.hasNext();) {
216 row = (RowImpl) itr.next();
217 if (row.isValid())
218 psMgr.flush(row);
219 }
220 }
221 }