Author: [log in to unmask] Date: Tue Dec 1 15:42:26 2015 New Revision: 3997 Log: Rewrite tag command so run range overlaps are handled correctly by using a disambiguation action (last created by default). Modified: java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/api/ConditionsRecord.java java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/cli/TagCommand.java Modified: java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/api/ConditionsRecord.java ============================================================================= --- java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/api/ConditionsRecord.java (original) +++ java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/api/ConditionsRecord.java Tue Dec 1 15:42:26 2015 @@ -8,6 +8,7 @@ import org.hps.conditions.database.ConditionsRecordConverter; import org.hps.conditions.database.Converter; import org.hps.conditions.database.Field; +import org.hps.conditions.database.MultipleCollectionsAction; import org.hps.conditions.database.Table; /** @@ -233,6 +234,33 @@ public final ConditionsRecordCollection sortedByUpdated() { return (ConditionsRecordCollection) this.sorted(new UpdatedComparator()); } + + /** + * Find a unique record using the selected action for disambiguating conditions with the same key. + * @param key the name of the key + * @param action the disambiguation action + * @return the unique conditions record or <code>null</code> if does not exist + */ + public ConditionsRecord findUniqueRecord(String key, MultipleCollectionsAction action) { + ConditionsRecord record = null; + ConditionsRecordCollection keyRecords = this.findByKey(key); + if (keyRecords.size() > 0) { + if (keyRecords.size() == 1) { + record = keyRecords.get(0); + } else { + if (action.equals(MultipleCollectionsAction.LAST_UPDATED)) { + record = sortedByUpdated().get(this.size() - 1); + } else if (action.equals(MultipleCollectionsAction.LAST_CREATED)) { + record = sortedByCreated().get(this.size() - 1); + } else if (action.equals(MultipleCollectionsAction.LATEST_RUN_START)) { + record = sortedByRunStart().get(this.size() - 1); + } else if (action.equals(MultipleCollectionsAction.ERROR)) { + throw new RuntimeException("Multiple ConditionsRecord object found for conditions key " + key + "."); + } + } + } + return record; + } } /** Modified: java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/cli/TagCommand.java ============================================================================= --- java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/cli/TagCommand.java (original) +++ java/branches/jeremy-dev/conditions/src/main/java/org/hps/conditions/cli/TagCommand.java Tue Dec 1 15:42:26 2015 @@ -1,10 +1,9 @@ package org.hps.conditions.cli; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; -import java.util.logging.Level; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; import java.util.logging.Logger; import org.apache.commons.cli.CommandLine; @@ -16,17 +15,16 @@ import org.hps.conditions.api.ConditionsTag; import org.hps.conditions.api.ConditionsTag.ConditionsTagCollection; import org.hps.conditions.api.DatabaseObjectException; -import org.hps.conditions.api.TableMetaData; import org.hps.conditions.api.TableRegistry; +import org.hps.conditions.database.DatabaseConditionsManager; import org.hps.conditions.database.MultipleCollectionsAction; +import org.lcsim.conditions.ConditionsManager.ConditionsNotFoundException; /** * Create a conditions system tag. * <p> * The tag groups together conditions records from the <i>conditions</i> database table with a run validity range that * is between a specified starting and ending run. - * <p> - * Tagging will not disambiguate overlapping conditions, which is done at run-time based on the current run number. * * @author Jeremy McCormick, SLAC */ @@ -41,20 +39,29 @@ * Defines command options. */ private static Options OPTIONS = new Options(); + + private MultipleCollectionsAction multipleCollectionsAction = MultipleCollectionsAction.LAST_CREATED; + + private static String getMultipleCollectionsActionString() { + StringBuffer sb = new StringBuffer(); + for (MultipleCollectionsAction action : MultipleCollectionsAction.values()) { + sb.append(action.name() + " "); + } + sb.setLength(sb.length() - 1); + return sb.toString(); + } /** * Define all command options. */ static { - OPTIONS.addOption(new Option("h", false, "Show help for tag command")); - OPTIONS.addOption(new Option("t", true, "Conditions tag name")); - OPTIONS.addOption(new Option("s", true, "Starting run number (required)")); - OPTIONS.getOption("s").setRequired(true); - OPTIONS.addOption(new Option("e", true, "Ending run number (default is unlimited)")); - OPTIONS.getOption("t").setRequired(true); - OPTIONS.addOption(new Option("m", true, - "MultipleCollectionsAction to use for disambiguation (default is LAST_CREATED)")); - OPTIONS.addOption(new Option("d", false, "Don't prompt before making tag (be careful!)")); + OPTIONS.addOption(new Option("h", "help", false, "Show help for tag command")); + OPTIONS.addOption(new Option("t", "tag", true, "Conditions tag name")); + OPTIONS.addOption(new Option("s", "run-start", true, "Starting run number (required)")); + OPTIONS.addOption(new Option("e", "run-end", true, "Ending run number (required)")); + OPTIONS.addOption(new Option("m", "multiple", true, + "set run overlap handling (" + getMultipleCollectionsActionString() + ")")); + OPTIONS.addOption(new Option("d", false, "Don't prompt before making tag (careful!)")); } /** @@ -103,6 +110,11 @@ } else { throw new RuntimeException("Missing required -t argument with the tag name."); } + + // Check if tag exists already. + if (getManager().getAvailableTags().contains(tagName)) { + throw new RuntimeException("The tag '" + tagName + "' already exists in the database."); + } // Starting run number (required). int runStart = -1; @@ -110,19 +122,16 @@ runStart = Integer.parseInt(commandLine.getOptionValue("s")); LOGGER.config("run start set to " + runStart); } else { - throw new RuntimeException("missing require -s argument with starting run number"); - } - - // Ending run number (max integer is default). - int runEnd = Integer.MAX_VALUE; + throw new RuntimeException("Missing required -s argument with starting run number."); + } + + // Ending run number (required). + int runEnd = -1; if (commandLine.hasOption("e")) { runEnd = Integer.parseInt(commandLine.getOptionValue("e")); LOGGER.config("run end set to " + runEnd); - } - - // Run end must be greater than or equal to run start. - if (runEnd < runStart) { - throw new IllegalArgumentException("runEnd < runStart"); + } else { + throw new RuntimeException("Missing required -e argument with starting run number."); } // Action for disambiguating overlapping collections (default is to use the most recent creation date). @@ -131,20 +140,24 @@ multipleCollectionsAction = MultipleCollectionsAction .valueOf(commandLine.getOptionValue("m").toUpperCase()); } - LOGGER.config("multiple collections action set tco " + multipleCollectionsAction); + LOGGER.config("run overlaps will be disambiguated using " + multipleCollectionsAction); // Whether to prompt before tagging (default is yes). boolean promptBeforeTagging = true; if (commandLine.hasOption("d")) { promptBeforeTagging = false; } - LOGGER.config("prompt before tagging: " + promptBeforeTagging); + LOGGER.config("prompt before tagging = " + promptBeforeTagging); // Conditions system configuration. this.getManager().setXmlConfig("/org/hps/conditions/config/conditions_database_no_svt.xml"); // Find all the applicable conditions records by their run number ranges. ConditionsRecordCollection tagConditionsRecordCollection = this.findConditionsRecords(runStart, runEnd); + + if (tagConditionsRecordCollection.size() == 0) { + throw new RuntimeException("No records found for tag."); + } LOGGER.info("found " + tagConditionsRecordCollection.size() + " conditions records for the tag"); @@ -152,8 +165,8 @@ final ConditionsTagCollection conditionsTagCollection = this.createConditionsTagCollection( tagConditionsRecordCollection, tagName); - LOGGER.info("created " + conditionsTagCollection.size() + " tag records ..." + '\n' + conditionsTagCollection); - + printConditionsRecords(tagConditionsRecordCollection); + // Prompt user to verify tag creation. boolean createTag = true; if (promptBeforeTagging) { @@ -178,68 +191,85 @@ LOGGER.info("done!"); } - - /** - * Find all the conditions records that are applicable for the given run range. - * <p> - * Overlapping run numbers in conditions with the same key are not disambiguated. - * This must be done in the user's job at runtime; usually the most recently created - * conditions record will be used if multiple one's are applicable to the current run. - * - * @param runStart the start run - * @param runEnd the end run (must be greater than or equal to <code>runStart</code>) - * @return the conditions records that fall in the run range + + /** + * Print information about conditions records in the tag to the log. + * + * @param collection the conditions tag collection + */ + private void printConditionsRecords(ConditionsRecordCollection records) { + StringBuffer sb = new StringBuffer(); + Set<String> keys = new TreeSet<String>(records.getConditionsKeys()); + for (String key : keys) { + ConditionsRecordCollection keyRecords = records.findByKey(key); + keyRecords.sortByKey(); + for (ConditionsRecord record : keyRecords) { + sb.append("conditions_id: " + record.getRowId() + ", name: " + record.getName() + ", collection_id: " + + record.getCollectionId() + ", run_start: " + record.getRunStart() + + ", run_end: " + record.getRunEnd() + ", notes: " + record.getNotes() + '\n'); + + } + } + LOGGER.info("including " + records.size() + " records in tag ..." + '\n' + sb.toString()); + } + + /** + * Scan through a run range to find conditions records for the tag. + * + * @param runStart the starting run number + * @param runEnd the ending run number + * @return the conditions records for the tag */ private ConditionsRecordCollection findConditionsRecords(final int runStart, final int runEnd) { - if (runStart > runEnd) { - throw new IllegalArgumentException("runStart > runEnd"); - } - if (runStart < 0) { - throw new IllegalArgumentException("invalid runStart: " + runStart); - } - if (runEnd < 0) { - throw new IllegalArgumentException("invalid runEnd: " + runEnd); - } - final Connection connection = this.getManager().getConnection(); - final ConditionsRecordCollection conditionsRecordCollection = new ConditionsRecordCollection(); - final TableMetaData tableMetaData = TableRegistry.getTableRegistry().findByTableName("conditions"); - PreparedStatement statement = null; - try { - /* - * SQL statement handles 3 cases: - * 1) condition's run_start in range - * 2) condition's run_end in range - * 3) condition's run_start and run_end enclose the range - */ - statement = connection - .prepareStatement("SELECT id FROM conditions WHERE (run_start >= ? and run_start <= ?) or (run_end >= ? and run_end <= ?)" - + " or (run_start <= ? and run_end >= ?)"); - statement.setInt(1, runStart); - statement.setInt(2, runEnd); - statement.setInt(3, runStart); - statement.setInt(4, runEnd); - statement.setInt(5, runStart); - statement.setInt(6, runEnd); - - final ResultSet resultSet = statement.executeQuery(); - while (resultSet.next()) { - final ConditionsRecord record = new ConditionsRecord(); - record.setConnection(connection); - record.setTableMetaData(tableMetaData); - record.select(resultSet.getInt(1)); - conditionsRecordCollection.add(record); - } - } catch (DatabaseObjectException | ConditionsObjectException | SQLException e) { - throw new RuntimeException(e); - } finally { + if (runStart < 0 ) { + throw new IllegalArgumentException("The run start " + runStart + " is invalid."); + } + if (runEnd < 0 ) { + throw new IllegalArgumentException("The run end " + runEnd + " is invalid."); + } + if (runStart > runEnd ) { + throw new IllegalArgumentException("The run start is greater than the run end."); + } + DatabaseConditionsManager dbManager = this.getManager(); + if (dbManager.isFrozen()) { + dbManager.unfreeze(); + } + if (!dbManager.getActiveTags().isEmpty()) { + dbManager.clearTags(); + } + final String detectorName = "HPS-dummy-detector"; + ConditionsRecordCollection tagRecords = new ConditionsRecordCollection(); + Set<Integer> ids = new HashSet<Integer>(); + for (int run = runStart; run <= runEnd; run++) { + LOGGER.info("loading run " + run); try { - if (statement != null) { - statement.close(); + dbManager.setDetector(detectorName, run); + } catch (ConditionsNotFoundException e) { + throw new RuntimeException(e); + } + ConditionsRecordCollection runRecords = dbManager.getConditionsRecords(); + Set<String> keys = runRecords.getConditionsKeys(); + LOGGER.fine("run has " + runRecords.size() + " conditions records"); + for (String key : keys) { + ConditionsRecord record = runRecords.findUniqueRecord(key, this.multipleCollectionsAction); + if (record == null) { + throw new RuntimeException("Missing expected unique condition record for " + key + "."); } - } catch (final SQLException e) { - e.printStackTrace(); - } - } - return conditionsRecordCollection; + if (!ids.contains(record.getRowId())) { + try { + LOGGER.fine("adding conditions to tag ..." + '\n' + record.toString()); + tagRecords.add(record); + ids.add(record.getRowId()); + } catch (ConditionsObjectException e) { + throw new RuntimeException(e); + } + } else { + LOGGER.fine("Conditions record with row id " + record.getRowId() + " is already in the tag."); + } + } + LOGGER.info("done processing run " + run); + } + LOGGER.info("Found " + tagRecords.size() + " conditions records for tag."); + return tagRecords; } }