Java tutorial
/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.module.dataimporttool; import static ch.lambdaj.Lambda.on; import static ch.lambdaj.Lambda.sort; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.ibatis.jdbc.SQL; import org.openmrs.api.context.Context; import org.hibernate.Session; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.module.dataimporttool.api.DataImportToolService; import org.openmrs.module.dataimporttool.dmt.dao.DAOFactory; import org.openmrs.module.dataimporttool.dmt.dao.DatabaseUtil; import org.openmrs.module.dataimporttool.dmt.helper.DAOTypes; import org.openmrs.module.dataimporttool.dmt.helper.EventCodeContants; import org.openmrs.module.dataimporttool.dmt.helper.MatchConstants; import org.openmrs.module.dataimporttool.dmt.helper.ProcessPhases; import org.openmrs.module.dataimporttool.dmt.helper.ProcessStatuses; import org.openmrs.module.dataimporttool.dmt.helper.SystemException; import org.openmrs.module.dataimporttool.dmt.util.DatatypeEnforcer; import org.openmrs.module.dataimporttool.dmt.util.ProcessReader;//To be refactored. import org.openmrs.module.dataimporttool.dmt.util.TupleTree; import org.openmrs.module.dataimporttool.dmt.util.log.Event; import org.openmrs.module.dataimporttool.dmt.util.log.EventCode; import org.openmrs.module.dataimporttool.dmt.util.log.Info; import org.openmrs.module.dataimporttool.dmt.util.log.LogIt; import org.openmrs.module.dataimporttool.matchingschema.MatchType; import org.openmrs.module.dataimporttool.matchingschema.ReferenceType; import org.openmrs.module.dataimporttool.matchingschema.TupleType; import org.openmrs.module.dataimporttool.matchingschema.ValueMatchType; /** * This manager is responsible to generate SQL queries to either SELECT or * INSERT data from source into target databases * */ public class TranslationManager implements LogIt { private TupleTree tree; private DatabaseUtil sourceDAO; private DatabaseUtil targetDAO; private DatatypeEnforcer de; protected final Log log = LogFactory.getLog(this.getClass()); private EventCode eventCode; private boolean skip;// this variable indicates whether or not a tuple must // return an insert query or an empty string. private final DataImportTool config = Context.getService(DataImportToolService.class).getDataImportTool(); private int processCount, treeCount, totalTreeNo, currTupleId; private boolean firstRun = true; /** * Parameterized constructor * * @param tree */ public TranslationManager(TupleTree tree) { this.tree = tree; try { sourceDAO = DAOFactory.getInstance().getDAO(DAOTypes.SOURCE); targetDAO = DAOFactory.getInstance().getDAO(DAOTypes.TARGET); de = new DatatypeEnforcer(); eventCode = new EventCode(); } catch (SystemException e) { e.printStackTrace(); } } /** * This method starts the process of translation * * @return * @throws SystemException */ public boolean execute() throws SystemException { // log start of process writeSimpleInfoLog(null, eventCode.getString(EventCodeContants.SEPARATOR)); writeSimpleInfoLog(ProcessPhases.EXECUTION, eventCode.getString(EventCodeContants.INF001)); writeSimpleInfoLog(null, eventCode.getString(EventCodeContants.SEPARATOR)); // reset processing point if config says so if (config.getResetProcess()) { ProcessReader.getInstance().recordProcess(0, //To be re designed. Calendar.getInstance().getTime(), ProcessStatuses.RESET); } // sets the process position to start. Default 0 processCount = 0; try { read(tree, null); } catch (SystemException ex) { ex.printStackTrace(); targetDAO.rollback(); // record current process as failed ProcessReader.getInstance().recordProcess(processCount, //To be redesigned Calendar.getInstance().getTime(), ProcessStatuses.FAILED); throw new SystemException( "An error occured durring translation/execution phase while processing tuple # " + currTupleId); } // log end of process logEndOfProcess(); return true; } /** * This method traverses the tree of tuples recursively and performs the * translation * * @param t * @param parentUUID * @param parentCurr * @param parentTop * @throws SystemException */ private void read(final TupleTree t, final String parentUUID) throws SystemException { // how many tuples? // select from source using the reference of the target side PKs // r_reference currTupleId = t.getHead().getId();// the current tuple ID String selectCurrsQuery = this.selectCurrs(t); List<List<Object>> currs = sourceDAO.executeQuery(selectCurrsQuery); // in case this is the first run, start from last run final point int currIndex = 0; if (firstRun) { currIndex = processCount; totalTreeNo = currs.size(); firstRun = false; } for (; currIndex < currs.size(); currIndex++) { // init transaction from root if (t.getParent() == null) { targetDAO.setSavePoint();// rollback should go till this point } Object top = null; final Object curr = currs.get(currIndex).get(0); t.setCurr(curr); // stop this tree here if curr was not found if (curr == null) { continue; } // keep the UUID of current insert final String uuid = UUID.randomUUID().toString(); // build insert statement based on translation logic final String insertTupleQuery = insertTuple(t, uuid, currIndex); // TODO remove print synchronized (System.out) { System.out.println( processCount + " ---------- " + t.getHead().getId() + " : " + t.getHead().getTable() + " - " + t.getHead().getTerminology() + " > CURR : " + t.getCurr() + " -------------"); System.out.println(insertTupleQuery); } // stop here if query was skipped if (insertTupleQuery.isEmpty()) { continue; } final List<List<Object>> tops = targetDAO.executeUpdate(insertTupleQuery); top = tops.get(0).get(0); t.setTop(top);// set top value to tuple // do all the same process for each child for (TupleTree eachTree : t.getSubTrees()) { read(eachTree, uuid); } // commit a transaction from root if (t.getParent() == null) { if (config.getAllowCommit()) { targetDAO.commit(); } // increment process counter for each tree processCount++; treeCount++; // stop if reached the limit of executions if (treeCount == config.getTreeLimit()) { // record current process as paused ProcessReader.getInstance().recordProcess(processCount, //NOTE: To be redesigned. Calendar.getInstance().getTime(), ProcessStatuses.PAUSED); break; } } } // close DAOs if (t.getParent() == null) { try { targetDAO.close(); sourceDAO.close(); } catch (Exception e) { e.printStackTrace(); } // record end of process if (processCount == totalTreeNo) { // record current process as completed ProcessReader.getInstance().recordProcess(0/* NOTE: To be redesigned * next time start * one tree ahead */, Calendar.getInstance().getTime(), ProcessStatuses.COMPLETED); } } } /** * This method generates insert query based on translation logic * * @param tuple * @param uuid * @param curr * @param top * @param currIndex * @return * @throws SystemException */ private synchronized String insertTuple(final TupleTree tree, final String uuid, final int currIndex) throws SystemException { skip = false;// reset skip to false String query = new SQL() { { TupleType tuple = tree.getHead(); INSERT_INTO(tuple.getTable()); // access matches of tuple for (MatchType match : tuple.getMatches()) { // if match default value is auto increment, skip it if (match.getDefaultValue().equals(MatchConstants.AI)) { continue; } String selectQuery = null;// keep the composed select query // 1. If a match doesnt have the right side, it should // insert the default value if (match.getRight() == null) { // 8. TOP Should use the PK value of the parent tuple String defaultValue = match.getDefaultValue().toString(); if (defaultValue.startsWith(MatchConstants.TOP)) { // compute the type of top int topType = (defaultValue.length() == 3) ? 1 : Integer.valueOf(defaultValue.substring(3)); TupleTree parentTree = tree;// parent tree is equal // to current tree for // now for (int i = 0; i < topType; i++) { parentTree = parentTree.getParent();// back to // the // desired // parent } VALUES(match.getLeft().getColumn(), targetDAO.cast(parentTree.getTop())); } // 14. NOW Should use the current system datetime else if (match.getDefaultValue().equals(MatchConstants.NOW)) { VALUES(match.getLeft().getColumn(), "NOW()"); } else { // use default value VALUES(match.getLeft().getColumn(), sourceDAO.cast(match.getDefaultValue())); } } else { selectQuery = selectMatch(match, tree);// generate final List<List<Object>> results = sourceDAO.executeQuery(selectQuery);// execute select // statement // in case the database return more than one result, use // the one at curr index int rowIndex = (results.size() > 1) ? currIndex : 0; Object value = null; try { value = results.get(rowIndex).get(0);// gets // the // database // result } catch (java.lang.IndexOutOfBoundsException ex) { ex.printStackTrace(); throw new SystemException("The # of results of the query: \"" + selectQuery + "\" is not equal to the # of results of its CURRS. Found " + results.size() + " results but expected " + (currIndex + 1) + " results or more. In match: " + match.getId()); } // in case the default value is AI_SKIP_TRUE or // AI_SKIP_FALSE if (match.getDefaultValue().equals(MatchConstants.AI_SKIP_TRUE) || match.getDefaultValue().equals(MatchConstants.AI_SKIP_FALSE)) { boolean boolValue = Boolean.valueOf(value.toString()); // 12.AI/SKIP/TRUE Should skip the entire tuple if // the value selected in the right side of the match // is TRUE. Must use auto increment otherwise if (match.getDefaultValue().equals(MatchConstants.AI_SKIP_TRUE) && boolValue) { skip = true;// indicate that all the tuple must // be skipped break; } // 13. AI/SKIP/FALSE Should skip the entire tuple // if the value selected in the right side of the // match is FALSE. Must use auto increment // otherwise. else if (match.getDefaultValue().equals(MatchConstants.AI_SKIP_FALSE) && !boolValue) { skip = true;// indicate that the entire tuple // must // be skipped break; } else { continue; } } // 5. If default value of a match is SKIP, the entire // tuple must be skipped if the match select doesnt // find any value. // generate select statement else if (match.getDefaultValue().equals(MatchConstants.SKIP) && value == null) { skip = true;// indicate that all the tuple must be // skipped break; } // 14. NOW Should use the current system datetime else if (match.getDefaultValue().equals(MatchConstants.NOW) && value == null) { VALUES(match.getLeft().getColumn(), "NOW()"); } // 4. If right side of the match is not required, it // must insert default value in case the right side // select doesnt find any value else if (match.getRight().isIsRequired().equals(MatchConstants.NO) && value == null) { // use default value VALUES(match.getLeft().getColumn(), sourceDAO.cast(match.getDefaultValue())); } // 7. If there is a value match in a match, it must // insert the value that the value match points to else if (!match.getValueMatchId().equals(MatchConstants.NA)) { Map<String, String> valueMatchGroup = ValueMatchType.valueMatches .get(Integer.valueOf(match.getValueMatchId().toString())); // log an error if value match group doesn't exist if (valueMatchGroup == null) { throw new SystemException( "An error ocurred during translation phase while processing value match group in match with id: " + match.getId() + ".\n Couldn't find group for id: " + value); } String valueMatch = valueMatchGroup.get(value.toString().toLowerCase()); // log an error if value match doesn't exist if (valueMatch == null) { // get the value of UNMATCHED match in case the // value is not in the group valueMatch = valueMatchGroup.get(MatchConstants.UNMATCHED.toLowerCase()); if (valueMatch == null) { throw new SystemException( "An error ocurred during translation phase while processing value match in match with id: " + match.getId() + ".\n Couldn't find match for value: " + value); } // SKIP entire tuple if value match is SKIP if (valueMatch.equalsIgnoreCase(MatchConstants.SKIP)) { skip = true;// indicate that all the tuple // must be skipped break; } } VALUES(match.getLeft().getColumn(), sourceDAO.cast(valueMatch)); } else { VALUES(match.getLeft().getColumn(), de.enforce(match.getLeft().getDatatype(), value)); } } } // foreign key columns if (!skip) { for (ReferenceType reference : tuple.getReferences().values()) { // check if reference is direct if (reference.getReferencee().getTable().equalsIgnoreCase(tuple.getTable())) { String referencedValue = reference.getReferencedValue().toString(); // 8. TOP Should use the PK value of the parent // tuple if (referencedValue.startsWith(MatchConstants.TOP)) { // compute the type of top TupleTree parentTree = tree;// parent tree is // equal to current // tree for now // find the parent while (true) { parentTree = parentTree.getParent();// back // to // the // desired // parent if (reference.getReferenced().getTable() .equalsIgnoreCase(parentTree.getHead().getTable())) { break; } } VALUES(reference.getReferencee().getColumn(), targetDAO.cast(parentTree.getTop())); } else { // use default value VALUES(reference.getReferencee().getColumn(), targetDAO.cast(referencedValue)); } } } // metadata VALUES("creator", sourceDAO.cast(1)); VALUES("date_created", "NOW()"); if (!tuple.getTable().equalsIgnoreCase("PROVIDER")) VALUES("voided", sourceDAO.cast(0)); // avoid PATIENT table if (!tuple.getTable().equalsIgnoreCase("PATIENT")) VALUES("uuid", sourceDAO.cast(uuid)); } } }.toString(); // check whether or not the query was skipped if (skip) return ""; return query; } /** * This method generates and returns SQL query that should be executed in * the source database to retrieve CURRS of tuple * * @param tree * @return * @throws SystemException */ private String selectCurrs(final TupleTree tree) throws SystemException { return new SQL() { { TupleType tuple = tree.getHead(); // the select should be constructed based on R-References of one // of the PKs match of the tuple MatchType pkMatch = findPkMatch(tuple); boolean isFirstDirectReference = true;// used to flag whether or // not the reference String prevReferencedTable = null;//keep the referenced table of previous loop // is the first direct, in case there are many direct references // construct the select query using the L-References of the PK // match of the tuple List<ReferenceType> references = new ArrayList<ReferenceType>(pkMatch.getReferences().values()); references = sort(references, on(ReferenceType.class).getId()); for (ReferenceType reference : references) { String referencedTable = reference.getReferenced().getTable(); String referencedColumn = reference.getReferenced().getColumn(); Object referencedValue = reference.getReferencedValue().toString(); // set the right referenced value if (referencedValue.equals(MatchConstants.CURR)) { referencedValue = tree.getParent().getCurr(); } else if (referencedValue.equals(MatchConstants.CURR2)) { referencedValue = tree.getParent().getParent().getCurr(); } else if (referencedValue.equals(MatchConstants.CURR3)) { referencedValue = tree.getParent().getParent().getParent().getCurr(); } else if (referencedValue.equals(MatchConstants.CURR4)) { referencedValue = tree.getParent().getParent().getParent().getParent().getCurr(); } //split referenced value if contais >> or << String[] referencedValues = null; final CharSequence OR = MatchConstants.OR; if (referencedValue.toString().contains(OR)) { referencedValues = referencedValue.toString().split(MatchConstants.OR); } // check whether the reference is direct or indirect if (reference.getPredecessor().equals(Integer.valueOf(0))) { // start from reference value if exists if (reference.getReferencee() != null) { // the referencee should be used in the result set if (isFirstDirectReference) { // select from, only // for first // reference SELECT(reference.getReferencee().getTable() + "." + reference.getReferencee().getColumn()); FROM(reference.getReferencee().getTable()); isFirstDirectReference = false;// no longer // first direct } } else { // the referenced should be used in the result set if (isFirstDirectReference) { // select from, only // for first // reference SELECT(referencedTable + "." + referencedColumn); FROM(referencedTable); isFirstDirectReference = false;// no longer // first direct } //check whether or not the current referenced table is equal to the previous else if (!referencedTable.equalsIgnoreCase(prevReferencedTable)) { FROM(referencedTable); } } if (referencedValue.equals(MatchConstants.ALL)) { break;// no more references must be processed } //in care there is >> condition if (referencedValues != null && referencedValues.length > 1) { String orCondition = ""; for (int i = 0; i < referencedValues.length; i++) { if (references.indexOf(reference) != 0 && i == 0) AND();//in case it's not the first reference use AND with separated braces orCondition += referencedTable + "." + referencedColumn + " = " + sourceDAO.cast(referencedValues[i].trim()); if (i < referencedValues.length - 1) orCondition += " OR "; } WHERE(orCondition); } else { WHERE(referencedTable + "." + referencedColumn + " = " + sourceDAO.cast(referencedValue)); } if (isFirstDirectReference) // select only for first // reference isFirstDirectReference = false; } else { String referenceeTable = reference.getReferencee().getTable(); String referenceeColumn = reference.getReferencee().getColumn(); // check whether or not the current referenced table is // equal to the previous if (!referencedTable.equalsIgnoreCase(prevReferencedTable)) { FROM(referencedTable); } WHERE(referenceeTable + "." + referenceeColumn + " = " + referencedTable + "." + referencedColumn); // in case the referenced value is not EQUALS if (!referencedValue.equals(MatchConstants.EQUALS)) { //in care there is >> condition if (referencedValues != null && referencedValues.length > 1) { String orCondition = null; for (int i = 0; i < referencedValues.length; i++) { if (references.indexOf(reference) != 0 && i == 0) AND();//in case it's not the first reference use AND with separated braces orCondition += referencedTable + "." + referencedColumn + " = " + sourceDAO.cast(referencedValues[i].trim()); if (i < referencedValues.length - 1) orCondition += " OR "; } WHERE(referenceeTable + "." + referenceeColumn + " = " + orCondition); } else { WHERE(referenceeTable + "." + referenceeColumn + " = " + sourceDAO.cast(referencedValue)); } } } prevReferencedTable = referencedTable;//keep the referenced table for comparison in the next loop } } }.toString(); } /** * This method generates and returns SQL query to be executed in the source * database to retrieve the data to be used as the value of the match, while * building the insert query * * @param match * @param curr * @return */ private String selectMatch(final MatchType match, final TupleTree tree) throws SystemException { return new SQL() { { SELECT(match.getRight().getTable() + "." + match.getRight().getColumn()); FROM(match.getRight().getTable()); // in case there are references build WHERE clause for (ReferenceType reference : match.getReferences().values()) { String referencedTable = reference.getReferenced().getTable(); String referencedColumn = reference.getReferenced().getColumn(); Object referencedValue = reference.getReferencedValue().toString(); String referenceeTable = null; String referenceeColumn = null; // set the right referenced value if (referencedValue.equals(MatchConstants.CURR)) { referencedValue = tree.getCurr(); } else if (referencedValue.equals(MatchConstants.CURR2)) { referencedValue = tree.getParent().getCurr(); } else if (referencedValue.equals(MatchConstants.CURR3)) { referencedValue = tree.getParent().getParent().getCurr(); } else if (referencedValue.equals(MatchConstants.CURR4)) { referencedValue = tree.getParent().getParent().getParent().getCurr(); } // in case the referencee exist if (reference.getReferencee() != null) { referenceeTable = reference.getReferencee().getTable(); referenceeColumn = reference.getReferencee().getColumn(); FROM(referencedTable); WHERE(referenceeTable + "." + referenceeColumn + " = " + referencedTable + "." + referencedColumn); // in case the referenced value is not EQUALS if (!referencedValue.equals(MatchConstants.EQUALS)) { WHERE(referencedTable + "." + referencedColumn + " = " + sourceDAO.cast(referencedValue)); } } else { WHERE(referencedTable + "." + referencedColumn + " = " + sourceDAO.cast(referencedValue)); } } } }.toString(); } /** * This method finds and returns the PK match of the tuple * * @param tuple * @return */ private MatchType findPkMatch(TupleType tuple) { MatchType pkMatch = null; // find the PK match of the tuple for (MatchType match : tuple.getMatches()) { if (match.isPk().equals(MatchConstants.YES)) { pkMatch = match; break; } } return pkMatch; } /** * Writes a simple text report containing the phase and message * * @param phase * @param text */ public void writeSimpleInfoLog(final String phase, final String text) { Event event = new Info(); event.setFase(phase); event.setDescricao(text); log.warn(event); } /** * Write the log report at the end of the validation process */ public void logEndOfProcess() { writeSimpleInfoLog(null, eventCode.getString(EventCodeContants.SEPARATOR)); writeSimpleInfoLog(ProcessPhases.EXECUTION, eventCode.getString(EventCodeContants.INF002)); writeSimpleInfoLog(null, eventCode.getString(EventCodeContants.SEPARATOR)); writeSimpleInfoLog(null, treeCount + " " + eventCode.getString(EventCodeContants.INF007)); } }