/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.common.buffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; import java.sql.SQLXML; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.rowset.serial.SerialBlob; import org.teiid.core.TeiidComponentException; import org.teiid.core.types.*; import org.teiid.core.types.InputStreamFactory.BlobInputStreamFactory; import org.teiid.core.types.InputStreamFactory.ClobInputStreamFactory; import org.teiid.core.types.InputStreamFactory.SQLXMLInputStreamFactory; import org.teiid.core.types.InputStreamFactory.StorageMode; import org.teiid.core.util.ObjectConverterUtil; import org.teiid.query.QueryPlugin; import org.teiid.query.sql.symbol.Expression; /** * Tracks lob references so they are not lost during serialization. * TODO: for temp tables we may need to have a copy by value management strategy */ public class LobManager { public enum ReferenceMode { ATTACH, CREATE, REMOVE, } private static class LobHolder { Streamable<?> lob; int referenceCount = 1; public LobHolder(Streamable<?> lob) { this.lob = lob; } } private Map<String, LobHolder> lobReferences = Collections.synchronizedMap(new HashMap<String, LobHolder>()); private boolean inlineLobs = true; private int maxMemoryBytes = DataTypeManager.MAX_LOB_MEMORY_BYTES; private int[] lobIndexes; private FileStore lobStore; public LobManager(int[] lobIndexes, FileStore lobStore) { this.lobIndexes = lobIndexes; this.lobStore = lobStore; } public LobManager clone() { LobManager clone = new LobManager(lobIndexes, null); clone.inlineLobs = inlineLobs; clone.maxMemoryBytes = maxMemoryBytes; synchronized (lobReferences) { for (Map.Entry<String, LobHolder> entry : lobReferences.entrySet()) { LobHolder lobHolder = new LobHolder(entry.getValue().lob); lobHolder.referenceCount = entry.getValue().referenceCount; clone.lobReferences.put(entry.getKey(), lobHolder); } } return clone; } public void setInlineLobs(boolean trackMemoryLobs) { this.inlineLobs = trackMemoryLobs; } public void setMaxMemoryBytes(int maxMemoryBytes) { this.maxMemoryBytes = maxMemoryBytes; } @SuppressWarnings("unchecked") public void updateReferences(List<?> tuple, ReferenceMode mode) throws TeiidComponentException { for (int i = 0; i < lobIndexes.length; i++) { Object anObj = tuple.get(lobIndexes[i]); if (!(anObj instanceof Streamable<?>)) { continue; } Streamable lob = (Streamable) anObj; String id = lob.getReferenceStreamId(); LobHolder lobHolder = this.lobReferences.get(id); switch (mode) { case REMOVE: if (lobHolder != null) { lobHolder.referenceCount--; if (lobHolder.referenceCount < 1) { this.lobReferences.remove(id); } } break; case ATTACH: if (lob.getReference() == null) { if (lobHolder == null) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30033, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30033)); } lob.setReference(lobHolder.lob.getReference()); } break; case CREATE: try { StorageMode storageMode = InputStreamFactory.getStorageMode(lob); if (lob.getReferenceStreamId() == null || (inlineLobs && (storageMode == StorageMode.MEMORY || (storageMode != StorageMode.FREE && lob.length()*(lob instanceof ClobType?2:1) <= maxMemoryBytes)))) { lob.setReferenceStreamId(null); //since this is untracked at this point, we must detach if possible if (inlineLobs && storageMode == StorageMode.OTHER) { persistLob(lob, null, null, true, maxMemoryBytes); } continue; } } catch (SQLException e) { //presumably the lob is bad, but let it slide for now } if (lob.getReference() == null) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30034, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30034)); } if (lobHolder == null) { this.lobReferences.put(id, new LobHolder(lob)); } else { lobHolder.referenceCount++; } break; } } } public Streamable<?> getLobReference(String id) throws TeiidComponentException { LobHolder lob = this.lobReferences.get(id); if (lob == null) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30035, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30035)); } return lob.lob; } public static int[] getLobIndexes(List<? extends Expression> expressions) { if (expressions == null) { return null; } int[] result = new int[expressions.size()]; int resultIndex = 0; for (int i = 0; i < expressions.size(); i++) { Expression expr = expressions.get(i); if (DataTypeManager.isLOB(expr.getType()) || expr.getType() == DataTypeManager.DefaultDataClasses.OBJECT) { result[resultIndex++] = i; } } if (resultIndex == 0) { return null; } return Arrays.copyOf(result, resultIndex); } public void persist() throws TeiidComponentException { // stream the contents of lob into file store. byte[] bytes = new byte[1 << 14]; AutoCleanupUtil.setCleanupReference(this, lobStore); for (Map.Entry<String, LobHolder> entry : this.lobReferences.entrySet()) { detachLob(entry.getValue().lob, lobStore, bytes); } } public void detachLob(final Streamable<?> lob, final FileStore store, byte[] bytes) throws TeiidComponentException { // if this is not attached, just return if (InputStreamFactory.getStorageMode(lob) != StorageMode.MEMORY) { persistLob(lob, store, bytes, inlineLobs, maxMemoryBytes); } } public static void persistLob(final Streamable<?> lob, final FileStore store, byte[] bytes, boolean inlineLobs, int maxMemoryBytes) throws TeiidComponentException { long byteLength = Integer.MAX_VALUE; try { byteLength = lob.length()*(lob instanceof ClobType?2:1); } catch (SQLException e) { //just ignore for now - for a single read resource computing the length invalidates //TODO - inline small persisted lobs } try { //inline if (lob.getReferenceStreamId() == null || (inlineLobs && (byteLength <= maxMemoryBytes))) { lob.setReferenceStreamId(null); if (InputStreamFactory.getStorageMode(lob) == StorageMode.MEMORY) { return; } if (lob instanceof BlobType) { BlobType b = (BlobType)lob; byte[] blobBytes = b.getBytes(1, (int)byteLength); b.setReference(new SerialBlob(blobBytes)); } else if (lob instanceof ClobType) { ClobType c = (ClobType)lob; String s = c.getSubString(1, (int)(byteLength>>>1)); c.setReference(new ClobImpl(s)); } else { XMLType x = (XMLType)lob; String s = x.getString(); x.setReference(new SQLXMLImpl(s)); } return; } InputStream is = null; if (lob instanceof BlobType) { is = new BlobInputStreamFactory((Blob)lob).getInputStream(); } else if (lob instanceof ClobType) { is = new ClobInputStreamFactory((Clob)lob).getInputStream(); } else { is = new SQLXMLInputStreamFactory((SQLXML)lob).getInputStream(); } long offset = store.getLength(); OutputStream fsos = store.createOutputStream(); byteLength = ObjectConverterUtil.write(fsos, is, bytes, -1); // re-construct the new lobs based on the file store final long lobOffset = offset; final long lobLength = byteLength; /* * Using an inner class here will hold a reference to the LobManager * which prevents the removal of the FileStore until all of the * lobs have been gc'd */ InputStreamFactory isf = new InputStreamFactory() { @Override public InputStream getInputStream() throws IOException { return store.createInputStream(lobOffset, lobLength); } @Override public StorageMode getStorageMode() { return StorageMode.PERSISTENT; } }; isf.setLength(byteLength); if (lob instanceof BlobType) { ((BlobType)lob).setReference(new BlobImpl(isf)); } else if (lob instanceof ClobType) { ((ClobType)lob).setReference(new ClobImpl(isf, ((ClobType)lob).length())); } else { ((XMLType)lob).setReference(new SQLXMLImpl(isf)); } } catch (SQLException e) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30037, e); } catch (IOException e) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30036, e); } } public int getLobCount() { return this.lobReferences.size(); } public void remove() { this.lobReferences.clear(); this.lobStore.remove(); } }