/* * (c) Copyright 2010-2011 by Volker Bergmann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is permitted under the terms of the * GNU General Public License (GPL). * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.databene.benerator.engine.statement; import java.util.List; import org.databene.benerator.composite.ComponentAndVariableSupport; import org.databene.benerator.composite.GeneratorComponent; import org.databene.benerator.engine.BeneratorContext; import org.databene.benerator.engine.Statement; import org.databene.benerator.factory.ComplexTypeGeneratorFactory; import org.databene.commons.ConfigurationError; import org.databene.commons.Context; import org.databene.commons.ErrorHandler; import org.databene.jdbacl.identity.IdentityModel; import org.databene.jdbacl.identity.IdentityProvider; import org.databene.jdbacl.identity.KeyMapper; import org.databene.jdbacl.identity.NoIdentity; import org.databene.jdbacl.model.DBTable; import org.databene.model.data.ComplexTypeDescriptor; import org.databene.model.data.Entity; import org.databene.model.data.InstanceDescriptor; import org.databene.model.data.ReferenceDescriptor; import org.databene.model.data.Uniqueness; import org.databene.platform.db.DBSystem; import org.databene.script.Expression; import org.databene.script.expression.ExpressionUtil; import org.databene.webdecs.DataContainer; import org.databene.webdecs.DataIterator; import org.databene.webdecs.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link Statement} that transcodes a database table.<br/><br/> * Created: 08.09.2010 16:23:56 * @since 0.6.4 * @author Volker Bergmann */ public class TranscodeStatement extends SequentialStatement implements CascadeParent { private static final Logger LOGGER = LoggerFactory.getLogger(TranscodeStatement.class); Expression<ComplexTypeDescriptor> typeExpression; Expression<DBSystem> sourceEx; Expression<String> selectorEx; Expression<DBSystem> targetEx; Expression<Long> pageSizeEx; Expression<ErrorHandler> errorHandlerEx; TranscodingTaskStatement parent; DBSystem source; private DBSystem target; private Entity currentEntity; public TranscodeStatement(MutatingTypeExpression typeExpression, TranscodingTaskStatement parent, Expression<DBSystem> sourceEx, Expression<String> selectorEx, Expression<DBSystem> targetEx, Expression<Long> pageSizeEx, Expression<ErrorHandler> errorHandlerEx) { this.typeExpression = cache(typeExpression); this.parent = parent; this.sourceEx = sourceEx; this.selectorEx = selectorEx; this.targetEx = targetEx; this.pageSizeEx = pageSizeEx; this.errorHandlerEx = errorHandlerEx; this.currentEntity = null; } @Override public boolean execute(BeneratorContext context) { DBSystem target = targetEx.evaluate(context); Long pageSize = ExpressionUtil.evaluate(pageSizeEx, context); if (pageSize == null) pageSize = 1L; transcodeTable(getSource(context), target, pageSize, context); return true; } public KeyMapper getKeyMapper() { return parent.getKeyMapper(); } public IdentityProvider getIdentityProvider() { return parent.getIdentityProvider(); } public Entity currentEntity() { return currentEntity; } public ComplexTypeDescriptor getType(DBSystem db, BeneratorContext context) { return typeExpression.evaluate(context); } public DBSystem getSource(BeneratorContext context) { if (source == null) source = sourceEx.evaluate(context); return source; } public DBSystem getTarget(BeneratorContext context) { if (target == null) target = targetEx.evaluate(context); return target; } public boolean needsNkMapping(String tableName) { return parent.needsNkMapping(tableName); } // helper methods -------------------------------------------------------------------------------------------------- private void transcodeTable(DBSystem source, DBSystem target, long pageSize, BeneratorContext context) { KeyMapper mapper = getKeyMapper(); ComplexTypeDescriptor type = typeExpression.evaluate(context); IdentityModel identity = getIdentityProvider().getIdentity(type.getName(), false); String tableName = type.getName(); LOGGER.info("Starting transcoding of " + tableName + " from " + source.getId() + " to " + target.getId()); // iterate rows String selector = ExpressionUtil.evaluate(selectorEx, context); DataSource<Entity> iterable = source.queryEntities(tableName, selector, context); List<GeneratorComponent<Entity>> generatorComponents = ComplexTypeGeneratorFactory.createMutatingGeneratorComponents(type, Uniqueness.NONE, context); ComponentAndVariableSupport<Entity> cavs = new ComponentAndVariableSupport<Entity>( tableName, generatorComponents, context); cavs.init(context); DataIterator<Entity> iterator = iterable.iterator(); mapper.registerSource(source.getId(), source.getConnection()); long rowCount = 0; DataContainer<Entity> container = new DataContainer<Entity>(); while ((container = iterator.next(container)) != null) { Entity sourceEntity = container.getData(); Object sourcePK = sourceEntity.idComponentValues(); boolean mapNk = parent.needsNkMapping(tableName); String nk = null; if (mapNk) nk = mapper.getNaturalKey(source.getId(), identity, sourcePK); Entity targetEntity = new Entity(sourceEntity); cavs.apply(targetEntity, context); Object targetPK = targetEntity.idComponentValues(); transcodeForeignKeys(targetEntity, source, context); mapper.store(source.getId(), identity, nk, sourcePK, targetPK); target.store(targetEntity); LOGGER.debug("transcoded {} to {}", sourceEntity, targetEntity); cascade(sourceEntity, context); rowCount++; if (rowCount % pageSize == 0) target.flush(); } target.flush(); LOGGER.info("Finished transcoding " + source.countEntities(tableName) + " rows of table " + tableName); } private void cascade(Entity sourceEntity, BeneratorContext context) { this.currentEntity = sourceEntity; executeSubStatements(context); this.currentEntity = null; } private void transcodeForeignKeys(Entity entity, DBSystem source, Context context) { ComplexTypeDescriptor tableDescriptor = entity.descriptor(); for (InstanceDescriptor component : tableDescriptor.getParts()) { if (component instanceof ReferenceDescriptor) { ReferenceDescriptor fk = (ReferenceDescriptor) component; String refereeTableName = fk.getTargetType(); Object sourceRef = entity.get(fk.getName()); if (sourceRef != null) { IdentityProvider identityProvider = parent.getIdentityProvider(); IdentityModel sourceIdentity = identityProvider.getIdentity(refereeTableName, false); if (sourceIdentity == null) { DBTable refereeTable = source.getDbMetaData().getTable(refereeTableName); sourceIdentity = new NoIdentity(refereeTable.getName()); identityProvider.registerIdentity(sourceIdentity, refereeTableName); } boolean needsNkMapping = parent.needsNkMapping(refereeTableName); if (sourceIdentity instanceof NoIdentity && needsNkMapping) throw new ConfigurationError("No identity defined for table " + refereeTableName); KeyMapper mapper = parent.getKeyMapper(); Object targetRef; if (needsNkMapping) { String sourceRefNK = mapper.getNaturalKey(source.getId(), sourceIdentity, sourceRef); targetRef = mapper.getTargetPK(sourceIdentity, sourceRefNK); } else { targetRef = mapper.getTargetPK(source.getId(), sourceIdentity, sourceRef); } if (targetRef == null) { String message = "No mapping found for " + source.getId() + '.' + refereeTableName + "#" + sourceRef + " referred in " + entity.type() + "(" + fk.getName() + "). " + "Probably has not been in the result set of the former '" + refereeTableName + "' nk query."; getErrorHandler(context).handleError(message); } entity.set(fk.getName(), targetRef); } } } } /* @Override public void merge(DBSystem source, DBSystem target, int pageSize, KeyMapper mapper, Context context) { String activity = "Merging " + name + " from " + source.getId() + " to " + target.getId(); startActivity(activity); HeavyweightIterator<Object[]> nkIterator = createNkPkIterator(source, mapper, context); Set<String> sourceNKs = new HashSet<String>(); try { while (nkIterator.hasNext()) { Object[] row = nkIterator.next(); String nk = String.valueOf(row[0]); sourceNKs.add(nk); Object sourceId = extractPK(row); Entity sourceEntity = source.queryEntityById(name, sourceId); Object targetId = mapper.getTargetPK(this, nk); if (targetId == null) { handleNKNotFound(nk, name, source, target); continue; } else { Entity targetEntity = target.queryEntityById(name, targetId); String message = checkEquivalence(sourceEntity, targetEntity, source, nk, mapper); if (message != null) handleNonEquivalence(message, source.getId(), sourceEntity); } // TODO v1.1 store in target if there is a rule one day mapper.store(source, this, nk, sourceId, targetId); } } finally { IOUtil.close(nkIterator); } target.flush(); endActivity(activity, source.countEntities(name)); } */ }