/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed 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.jkiss.dbeaver.model.impl.jdbc.data.handlers; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.data.*; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.exec.DBCSession; import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement; import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; import org.jkiss.dbeaver.model.impl.jdbc.data.*; import org.jkiss.dbeaver.model.messages.ModelMessages; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.SQLDataSource; import org.jkiss.dbeaver.model.struct.DBSTypedObject; import org.jkiss.dbeaver.utils.ContentUtils; import org.jkiss.dbeaver.utils.MimeTypes; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.IOUtils; import java.io.*; import java.sql.*; /** * JDBC Content value handler. * Handle LOBs, LONGs and BINARY types. * * @author Serge Rider */ public class JDBCContentValueHandler extends JDBCAbstractValueHandler implements DBDContentValueHandler { private static final Log log = Log.getLog(JDBCContentValueHandler.class); public static final JDBCContentValueHandler INSTANCE = new JDBCContentValueHandler(); public static final int MAX_CACHED_CLOB_LENGTH = 10000; @NotNull @Override public String getValueContentType(@NotNull DBSTypedObject attribute) { if (attribute.getTypeID() == Types.SQLXML) { return MimeTypes.TEXT_XML; } return MimeTypes.OCTET_STREAM; } @Override protected DBDContent fetchColumnValue( DBCSession session, JDBCResultSet resultSet, DBSTypedObject type, int index) throws DBCException, SQLException { Object value = resultSet.getObject(index); if (value == null && !resultSet.wasNull()) { // This may happen in some bad drivers like ODBC bridge switch (type.getTypeID()) { case java.sql.Types.CHAR: case java.sql.Types.VARCHAR: case java.sql.Types.NVARCHAR: case java.sql.Types.LONGVARCHAR: case java.sql.Types.LONGNVARCHAR: case java.sql.Types.CLOB: case java.sql.Types.NCLOB: value = resultSet.getString(index); break; case java.sql.Types.BINARY: case java.sql.Types.VARBINARY: case java.sql.Types.LONGVARBINARY: case java.sql.Types.BLOB: value = resultSet.getBytes(index); break; case java.sql.Types.SQLXML: value = resultSet.getSQLXML(index); break; default: value = resultSet.getObject(index); break; } } if (value instanceof String) { // If we have a string - do not try to convert it to a binary representation (#494) // We need to convert only in case of some value transformations, not when getting it from DB return new JDBCContentChars(session.getDataSource(), (String) value); } return getValueFromObject(session, type, value, false); } @Override protected void bindParameter( JDBCSession session, JDBCPreparedStatement statement, DBSTypedObject paramType, int paramIndex, Object value) throws DBCException, SQLException { if (value instanceof JDBCContentAbstract) { ((JDBCContentAbstract)value).bindParameter(session, statement, paramType, paramIndex); } else { throw new DBCException(ModelMessages.model_jdbc_unsupported_value_type_ + value); } } @NotNull @Override public Class<DBDContent> getValueObjectType(@NotNull DBSTypedObject attribute) { return DBDContent.class; } @Override public DBDContent getValueFromObject(@NotNull DBCSession session, @NotNull DBSTypedObject type, Object object, boolean copy) throws DBCException { if (object == null) { // Create wrapper using column type switch (type.getTypeID()) { case java.sql.Types.CHAR: case java.sql.Types.VARCHAR: case java.sql.Types.NVARCHAR: case java.sql.Types.LONGVARCHAR: case java.sql.Types.LONGNVARCHAR: return new JDBCContentChars(session.getDataSource(), null); case java.sql.Types.CLOB: case java.sql.Types.NCLOB: return new JDBCContentCLOB(session.getDataSource(), null); case java.sql.Types.BINARY: case java.sql.Types.VARBINARY: case java.sql.Types.LONGVARBINARY: return new JDBCContentBytes(session.getDataSource()); case java.sql.Types.BLOB: return new JDBCContentBLOB(session.getDataSource(), null); case java.sql.Types.SQLXML: return new JDBCContentXML(session.getDataSource(), null); default: log.error(ModelMessages.model_jdbc_unsupported_column_type_ + type.getTypeName()); return new JDBCContentBytes(session.getDataSource()); } } else if (object instanceof byte[]) { return new JDBCContentBytes(session.getDataSource(), (byte[]) object); } else if (object instanceof String) { // String is a default format in many cases (like clipboard transfer) // So it is possible that real object type isn't string switch (type.getTypeID()) { case java.sql.Types.BINARY: case java.sql.Types.VARBINARY: case java.sql.Types.LONGVARBINARY: return new JDBCContentBytes(session.getDataSource(), (String) object); default: // String by default return new JDBCContentChars(session.getDataSource(), (String) object); } } else if (object instanceof Blob) { final JDBCContentBLOB blob = new JDBCContentBLOB(session.getDataSource(), (Blob) object); final DBPPreferenceStore preferenceStore = session.getDataSource().getContainer().getPreferenceStore(); if (preferenceStore.getBoolean(ModelPreferences.CONTENT_CACHE_BLOB) && blob.getLOBLength() < preferenceStore.getLong(ModelPreferences.CONTENT_CACHE_MAX_SIZE)) { // Precache content blob.getContents(session.getProgressMonitor()); } return blob; } else if (object instanceof Clob) { JDBCContentCLOB clob = new JDBCContentCLOB(session.getDataSource(), (Clob) object); final DBPPreferenceStore preferenceStore = session.getDataSource().getContainer().getPreferenceStore(); if (preferenceStore.getBoolean(ModelPreferences.CONTENT_CACHE_CLOB) && clob.getLOBLength() < preferenceStore.getLong(ModelPreferences.CONTENT_CACHE_MAX_SIZE)) { // Precache content clob.getContents(session.getProgressMonitor()); } return clob; } else if (object instanceof SQLXML) { return new JDBCContentXML(session.getDataSource(), (SQLXML) object); } else if (object instanceof InputStream) { // Some weird drivers returns InputStream instead of Xlob. // Copy stream to byte array ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final InputStream stream = (InputStream) object; try { IOUtils.copyStream(stream, buffer); } catch (Exception e) { throw new DBCException("Error reading content stream", e); } IOUtils.close(stream); return new JDBCContentBytes(session.getDataSource(), buffer.toByteArray()); } else if (object instanceof Reader) { // Copy reader to string StringWriter buffer = new StringWriter(); final Reader reader = (Reader) object; try { IOUtils.copyText(reader, buffer); } catch (Exception e) { throw new DBCException("Error reading content reader", e); } IOUtils.close(reader); return new JDBCContentChars(session.getDataSource(), buffer.toString()); } else if (object instanceof DBDContent) { if (copy && object instanceof DBDValueCloneable) { return (DBDContent) ((DBDValueCloneable)object).cloneValue(session.getProgressMonitor()); } return (DBDContent) object; } else { // Give up. Let's show string value return new JDBCContentChars(session.getDataSource(), CommonUtils.toString(object)); //throw new DBCException(ModelMessages.model_jdbc_unsupported_value_type_ + object.getClass().getName()); } } @NotNull @Override public String getValueDisplayString(@NotNull DBSTypedObject column, Object value, @NotNull DBDDisplayFormat format) { if (value instanceof DBDContent) { String result = ((DBDContent) value).getDisplayString(format); if (result == null) { return super.getValueDisplayString(column, null, format); } else { return result; } } return super.getValueDisplayString(column, value, format); } @Override public void writeStreamValue(DBRProgressMonitor monitor, @NotNull DBPDataSource dataSource, @NotNull DBSTypedObject type, @NotNull DBDContent object, @NotNull Writer writer) throws DBCException, IOException { DBDContentStorage cs = object.getContents(monitor); if (cs != null) { if (ContentUtils.isTextContent(object)) { writer.write("'"); String strValue = ContentUtils.getContentStringValue(monitor, object); if (dataSource instanceof SQLDataSource) { strValue = ((SQLDataSource) dataSource).getSQLDialect().escapeString(strValue); } writer.write(strValue); writer.write("'"); } else { if (dataSource instanceof SQLDataSource) { DBDBinaryFormatter binaryFormatter = ((SQLDataSource) dataSource).getSQLDialect().getNativeBinaryFormatter(); ByteArrayOutputStream buffer = new ByteArrayOutputStream((int) cs.getContentLength()); try (InputStream contentStream = cs.getContentStream()) { IOUtils.copyStream(contentStream, buffer); } final byte[] bytes = buffer.toByteArray(); final String binaryString = binaryFormatter.toString(bytes, 0, bytes.length); writer.write(binaryString); } else { // Binary data not supported writer.write("NULL"); } } } else { writer.write("NULL"); } } }