/* * Copyright 2004-2009 the original author or authors. * * 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.compass.gps.device.ibatis; import java.sql.SQLException; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapSession; import com.ibatis.sqlmap.client.event.RowHandler; import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient; import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement; import org.compass.core.CompassException; import org.compass.core.CompassSession; import org.compass.core.mapping.ResourceMapping; import org.compass.gps.CompassGpsException; import org.compass.gps.device.support.parallel.AbstractParallelGpsDevice; import org.compass.gps.device.support.parallel.IndexEntitiesIndexer; import org.compass.gps.device.support.parallel.IndexEntity; import org.compass.gps.spi.CompassGpsInterfaceDevice; /** * A <code>SqlMapClient</code> device, provides support for iBatis 2 and the * <code>index</code> operation. The device holds a list of iBatis select * statements ids, executes them, and index the result. * * <p>The device must be initialized with a <code>SqlMapClient</code> instance. * When indexing the data, a <code>SqlMapSession</code> will be opened, and a * transaction will be started. The device will then execute the select * statement id, and use the iBatis <code>PaginatedList</code> to index the * data. * * <p>The page size for the <code>PaginatedList</code> can be controlled using * the <code>pageSize</code> property. * * <p>The select statment can have a parameter object associated with it. If one of * the select statements requires a parameter object, then the * <code>statementsParameterObjects</code> property must be set. It must have * the same size as the <code>selectStatementsIds</code>, and the matching * index of the <code>selectStatementsIds</code> should be set at the * <code>statementsParameterObjects</code> property. * * <p>As a replacement, the {@link #setIndexStatements(IndexStatement[])} can be used * which combines both a select statement id and its optional parameter. * * @author kimchy */ public class SqlMapClientGpsDevice extends AbstractParallelGpsDevice { private SqlMapClient sqlMapClient; private String[] selectStatementsIds; private Object[] statementsParameterObjects; private int pageSize = 200; public SqlMapClientGpsDevice() { } public SqlMapClientGpsDevice(String deviceName, SqlMapClient sqlMapClient, IndexStatement... statements) { setName(deviceName); this.sqlMapClient = sqlMapClient; setIndexStatements(statements); } public SqlMapClientGpsDevice(String deviceName, SqlMapClient sqlMapClient, String... selectStatementsIds) { this(deviceName, sqlMapClient, selectStatementsIds, null); } public SqlMapClientGpsDevice(String deviceName, SqlMapClient sqlMapClient, String[] selectStatementsIds, Object[] statementsParameterObjects) { setName(deviceName); this.sqlMapClient = sqlMapClient; this.selectStatementsIds = selectStatementsIds; this.statementsParameterObjects = statementsParameterObjects; } protected void doStart() throws CompassGpsException { if (sqlMapClient == null) { throw new IllegalArgumentException(buildMessage("Must set sqlMapClaient property")); } if (selectStatementsIds == null) { throw new IllegalArgumentException(buildMessage("Must set selectStatementsIds property")); } if (selectStatementsIds.length == 0) { throw new IllegalArgumentException( buildMessage("selectStatementsIds property must have at least one entry")); } if (statementsParameterObjects != null && statementsParameterObjects.length != selectStatementsIds.length) { throw new IllegalArgumentException( buildMessage("Once the statementsParameterObjects property is set, it must have the same length as the selectStatementsIds property")); } } protected IndexEntity[] doGetIndexEntities() throws CompassGpsException { ExtendedSqlMapClient extSqlMapClient = (ExtendedSqlMapClient) sqlMapClient; IndexEntity[] entities = new IndexEntity[selectStatementsIds.length]; for (int i = 0; i < selectStatementsIds.length; i++) { String statementId = selectStatementsIds[i]; MappedStatement statement = extSqlMapClient.getDelegate().getMappedStatement(statementId); if (statement == null) { throw new IllegalArgumentException("Failed to find statement for [" + statementId + "]"); } Class resultClass = statement.getResultMap().getResultClass(); ResourceMapping resourceMapping = ((CompassGpsInterfaceDevice) getGps()).getMappingForEntityForIndex(resultClass); if (resourceMapping == null) { throw new IllegalArgumentException("Failed to find mapping for class [" + resultClass.getName() + "]"); } Object parameterObject = null; if (statementsParameterObjects != null) { parameterObject = statementsParameterObjects[i]; } entities[i] = new SqlMapIndexEntity(resultClass.getName(), resourceMapping.getSubIndexHash().getSubIndexes(), statementId, parameterObject); } return entities; } protected IndexEntitiesIndexer doGetIndexEntitiesIndexer() { return new SqlMapIndexer(); } public SqlMapClient getSqlMapClient() { return sqlMapClient; } public void setSqlMapClient(SqlMapClient sqlMapClient) { this.sqlMapClient = sqlMapClient; } /** * Sets the given index statements that will be used. An index statement is a combination of the * statement id and a possible parameter. * * <p>Note, this method is used to replace the combination of {@link #setSelectStatementsIds(String[])} and * {@link #setStatementsParameterObjects(Object[])}. */ public void setIndexStatements(IndexStatement... statements) { selectStatementsIds = new String[statements.length]; statementsParameterObjects = new Object[statements.length]; for (int i = 0; i < statements.length; i++) { selectStatementsIds[i] = statements[i].getStatementId(); statementsParameterObjects[i] = statements[i].getParam(); } } /** * Sets the select statement ids that will be used to fetch data to be indexed. If parameters are required * for some of the statements, they can be passed using {@link #setStatementsParameterObjects(Object[])} with * the order similar to the statement ids. * * <p>Note, this method can be replaced with {@link #setIndexStatements(IndexStatement[])}. */ public void setSelectStatementsIds(String... statementsNames) { this.selectStatementsIds = statementsNames; } /** * Sets the select statement parameters for each select statment. The order is important and must match the * {@link #setSelectStatementsIds(String[])} order. * * <p>Note, the {@link #setIndexStatements(IndexStatement[])} can replce the combination of * {@link #setSelectStatementsIds(String[])} and {@link #setStatementsParameterObjects(Object[])}. */ public void setStatementsParameterObjects(Object[] statementsParameterObjects) { this.statementsParameterObjects = statementsParameterObjects; } /** * Sets the pagination/fetch size when iterating through the result set. */ public void setPageSize(int pageSize) { this.pageSize = pageSize; } private class SqlMapIndexer implements IndexEntitiesIndexer { public void performIndex(CompassSession session, IndexEntity[] entities) throws CompassException { for (IndexEntity entity : entities) { SqlMapIndexEntity indexEntity = (SqlMapIndexEntity) entity; SqlMapSession sqlMapSession = getSqlMapClient().openSession(); try { sqlMapSession.startTransaction(); if (log.isDebugEnabled()) { log.debug(buildMessage("Indexing select statement id [" + indexEntity.getStatementId() + "]")); } sqlMapSession.queryWithRowHandler(indexEntity.getStatementId(), indexEntity.getParam(), new SqlMapClientGpsDeviceRowHandler(indexEntity, session, pageSize)); session.evictAll(); sqlMapSession.commitTransaction(); } catch (SQLException e) { throw new SqlMapGpsDeviceException("Failed to fetch paginated list for statement [" + indexEntity.getStatementId() + "]", e); } finally { try { try { sqlMapSession.endTransaction(); } catch (Exception e) { log.warn(buildMessage("Failed to close sqlMap session, ignoring"), e); } } finally { sqlMapSession.close(); } } } } } public class SqlMapClientGpsDeviceRowHandler implements RowHandler { private SqlMapIndexEntity indexEntity; private CompassSession session; private int pageSize; private int pageCount = 0; private int currentItem = 1; public SqlMapClientGpsDeviceRowHandler(SqlMapIndexEntity indexEntity, CompassSession session, int pageSize) { this.session = session; this.pageSize = pageSize; this.indexEntity = indexEntity; } public void handleRow(Object o) { session.create(o); if (currentItem == pageSize) { if (log.isTraceEnabled()) { log.trace(buildMessage("Indexing [" + indexEntity.getName() + "] page number [" + pageCount + "]")); } session.evictAll(); pageCount++; currentItem = 0; } currentItem++; } } }