/* * 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.dqp.internal.process; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import org.teiid.client.SourceWarning; import org.teiid.common.buffer.BlockedException; import org.teiid.common.buffer.TupleSource; import org.teiid.core.TeiidComponentException; import org.teiid.core.TeiidProcessingException; import org.teiid.core.TeiidRuntimeException; import org.teiid.core.util.Assertion; import org.teiid.dqp.internal.datamgr.ConnectorWork; import org.teiid.dqp.internal.process.DQPCore.CompletionListener; import org.teiid.dqp.message.AtomicRequestMessage; import org.teiid.dqp.message.AtomicResultsMessage; import org.teiid.events.EventDistributor; import org.teiid.metadata.Table; import org.teiid.query.QueryPlugin; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.processor.relational.RelationalNodeUtil; import org.teiid.query.sql.lang.BatchedUpdateCommand; import org.teiid.query.sql.lang.Command; import org.teiid.query.sql.lang.ProcedureContainer; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.TranslatorException; import org.teiid.translator.CacheDirective.Scope; /** * This tuple source impl can only be used once; once it is closed, it * cannot be reopened and reused. * * TODO: the handling of DataNotAvailable is awkward. * In the multi-threaded case we'd like to not even * notify the parent plan and just schedule the next poll. */ public class DataTierTupleSource implements TupleSource, CompletionListener<AtomicResultsMessage> { // Construction state private final AtomicRequestMessage aqr; private final RequestWorkItem workItem; private final ConnectorWork cwi; private final DataTierManagerImpl dtm; private int limit = -1; // Data state private int index; private int rowsProcessed; private AtomicResultsMessage arm; private AtomicBoolean closing = new AtomicBoolean(); private AtomicBoolean closed = new AtomicBoolean(); private volatile boolean canceled; private volatile boolean cancelAsynch; private boolean executed; private volatile boolean done; private boolean explicitClose; private volatile FutureWork<AtomicResultsMessage> futureResult; private volatile boolean running; boolean errored; Scope scope; //this is to avoid synchronization private long waitUntil; private Future<Void> scheduledFuture; public DataTierTupleSource(AtomicRequestMessage aqr, RequestWorkItem workItem, ConnectorWork cwi, DataTierManagerImpl dtm, int limit) { this.aqr = aqr; this.workItem = workItem; this.cwi = cwi; this.dtm = dtm; this.limit = limit; Assertion.isNull(workItem.getConnectorRequest(aqr.getAtomicRequestID())); workItem.addConnectorRequest(aqr.getAtomicRequestID(), this); } void addWork() { futureResult = workItem.addWork(new Callable<AtomicResultsMessage>() { @Override public AtomicResultsMessage call() throws Exception { try { return getResults(); } finally { if (closing.get() && closed.compareAndSet(false, true)) { cwi.close(); } } } }, this, 100); } public List<?> nextTuple() throws TeiidComponentException, TeiidProcessingException { if (waitUntil > 0 && waitUntil > System.currentTimeMillis()) { if (!this.cwi.isDataAvailable()) { throw BlockedException.block(aqr.getAtomicRequestID(), "Blocking until", waitUntil); //$NON-NLS-1$ } this.waitUntil = 0; } while (true) { if (arm == null) { if (isDone()) { //sanity check return null; //TODO: could throw an illegal state exception } boolean partial = false; AtomicResultsMessage results = null; boolean noResults = false; try { if (futureResult != null || !aqr.isSerial()) { results = asynchGet(); } else { results = getResults(); } //check for update events if (index == 0 && this.dtm.detectChangeEvents()) { Command command = aqr.getCommand(); int commandIndex = 0; if (RelationalNodeUtil.isUpdate(command)) { long ts = System.currentTimeMillis(); checkForUpdates(results, command, dtm.getEventDistributor(), commandIndex, ts); } else if (command instanceof BatchedUpdateCommand) { long ts = System.currentTimeMillis(); BatchedUpdateCommand bac = (BatchedUpdateCommand)command; for (Command uc : bac.getUpdateCommands()) { checkForUpdates(results, uc, dtm.getEventDistributor(), commandIndex++, ts); } } } } catch (TranslatorException e) { errored = true; results = exceptionOccurred(e); partial = true; } catch (BlockedException e) { noResults = true; throw e; } catch (DataNotAvailableException e) { noResults = true; handleDataNotAvailable(e); continue; } finally { if (!noResults && results == null) { errored = true; } } receiveResults(results, partial); } if (index < arm.getResults().length) { if (limit-- == 0) { this.done = true; arm = null; return null; } return this.arm.getResults()[index++]; } arm = null; if (isDone()) { return null; } } } private void handleDataNotAvailable(DataNotAvailableException e) throws BlockedException { if (e.getWaitUntil() != null) { long timeDiff = e.getWaitUntil().getTime() - System.currentTimeMillis(); if (timeDiff <= 0) { //already met the time return; } if (e.isStrict()) { this.waitUntil = e.getWaitUntil().getTime(); } scheduleMoreWork(timeDiff); } else if (e.getRetryDelay() >= 0) { if (e.isStrict()) { this.waitUntil = System.currentTimeMillis() + e.getRetryDelay(); } scheduleMoreWork(e.getRetryDelay()); } else if (this.cwi.isDataAvailable()) { return; //no polling, but data is already available } else if (e.isStrict()) { //no polling, wait indefinitely this.waitUntil = Long.MAX_VALUE; } throw BlockedException.block(aqr.getAtomicRequestID(), "Blocking on DataNotAvailableException", aqr.getAtomicRequestID()); //$NON-NLS-1$ } private void scheduleMoreWork(long timeDiff) { if (scheduledFuture != null) { this.scheduledFuture.cancel(false); } scheduledFuture = workItem.scheduleWork(timeDiff); } private void checkForUpdates(AtomicResultsMessage results, Command command, EventDistributor distributor, int commandIndex, long ts) { if (!RelationalNodeUtil.isUpdate(command) || !(command instanceof ProcedureContainer)) { return; } ProcedureContainer pc = (ProcedureContainer)command; GroupSymbol gs = pc.getGroup(); Integer zero = Integer.valueOf(0); if (results.getResults().length <= commandIndex || zero.equals(results.getResults()[commandIndex].get(0))) { return; } Object metadataId = gs.getMetadataID(); if (metadataId == null) { return; } if (!(metadataId instanceof Table)) { if (metadataId instanceof TempMetadataID) { TempMetadataID tid = (TempMetadataID)metadataId; if (tid.getTableData().getModel() != null) { tid.getTableData().dataModified((Integer)results.getResults()[commandIndex].get(0)); } } return; } Table t = (Table)metadataId; t.setLastDataModification(ts); if (distributor != null) { distributor.dataModification(this.workItem.getDqpWorkContext().getVdbName(), this.workItem.getDqpWorkContext().getVdbVersion(), t.getParent().getName(), t.getName()); } } private AtomicResultsMessage asynchGet() throws BlockedException, TeiidProcessingException, TeiidComponentException, TranslatorException { if (futureResult == null) { addWork(); } if (!futureResult.isDone()) { throw BlockedException.block(aqr.getAtomicRequestID(), "Blocking on source query", aqr.getAtomicRequestID()); //$NON-NLS-1$ } FutureWork<AtomicResultsMessage> currentResults = futureResult; futureResult = null; AtomicResultsMessage results = null; try { results = currentResults.get(); if (results.getFinalRow() < 0) { addWork(); } } catch (CancellationException e) { throw new TeiidProcessingException(e); } catch (InterruptedException e) { throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30503, e); } catch (ExecutionException e) { if (e.getCause() instanceof TeiidProcessingException) { throw (TeiidProcessingException)e.getCause(); } if (e.getCause() instanceof TeiidComponentException) { throw (TeiidComponentException)e.getCause(); } if (e.getCause() instanceof TranslatorException) { throw (TranslatorException)e.getCause(); } if (e.getCause() instanceof RuntimeException) { throw (RuntimeException)e.getCause(); } //shouldn't happen throw new RuntimeException(e); } return results; } AtomicResultsMessage getResults() throws BlockedException, TeiidComponentException, TranslatorException { AtomicResultsMessage results = null; if (cancelAsynch) { return null; } running = true; try { if (!executed) { cwi.execute(); executed = true; } results = cwi.more(); } finally { running = false; } return results; } public boolean isQueued() { FutureWork<AtomicResultsMessage> future = futureResult; return !running && future != null && !future.isDone(); } public boolean isDone() { return done; } public boolean isRunning() { return running; } public void fullyCloseSource() { cancelFutures(); cancelAsynch = true; if (closing.compareAndSet(false, true)) { if (!done && !errored) { this.cwi.cancel(false); } workItem.closeAtomicRequest(this.aqr.getAtomicRequestID()); if (aqr.isSerial() || futureResult == null) { this.cwi.close(); } else { futureResult.addCompletionListener(new CompletionListener<AtomicResultsMessage>() { @Override public void onCompletion(FutureWork<AtomicResultsMessage> future) { if (running) { return; //-- let the other thread close } if (closed.compareAndSet(false, true)) { cwi.close(); } } }); } } } public boolean isCanceled() { return canceled; } public void cancelRequest() { this.canceled = true; this.cwi.cancel(true); cancelFutures(); } /** * @see TupleSource#closeSource() */ public void closeSource() { cancelFutures(); cancelAsynch = true; if (!explicitClose) { fullyCloseSource(); } } private void cancelFutures() { if (this.scheduledFuture != null) { this.scheduledFuture.cancel(true); this.scheduledFuture = null; } if (this.futureResult != null) { this.futureResult.cancel(false); } } AtomicResultsMessage exceptionOccurred(TranslatorException exception) throws TeiidComponentException, TeiidProcessingException { if(workItem.requestMsg.supportsPartialResults()) { AtomicResultsMessage emptyResults = new AtomicResultsMessage(new List[0]); emptyResults.setWarnings(Arrays.asList((Exception)exception)); emptyResults.setFinalRow(this.rowsProcessed); return emptyResults; } fullyCloseSource(); if (exception.getCause() instanceof TeiidComponentException) { throw (TeiidComponentException)exception.getCause(); } if (exception.getCause() instanceof TeiidProcessingException) { throw (TeiidProcessingException)exception.getCause(); } throw new TeiidProcessingException(QueryPlugin.Event.TEIID30504, exception, this.getConnectorName() + ": " + exception.getMessage()); //$NON-NLS-1$ } void receiveResults(AtomicResultsMessage response, boolean partial) { this.arm = response; this.scope = response.getScope(); if (this.scope != null) { this.aqr.getCommandContext().setDeterminismLevel(CachingTupleSource.getDeterminismLevel(this.scope)); } explicitClose |= !arm.supportsImplicitClose(); rowsProcessed += response.getResults().length; index = 0; if (response.getWarnings() != null) { for (Exception warning : response.getWarnings()) { SourceWarning sourceFailure = new SourceWarning(this.aqr.getModelName(), aqr.getConnectorName(), warning, partial); this.aqr.getCommandContext().addWarning(sourceFailure); } } if (response.getFinalRow() >= 0) { done = true; } } public AtomicRequestMessage getAtomicRequestMessage() { return aqr; } public String getConnectorName() { return this.aqr.getConnectorName(); } public boolean isTransactional() { return this.aqr.isTransactional(); } @Override public void onCompletion(FutureWork<AtomicResultsMessage> future) { if (!cancelAsynch) { workItem.moreWork(); //this is not necessary in some situations with DataNotAvailable } } public boolean isExplicitClose() { return explicitClose; } public Future<Void> getScheduledFuture() { return scheduledFuture; } }