View Javadoc

1   /*
2    * Copyright (C) 2003-2012 David E. Berry
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17   *
18   * A copy of the GNU Lesser General Public License may also be found at
19   * http://www.gnu.org/licenses/lgpl.txt
20   */
21  package org.synchronoss.cpo.jdbc;
22  
23  import java.io.StringReader;
24  import java.lang.reflect.InvocationTargetException;
25  import java.sql.Connection;
26  import java.sql.PreparedStatement;
27  import java.sql.SQLException;
28  import java.util.Map.Entry;
29  import java.util.*;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  import org.synchronoss.cpo.*;
33  import org.synchronoss.cpo.helper.ExceptionHelper;
34  import org.synchronoss.cpo.meta.domain.CpoArgument;
35  import org.synchronoss.cpo.meta.domain.CpoClass;
36  import org.synchronoss.cpo.meta.domain.CpoFunction;
37  
38  /**
39   * JdbcPreparedStatementFactory is the object that encapsulates the creation of the actual PreparedStatement for the
40   * JDBC driver.
41   *
42   * @author david berry
43   */
44  public class JdbcPreparedStatementFactory implements CpoReleasible {
45  
46    /**
47     * Version Id for this class.
48     */
49    private static final long serialVersionUID = 1L;
50    /**
51     * DOCUMENT ME!
52     */
53    private static final Logger logger = LoggerFactory.getLogger(JdbcPreparedStatementFactory.class);
54    private Logger localLogger = null;
55    private PreparedStatement ps_ = null;
56  
57    @SuppressWarnings("unused")
58    private JdbcPreparedStatementFactory() {
59    }
60    private List<CpoReleasible> releasibles = new ArrayList<CpoReleasible>();
61    private static final String WHERE_MARKER = "__CPO_WHERE__";
62    private static final String ORDERBY_MARKER = "__CPO_ORDERBY__";
63  
64    /**
65     * Used to build the PreparedStatement that is used by CPO to create the actual JDBC PreparedStatement.
66     *
67     * The constructor is called by the internal CPO framework. This is not to be used by users of CPO. Programmers that
68     * build Transforms may need to use this object to get access to the actual connection.
69     *
70     * @param conn The actual jdbc connection that will be used to create the callable statement.
71     * @param jca The JdbcCpoAdapter that is controlling this transaction
72     * @param jq The CpoFunction that is being executed
73     * @param obj The pojo that is being acted upon
74     * @param additionalSql Additional sql to be appended to the CpoFunction sql that is used to create the actual JDBC
75     * PreparedStatement
76     *
77     * @throws CpoException if a CPO error occurs
78     * @throws SQLException if a JDBC error occurs
79     */
80    public <T> JdbcPreparedStatementFactory(Connection conn, JdbcCpoAdapter jca, CpoClass criteria,
81            CpoFunction function, T obj, Collection<CpoWhere> wheres, Collection<CpoOrderBy> orderBy,
82            Collection<CpoNativeFunction> nativeQueries) throws CpoException {
83  
84      // get the list of bindValues from the function parameters
85      List<BindAttribute> bindValues = getBindValues(function, obj);
86  
87      String sql = buildSql(criteria, function.getExpression(), wheres, orderBy, nativeQueries, bindValues);
88  
89      localLogger = obj == null ? logger : LoggerFactory.getLogger(obj.getClass());
90  
91      localLogger.debug("CpoFunction SQL = <" + sql + ">");
92  
93      PreparedStatement pstmt = null;
94  
95      try {
96        pstmt = conn.prepareStatement(sql);
97      } catch (SQLException se) {
98        localLogger.error("Error Instantiating JdbcPreparedStatementFactory SQL=<" + sql + ">" + ExceptionHelper.getLocalizedMessage(se));
99        throw new CpoException(se);
100     }
101     setPreparedStatement(pstmt);
102 
103     setBindValues(bindValues);
104 
105   }
106 
107   /**
108    * DOCUMENT ME!
109    *
110    * @param cpoClass DOCUMENT ME!
111    * @param sql DOCUMENT ME!
112    * @param where DOCUMENT ME!
113    * @param orderBy DOCUMENT ME!
114    *
115    * @return DOCUMENT ME!
116    *
117    * @throws CpoException DOCUMENT ME!
118    */
119   private <T> String buildSql(CpoClass cpoClass, String sql, Collection<CpoWhere> wheres, Collection<CpoOrderBy> orderBy, Collection<CpoNativeFunction> nativeQueries, List<BindAttribute> bindValues) throws CpoException {
120     StringBuilder sqlText = new StringBuilder();
121 
122     sqlText.append(sql);
123 
124     if (wheres != null) {
125       for (CpoWhere where : wheres) {
126         JdbcWhereBuilder<T> jwb = new JdbcWhereBuilder<T>(cpoClass);
127         JdbcCpoWhere jcw = (JdbcCpoWhere) where;
128 
129         // do the where stuff here when ready
130         try {
131           jcw.acceptDFVisitor(jwb);
132         } catch (Exception e) {
133           throw new CpoException("Unable to build WHERE clause", e);
134         }
135 
136         if (sqlText.indexOf(jcw.getName()) == -1) {
137           sqlText.append(" ");
138           sqlText.append(jwb.getWhereClause());
139           bindValues.addAll(jwb.getBindValues());
140         } else {
141           sqlText = replaceMarker(sqlText, jcw.getName(), jwb, bindValues);
142         }
143       }
144     }
145 
146     // do the order by stuff now
147     if (orderBy != null) {
148       HashMap<String, StringBuilder> mapOrderBy = new HashMap<String, StringBuilder>();
149       try {
150         for (CpoOrderBy ob : orderBy) {
151           StringBuilder sb = mapOrderBy.get(ob.getMarker());
152           if (sb == null) {
153             sb = new StringBuilder(" ORDER BY ");
154             mapOrderBy.put(ob.getMarker(), sb);
155           } else {
156             sb.append(",");
157           }
158           sb.append(((JdbcCpoOrderBy) ob).toString(cpoClass));
159         }
160       } catch (CpoException ce) {
161         throw new CpoException("Error Processing OrderBy Attribute<" + ExceptionHelper.getLocalizedMessage(ce) + "> not Found. JDBC Expression=<" + sqlText.toString() + ">");
162       }
163 
164       Set<Entry<String, StringBuilder>> entries = mapOrderBy.entrySet();
165       for (Entry<String, StringBuilder> entry : entries) {
166         if (sqlText.indexOf(entry.getKey()) == -1) {
167           sqlText.append(entry.getValue().toString());
168         } else {
169           sqlText = replaceMarker(sqlText, entry.getKey(), entry.getValue().toString());
170         }
171       }
172     }
173 
174     if (nativeQueries != null) {
175       for (CpoNativeFunction cnq : nativeQueries) {
176         if (cnq.getMarker() == null || sqlText.indexOf(cnq.getMarker()) == -1) {
177           if (cnq.getExpression() != null && cnq.getExpression().length() > 0) {
178             sqlText.append(" ");
179             sqlText.append(cnq.getExpression());
180           }
181         } else {
182           sqlText = replaceMarker(sqlText, cnq.getMarker(), cnq.getExpression());
183         }
184       }
185     }
186 
187     // left for backwards compatibility
188     sqlText = replaceMarker(sqlText, WHERE_MARKER, "");
189     sqlText = replaceMarker(sqlText, ORDERBY_MARKER, "");
190 
191     return sqlText.toString();
192   }
193 
194   private StringBuilder replaceMarker(StringBuilder source, String marker, String replace) {
195     int attrOffset;
196     int fromIndex = 0;
197     int mLength = marker.length();
198     String replaceText = replace == null ? "" : replace;
199     int rLength = replaceText.length();
200 
201     //OUT.debug("starting string <"+source.toString()+">");
202     if (source != null && source.length() > 0) {
203       while ((attrOffset = source.indexOf(marker, fromIndex)) != -1) {
204         source.replace(attrOffset, attrOffset + mLength, replaceText);
205         fromIndex = attrOffset + rLength;
206       }
207     }
208     //OUT.debug("ending string <"+source.toString()+">");
209 
210     return source;
211   }
212 
213   private <T> StringBuilder replaceMarker(StringBuilder source, String marker, JdbcWhereBuilder<T> jwb, List<BindAttribute> bindValues) {
214     int attrOffset;
215     int fromIndex = 0;
216     int mLength = marker.length();
217     String replace = jwb.getWhereClause();
218     int rLength = replace.length();
219     Collection<BindAttribute> jwbBindValues = jwb.getBindValues();
220 
221     //OUT.debug("starting string <"+source.toString()+">");
222     if (source != null && source.length() > 0) {
223       while ((attrOffset = source.indexOf(marker, fromIndex)) != -1) {
224         source.replace(attrOffset, attrOffset + mLength, replace);
225         fromIndex = attrOffset + rLength;
226         bindValues.addAll(countBindMarkers(source.substring(0, attrOffset)), jwbBindValues);
227       }
228     }
229     //OUT.debug("ending string <"+source.toString()+">");
230 
231     return source;
232   }
233 
234   private int countBindMarkers(String source) {
235     StringReader reader;
236     int rc;
237     int qMarks = 0;
238     boolean inDoubleQuotes = false;
239     boolean inSingleQuotes = false;
240 
241     if (source != null) {
242       reader = new StringReader(source);
243 
244       try {
245         do {
246           rc = reader.read();
247           if (((char) rc) == '\'') {
248             inSingleQuotes = !inSingleQuotes;
249           } else if (((char) rc) == '"') {
250             inDoubleQuotes = !inDoubleQuotes;
251           } else if (!inSingleQuotes && !inDoubleQuotes && ((char) rc) == '?') {
252             qMarks++;
253           }
254         } while (rc != -1);
255       } catch (Exception e) {
256         logger.error("error counting bind markers");
257       }
258     }
259 
260     return qMarks;
261   }
262 
263   /**
264    * Returns the jdbc prepared statment associated with this object
265    */
266   public PreparedStatement getPreparedStatement() {
267     return ps_;
268   }
269 
270   protected void setPreparedStatement(PreparedStatement ps) {
271     ps_ = ps;
272   }
273 
274   /**
275    * Adds a releasible object to this object. The release method on the releasible will be called when the
276    * PreparedStatement is executed.
277    *
278    */
279   public void AddReleasible(CpoReleasible releasible) {
280     if (releasible != null) {
281       releasibles.add(releasible);
282     }
283   }
284 
285   /**
286    * Called by the CPO framework. This method calls the
287    * <code>release</code> on all the CpoReleasible associated with this object
288    */
289   @Override
290   public void release() throws CpoException {
291     for (CpoReleasible releasible : releasibles) {
292       try {
293         releasible.release();
294       } catch (CpoException ce) {
295         localLogger.error("Error Releasing Prepared Statement Transform Object", ce);
296         throw ce;
297       }
298     }
299   }
300 
301   /**
302    * Called by the CPO Framework. Binds all the attibutes from the class for the CPO meta parameters and the parameters
303    * from the dynamic where.
304    *
305    */
306   protected List<BindAttribute> getBindValues(CpoFunction function, Object obj) throws CpoException {
307     List<BindAttribute> bindValues = new ArrayList<BindAttribute>();
308     List<CpoArgument> arguments = function.getArguments();
309     for (CpoArgument argument : arguments) {
310       if (argument == null) {
311         throw new CpoException("CpoArgument is null!");
312       }
313       bindValues.add(new BindAttribute((JdbcCpoAttribute) argument.getAttribute(), obj));
314     }
315     return bindValues;
316   }
317 
318   /**
319    * Called by the CPO Framework. Binds all the attibutes from the class for the CPO meta parameters and the parameters
320    * from the dynamic where.
321    *
322    */
323   protected void setBindValues(Collection<BindAttribute> bindValues) throws CpoException {
324 
325     if (bindValues != null) {
326       int index = 1;
327 
328       //runs through the bind attributes and binds them to the prepared statement
329       // They must be in correct order.
330       for (BindAttribute bindAttr : bindValues) {
331         Object bindObject = bindAttr.getBindObject();
332         JdbcCpoAttribute ja = bindAttr.getJdbcAttribute();
333 
334         // check to see if we are getting a cpo value object or an object that
335         // can be put directly in the statement (String, BigDecimal, etc)
336         JavaSqlMethod<?> jsm = null;
337         jsm = JavaSqlMethods.getJavaSqlMethod(bindObject.getClass());
338 
339         if (jsm != null) {
340           try {
341             if (ja == null) {
342               localLogger.debug(bindAttr.getName() + "=" + bindObject);
343             } else {
344               localLogger.debug(ja.getDataName() + "=" + bindObject);
345             }
346             jsm.getPsSetter().invoke(this.getPreparedStatement(), new Object[]{index++, bindObject});
347           } catch (IllegalAccessException iae) {
348             localLogger.error("Error Accessing Prepared Statement Setter: " + ExceptionHelper.getLocalizedMessage(iae));
349             throw new CpoException(iae);
350           } catch (InvocationTargetException ite) {
351             localLogger.error("Error Invoking Prepared Statement Setter: " + ExceptionHelper.getLocalizedMessage(ite));
352             throw new CpoException(ite.getCause());
353           }
354         } else {
355           CpoData cpoData = new PreparedStatementCpoData(this, ja, index++);
356           cpoData.invokeSetter(bindObject);
357         }
358       }
359     }
360 
361   }
362 }