/* * Copyright 2007 T-Rank AS * * 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 no.trank.openpipe.jdbc.store; import java.util.Iterator; import java.util.NoSuchElementException; import javax.sql.DataSource; import org.apache.ws.jaxme.sqls.SQLFactory; import no.trank.openpipe.api.PipelineException; import no.trank.openpipe.api.document.Document; import no.trank.openpipe.api.document.DocumentOperation; import no.trank.openpipe.api.document.DocumentProducer; import no.trank.openpipe.config.BeanValidator; import no.trank.openpipe.config.annotation.NotEmpty; import no.trank.openpipe.config.annotation.NotNull; /** * A "decorator" document producer, that tracks whether a document is new, updated or deleted using * {@link IdStateHolder}.<br/> * <b>Note:</b> Documents produced by {@link #getProducer()} must contain a field with a field value for a * field with name {@link #getIdFieldName()}. * * @version $Revision$ */ public class StateDocumentProducer implements DocumentProducer, TableDescription { private final DocumentProducer producer; @NotEmpty private String idFieldName; @NotNull private SQLFactory sqlFactory; @NotNull private DataSource dataSource; @NotEmpty private String tableName = "doc_store"; @NotEmpty private String idColumnName = "id"; @NotEmpty private String updColumnName = "updated"; private int idMaxLength = 256; private IdStateHolder idStateHolder; /** * Creates a document producer that decorates <tt>producer</tt>. * * @param producer the producer to decorate. */ public StateDocumentProducer(DocumentProducer producer) { if (producer == null) { throw new NullPointerException("producer cannot be null"); } this.producer = producer; } /** * Calls {@link #getProducer()}<tt>.prapare()</tt> and then {@link IdStateHolder#prepare()}. */ @Override public void init() { try { BeanValidator.validate(this); } catch (PipelineException e) { throw new RuntimeException(e); } producer.init(); if (idStateHolder == null) { idStateHolder = new IdStateHolder(dataSource, sqlFactory, this); } idStateHolder.prepare(); } /** * Calls {@link #getProducer()}<tt>.close()</tt> and then {@link IdStateHolder#commit()}. */ @Override public void close() { producer.close(); if (idStateHolder != null) { idStateHolder.commit(); } } /** * Calls {@link #getProducer()}<tt>.fail()</tt> and then {@link IdStateHolder#rollback()}. */ @Override public void fail() { producer.fail(); if (idStateHolder != null) { idStateHolder.rollback(); } } /** * Returns an iterator that first updates {@link Document#setOperation(String)} of all documents returned by * {@link #getProducer()}<tt>.iterator()</tt> to either {@link DocumentOperation#ADD_VALUE} or * {@link DocumentOperation#MODIFY_VALUE} based on the outcome of * {@link IdStateHolder#isUpdate(String)}. And then documents with id in * {@link IdStateHolder#findDeletedIds()} and {@link Document#setOperation(String) Doument.setOperation(} * {@link DocumentOperation#DELETE_VALUE}<tt>)</tt>. */ @Override public Iterator<Document> iterator() { return new StateDocumentIterator(producer.iterator(), idStateHolder, idFieldName); } public DocumentProducer getProducer() { return producer; } /** * Get in what field of the document the id can be found. * * @return the name of the id-field. */ public String getIdFieldName() { return idFieldName; } /** * Set in what field of the document the id can be found. * * @param idFieldName the name of the id-field. <em>Cannot</em> be <tt>null</tt> or <tt>""</tt>. */ public void setIdFieldName(String idFieldName) { this.idFieldName = idFieldName; } /** * {@inheritDoc} * Default value is <tt>"doc_store"</tt>. */ @Override public String getTableName() { return tableName; } /** * Sets the name of the table. * * @param tableName the name of the table. <em>Cannot</em> be <tt>null</tt> or <tt>""</tt>. */ public void setTableName(String tableName) { this.tableName = tableName; } /** * {@inheritDoc} * Default value is <tt>"id"</tt>. */ @Override public String getIdColumnName() { return idColumnName; } /** * Sets the name of the <tt>ID</tt> column. * * @param idColumnName the name of the <tt>ID</tt> column. <em>Cannot</em> be <tt>null</tt> or <tt>""</tt>. */ public void setIdColumnName(String idColumnName) { this.idColumnName = idColumnName; } /** * {@inheritDoc} * Default value is <tt>"updated"</tt>. */ @Override public String getUpdColumnName() { return updColumnName; } /** * Sets the name of the <tt>lastUpdated</tt> column. * * @param updColumnName the name of the <tt>lastUpdated</tt> column. <em>Cannot</em> be <tt>null</tt> or * <tt>""</tt>. */ public void setUpdColumnName(String updColumnName) { this.updColumnName = updColumnName; } /** * {@inheritDoc} * Default value is <tt>256</tt>. */ @Override public int getIdMaxLength() { return idMaxLength; } public void setIdMaxLength(int idMaxLength) { this.idMaxLength = idMaxLength; } /** * Gets the sql factory used to create the SQL needed. * * @return the sql factory used to create the SQL needed. */ public SQLFactory getSqlFactory() { return sqlFactory; } /** * Sets the sql factory used to create the SQL needed. * * @param sqlFactory the sql factory used to create the SQL needed. <em>Cannot</em> be <tt>null</tt>. */ public void setSqlFactory(SQLFactory sqlFactory) { this.sqlFactory = sqlFactory; } /** * Gets the datasource used for tracking document ids. * * @return the datasource used for tracking document ids. */ public DataSource getDataSource() { return dataSource; } /** * Sets the datasource used for tracking document ids. * * @param dataSource the datasource used for tracking document ids. <em>Cannot</em> be <tt>null</tt>. */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } private static class StateDocumentIterator implements Iterator<Document> { private final Iterator<Document> iterator; private final IdStateHolder idStateHolder; private Iterator<Document> delIterator; private String idFieldName; public StateDocumentIterator(Iterator<Document> iterator, IdStateHolder idStateHolder, String idFieldName) { this.iterator = iterator; this.idStateHolder = idStateHolder; this.idFieldName = idFieldName; } @Override public boolean hasNext() { if (iterator.hasNext()) { return true; } if (delIterator == null) { delIterator = createDelIterator(); } return delIterator.hasNext(); } private Iterator<Document> createDelIterator() { return new IdDocumentIterator(idStateHolder.findDeletedIds(), idFieldName); } @Override public Document next() { if (!hasNext()) { throw new NoSuchElementException(); } if (delIterator == null) { final Document doc = iterator.next(); if (idStateHolder.isUpdate(doc.getFieldValue(idFieldName))) { doc.setOperation(DocumentOperation.MODIFY_VALUE); } else { doc.setOperation(DocumentOperation.ADD_VALUE); } return doc; } return delIterator.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } } private static class IdDocumentIterator implements Iterator<Document> { private final Iterator<String> it; private final String idFieldName; public IdDocumentIterator(Iterator<String> it, String idFieldName) { this.it = it; this.idFieldName = idFieldName; } @Override public boolean hasNext() { return it.hasNext(); } @Override public Document next() { final Document doc = new Document(); doc.setOperation(DocumentOperation.DELETE_VALUE); doc.setFieldValue(idFieldName, it.next()); return doc; } @Override public void remove() { throw new UnsupportedOperationException(); } } }