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;
023:
024: import java.lang.reflect.Method;
025: import java.sql.Connection;
026: import java.sql.PreparedStatement;
027: import java.sql.SQLException;
028: import java.util.ArrayList;
029: import java.util.Date;
030: import java.util.List;
031: import javax.ejb.CreateException;
032: import javax.ejb.FinderException;
033: import javax.management.MalformedObjectNameException;
034: import javax.sql.DataSource;
035:
036: import org.jboss.deployment.DeploymentException;
037: import org.jboss.ejb.EntityEnterpriseContext;
038: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
039: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
040: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
041: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
042: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityCommandMetaData;
043: import org.jboss.logging.Logger;
044: import org.jboss.mx.util.MBeanProxyExt;
045: import org.jboss.security.AuthenticationManager;
046:
047: /**
048: * Base class for create commands that drives the operation sequence.
049: *
050: * @author <a href="mailto:jeremy@boynes.com">Jeremy Boynes</a>
051: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
052: */
053: public abstract class JDBCAbstractCreateCommand implements
054: JDBCCreateCommand {
055: protected Logger log;
056: protected boolean debug;
057: protected boolean trace;
058: protected JDBCEntityBridge entity;
059: protected AuthenticationManager securityManager;
060: protected boolean createAllowed;
061: protected SQLExceptionProcessorMBean exceptionProcessor;
062: protected String insertSQL;
063: protected JDBCFieldBridge[] insertFields;
064: protected boolean insertAfterEjbPostCreate;
065:
066: // Generated fields
067: private JDBCCMPFieldBridge createdPrincipal;
068: private JDBCCMPFieldBridge createdTime;
069: private JDBCCMPFieldBridge updatedPrincipal;
070: private JDBCCMPFieldBridge updatedTime;
071:
072: public void init(JDBCStoreManager manager)
073: throws DeploymentException {
074: log = Logger.getLogger(getClass().getName() + '.'
075: + manager.getMetaData().getName());
076: debug = log.isDebugEnabled();
077: trace = log.isTraceEnabled();
078:
079: entity = (JDBCEntityBridge) manager.getEntityBridge();
080: securityManager = manager.getContainer().getSecurityManager();
081:
082: insertAfterEjbPostCreate = manager.getContainer()
083: .getBeanMetaData().getContainerConfiguration()
084: .isInsertAfterEjbPostCreate();
085:
086: // set create allowed
087: createAllowed = true;
088: JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
089: for (int i = 0; i < pkFields.length; i++) {
090: if (pkFields[i].isReadOnly()) {
091: createAllowed = false;
092: log
093: .debug("Create will not be allowed because pk field "
094: + pkFields[i].getFieldName()
095: + "is read only.");
096: break;
097: }
098: }
099:
100: initGeneratedFields();
101:
102: JDBCEntityCommandMetaData entityCommand = manager.getMetaData()
103: .getEntityCommand();
104: if (entityCommand == null) {
105: throw new DeploymentException("entity-command is null");
106: }
107: initEntityCommand(entityCommand);
108:
109: initInsertFields();
110: initInsertSQL();
111: }
112:
113: protected void initEntityCommand(
114: JDBCEntityCommandMetaData entityCommand)
115: throws DeploymentException {
116: String objectName = entityCommand
117: .getAttribute("SQLExceptionProcessor");
118: if (objectName != null) {
119: try {
120: exceptionProcessor = (SQLExceptionProcessorMBean) MBeanProxyExt
121: .create(SQLExceptionProcessorMBean.class,
122: objectName);
123: } catch (MalformedObjectNameException e) {
124: throw new DeploymentException(
125: "Invalid object name for SQLExceptionProcessor: ",
126: e);
127: }
128: }
129: }
130:
131: public Object execute(Method m, Object[] args,
132: EntityEnterpriseContext ctx) throws CreateException {
133: // TODO: implement this logic nicer
134: if (insertAfterEjbPostCreate) {
135: if (!JDBCEntityBridge.isEjbCreateDone(ctx)) {
136: checkCreateAllowed();
137: generateFields(ctx);
138: JDBCEntityBridge.setEjbCreateDone(ctx);
139: } else {
140: beforeInsert(ctx);
141: performInsert(ctx);
142: afterInsert(ctx);
143: JDBCEntityBridge.setCreated(ctx);
144: }
145: } else {
146: checkCreateAllowed();
147: generateFields(ctx);
148: beforeInsert(ctx);
149: performInsert(ctx);
150: afterInsert(ctx);
151: JDBCEntityBridge.setCreated(ctx);
152: }
153: return getPrimaryKey(ctx);
154: }
155:
156: protected void checkCreateAllowed() throws CreateException {
157: if (!createAllowed) {
158: throw new CreateException(
159: "Creation is not allowed because a primary key field is read only.");
160: }
161: }
162:
163: protected JDBCCMPFieldBridge getGeneratedPKField()
164: throws DeploymentException {
165: // extract the pk field to be generated
166: JDBCCMPFieldBridge pkField = null;
167: JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
168: for (int i = 0; i < pkFields.length; ++i) {
169: if (pkField != null)
170: throw new DeploymentException(
171: "Generation only supported with single PK field");
172: pkField = (JDBCCMPFieldBridge) pkFields[i];
173: }
174: return pkField;
175: }
176:
177: protected void initGeneratedFields() throws DeploymentException {
178: createdPrincipal = entity.getCreatedPrincipalField();
179: if (securityManager == null && createdPrincipal != null) {
180: throw new DeploymentException(
181: "No security-domain configured but created-by specified");
182: }
183: updatedPrincipal = entity.getUpdatedPrincipalField();
184: if (securityManager == null && updatedPrincipal != null) {
185: throw new DeploymentException(
186: "No security-domain configured but updated-by specified");
187: }
188: createdTime = entity.getCreatedTimeField();
189: updatedTime = entity.getUpdatedTimeField();
190: }
191:
192: protected void generateFields(EntityEnterpriseContext ctx)
193: throws CreateException {
194: // Audit principal fields
195: if (securityManager != null) {
196: String principalName = ctx.getEJBContext()
197: .getCallerPrincipal().getName();
198: if (createdPrincipal != null
199: && createdPrincipal.getInstanceValue(ctx) == null) {
200: createdPrincipal.setInstanceValue(ctx, principalName);
201: }
202: /*
203: if(updatedPrincipal != null && updatedPrincipal.getInstanceValue(ctx) == null)
204: {
205: updatedPrincipal.setInstanceValue(ctx, principalName);
206: }
207: */
208: }
209:
210: // Audit time fields
211: Date date = null;
212: if (createdTime != null
213: && createdTime.getInstanceValue(ctx) == null) {
214: date = new Date();
215: createdTime.setInstanceValue(ctx, date);
216: }
217: /*
218: if(updatedTime != null && updatedTime.getInstanceValue(ctx) == null)
219: {
220: if(date == null)
221: date = new Date();
222: updatedTime.setInstanceValue(ctx, date);
223: }
224: */
225: }
226:
227: protected void initInsertFields() {
228: JDBCFieldBridge[] fields = entity.getTableFields();
229: List insertFieldsList = new ArrayList(fields.length);
230: for (int i = 0; i < fields.length; i++) {
231: JDBCFieldBridge field = fields[i];
232: if (isInsertField(field))
233: insertFieldsList.add(field);
234: }
235:
236: insertFields = (JDBCFieldBridge[]) insertFieldsList
237: .toArray(new JDBCFieldBridge[insertFieldsList.size()]);
238: }
239:
240: protected boolean isInsertField(JDBCFieldBridge field) {
241: boolean result = !(field instanceof JDBCCMRFieldBridge)
242: && field.getJDBCType() != null && !field.isReadOnly();
243: if (field instanceof JDBCCMPFieldBridge)
244: result = result
245: && !((JDBCCMPFieldBridge) field)
246: .isRelationTableField();
247: return result;
248: }
249:
250: protected void initInsertSQL() {
251: StringBuffer sql = new StringBuffer(250);
252: sql.append(SQLUtil.INSERT_INTO).append(
253: entity.getQualifiedTableName()).append(" (");
254:
255: SQLUtil.getColumnNamesClause(insertFields, sql);
256:
257: sql.append(')').append(SQLUtil.VALUES).append('(');
258: SQLUtil.getValuesClause(insertFields, sql).append(')');
259: insertSQL = sql.toString();
260:
261: if (debug)
262: log.debug("Insert Entity SQL: " + insertSQL);
263: }
264:
265: protected void beforeInsert(EntityEnterpriseContext ctx)
266: throws CreateException {
267: }
268:
269: protected void performInsert(EntityEnterpriseContext ctx)
270: throws CreateException {
271: Connection c = null;
272: PreparedStatement ps = null;
273: boolean throwRuntimeExceptions = entity.getMetaData()
274: .getThrowRuntimeExceptions();
275:
276: // if metadata is true, the getconnection is done inside
277: // its own try catch block to throw a runtime exception (EJBException)
278: if (throwRuntimeExceptions) {
279: try {
280: c = entity.getDataSource().getConnection();
281: } catch (SQLException sqle) {
282: javax.ejb.EJBException ejbe = new javax.ejb.EJBException(
283: "Could not get a connection; " + sqle);
284: ejbe.initCause(sqle);
285: throw ejbe;
286: }
287: }
288:
289: try {
290: if (debug)
291: log.debug("Executing SQL: " + insertSQL);
292:
293: // if metadata is false, the getconnection is done inside this try catch block
294: if (!throwRuntimeExceptions) {
295: c = entity.getDataSource().getConnection();
296: }
297: ps = prepareStatement(c, insertSQL, ctx);
298:
299: // set the parameters
300: int index = 1;
301: for (int fieldInd = 0; fieldInd < insertFields.length; ++fieldInd) {
302: index = insertFields[fieldInd].setInstanceParameters(
303: ps, index, ctx);
304: }
305:
306: // execute statement
307: int rowsAffected = executeInsert(index, ps, ctx);
308: if (rowsAffected != 1) {
309: throw new CreateException(
310: "Expected one affected row but update returned"
311: + rowsAffected + " for id="
312: + ctx.getId());
313: }
314: } catch (SQLException e) {
315: if (exceptionProcessor != null
316: && exceptionProcessor.isDuplicateKey(e)) {
317: log.error("Failed to create instance.", e);
318: throw new CreateException(
319: "Integrity constraint violation. Possibly unique key violation or invalid foreign key value.");
320: } else {
321: log.error("Could not create entity", e);
322: CreateException ce = new CreateException(
323: "Could not create entity:" + e);
324: ce.initCause(e);
325: throw ce;
326: }
327: } finally {
328: JDBCUtil.safeClose(ps);
329: JDBCUtil.safeClose(c);
330: }
331:
332: // Mark the inserted fields as clean.
333: for (int fieldInd = 0; fieldInd < insertFields.length; ++fieldInd) {
334: insertFields[fieldInd].setClean(ctx);
335: }
336: }
337:
338: protected PreparedStatement prepareStatement(Connection c,
339: String sql, EntityEnterpriseContext ctx)
340: throws SQLException {
341: return c.prepareStatement(sql);
342: }
343:
344: protected int executeInsert(int paramIndex, PreparedStatement ps,
345: EntityEnterpriseContext ctx) throws SQLException {
346: return ps.executeUpdate();
347: }
348:
349: protected void afterInsert(EntityEnterpriseContext ctx)
350: throws CreateException {
351: }
352:
353: protected Object getPrimaryKey(EntityEnterpriseContext ctx) {
354: return entity.extractPrimaryKeyFromInstance(ctx);
355: }
356: }
|