| /***************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| ****************************************************************/ |
| |
| package org.apache.cayenne.access.types; |
| |
| import org.apache.cayenne.CayenneException; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.io.StringWriter; |
| import java.sql.CallableStatement; |
| import java.sql.Clob; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Types; |
| |
| /** |
| * Handles <code>java.lang.String</code>, mapping it as either of JDBC types - |
| * CLOB or (VAR)CHAR. Can be configured to trim trailing spaces. |
| */ |
| public class CharType implements ExtendedType<String> { |
| |
| private static final int BUF_SIZE = 8 * 1024; |
| private static final int TRIM_VALUES_THRESHOLD = 30; |
| |
| protected boolean trimmingChars; |
| protected boolean usingClobs; |
| |
| public CharType(boolean trimmingChars, boolean usingClobs) { |
| this.trimmingChars = trimmingChars; |
| this.usingClobs = usingClobs; |
| } |
| |
| /** |
| * Returns "java.lang.String". |
| */ |
| @Override |
| public String getClassName() { |
| return String.class.getName(); |
| } |
| |
| /** Return trimmed string. */ |
| @Override |
| public String materializeObject(ResultSet rs, int index, int type) throws Exception { |
| |
| if (type == Types.CLOB || type == Types.NCLOB) { |
| return isUsingClobs() ? readClob(rs.getClob(index)) : readCharStream(rs, index); |
| } |
| |
| return handleString(rs.getString(index), type); |
| } |
| |
| @Override |
| public String materializeObject(CallableStatement cs, int index, int type) throws Exception { |
| |
| if (type == Types.CLOB || type == Types.NCLOB) { |
| if (!isUsingClobs()) { |
| throw new CayenneException("Character streams are not supported in stored procedure parameters."); |
| } |
| |
| return readClob(cs.getClob(index)); |
| } |
| |
| return handleString(cs.getString(index), type); |
| } |
| |
| private String handleString(String val, int type) throws SQLException { |
| // trim CHAR type |
| if (val != null && (type == Types.CHAR || type == Types.NCHAR) && isTrimmingChars()) { |
| return rtrim(val); |
| } |
| |
| return val; |
| } |
| |
| /** Trim right spaces. */ |
| protected String rtrim(String value) { |
| int end = value.length() - 1; |
| int count = end; |
| while (end >= 0 && value.charAt(end) <= ' ') { |
| end--; |
| } |
| return end == count ? value : value.substring(0, end + 1); |
| } |
| |
| @Override |
| public void setJdbcObject(PreparedStatement st, String value, int pos, int type, int scale) throws Exception { |
| |
| // if this is a CLOB column, set the value as "String" |
| // instead. This should work with most drivers |
| if (type == Types.CLOB || type == Types.NCLOB) { |
| st.setString(pos, value); |
| } else if (scale != -1) { |
| st.setObject(pos, value, type, scale); |
| } else { |
| st.setObject(pos, value, type); |
| } |
| } |
| |
| @Override |
| public String toString(String value) { |
| if (value == null) { |
| return "NULL"; |
| } |
| |
| StringBuilder buffer = new StringBuilder(); |
| |
| buffer.append('\''); |
| |
| String literal = value; |
| if (literal.length() > TRIM_VALUES_THRESHOLD) { |
| literal = literal.substring(0, TRIM_VALUES_THRESHOLD) + "..."; |
| } |
| |
| // escape quotes |
| int curPos = 0; |
| int endPos = 0; |
| |
| while ((endPos = literal.indexOf('\'', curPos)) >= 0) { |
| buffer.append(literal.substring(curPos, endPos + 1)).append('\''); |
| curPos = endPos + 1; |
| } |
| |
| if (curPos < literal.length()) { |
| buffer.append(literal.substring(curPos)); |
| } |
| |
| buffer.append('\''); |
| |
| return buffer.toString(); |
| } |
| |
| protected String readClob(Clob clob) throws IOException, SQLException { |
| if (clob == null) { |
| return null; |
| } |
| |
| // sanity check on size |
| if (clob.length() > Integer.MAX_VALUE) { |
| throw new IllegalArgumentException("CLOB is too big to be read as String in memory: " + clob.length()); |
| } |
| |
| int size = (int) clob.length(); |
| if (size == 0) { |
| return ""; |
| } |
| |
| return clob.getSubString(1, size); |
| } |
| |
| protected String readCharStream(ResultSet rs, int index) throws IOException, SQLException { |
| try (Reader in = rs.getCharacterStream(index);) { |
| return in != null ? readValueStream(in, -1, BUF_SIZE) : null; |
| } |
| } |
| |
| protected String readValueStream(Reader in, int streamSize, int bufSize) throws IOException { |
| char[] buf = new char[bufSize]; |
| StringWriter out = streamSize > 0 ? new StringWriter(streamSize) : new StringWriter(); |
| |
| int read; |
| while ((read = in.read(buf, 0, bufSize)) >= 0) { |
| out.write(buf, 0, read); |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * Returns <code>true</code> if 'materializeObject' method should trim |
| * trailing spaces from the CHAR columns. This addresses an issue with some |
| * JDBC drivers (e.g. Oracle), that return Strings for CHAR columns padded |
| * with spaces. |
| */ |
| public boolean isTrimmingChars() { |
| return trimmingChars; |
| } |
| |
| public void setTrimmingChars(boolean trimingChars) { |
| this.trimmingChars = trimingChars; |
| } |
| |
| public boolean isUsingClobs() { |
| return usingClobs; |
| } |
| |
| public void setUsingClobs(boolean usingClobs) { |
| this.usingClobs = usingClobs; |
| } |
| } |