/******************************************************************************* * Copyright (c) 2013 Arapiki Solutions Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * psmith - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.model.impl; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import com.buildml.model.FatalBuildStoreError; import com.buildml.model.IBuildStore; import com.buildml.model.ISlotTypes; import com.buildml.model.ISlotTypes.SlotDetails; import com.buildml.utils.errors.ErrorCode; import com.buildml.utils.string.BuildStoreUtils; /** * A class for managing slots. This is a "hidden" BuildStore manager. A reference * to this class can be obtained from a BuildStore via the getSlotMgr() method, * although there's no public-facing interface where client can invoke methods. * Instead, only the other BuildStore managers are allowed to access the methods. * * All the methods in this class should be package-private, or private. * * @author Peter Smith <psmith@arapiki.com> */ class SlotMgr { /* * Note: the following slot IDs are pre-defined: * 1 - SLOT_OWNER_ACTION / "Shell Command" / "Input" / Input pos / FileGroup type. * 2 - SLOT_OWNER_ACTION / "Shell Command" / "Command" / Param pos / String type. * 3 - SLOT_OWNER_ACTION / "Shell Command" / "Directory" / Param pos / Directory type. * 4 - SLOT_OWNER_ACTION / "Shell Command" / "Output" / Output pos / FileGroup type. */ /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ /** Cached-copies of the SlotDetails for the default slots */ SlotDetails defaultSlots[] = null; /** The BuildStore that owns this SlotMgr */ private BuildStore buildStore; /** * Our database manager object, used to access the database content. This is provided * to us when the SlotMgr object is first instantiated. */ private BuildStoreDB db = null; /** Various prepared statement for database access. */ private PreparedStatement insertTypePrepStmt = null, updateTypePrepStmt = null, findTypeByNamePrepStmt = null, findTypeByIdPrepStmt = null, findTypeByPosPrepStmt = null, findTypeByAnyPosPrepStmt = null, trashTypePrepStmt = null, insertValuePrepStmt = null, updateValuePrepStmt = null, findValuePrepStmt = null, deleteValuePrepStmt = null, countSlotUsage = null, selectActionsWithMatchingSlotPrepStmt = null, selectActionsWithEqualSlotPrepStmt = null, doesSlotTypeExistPrepStmt = null; /*=====================================================================================* * CONSTRUCTORS *=====================================================================================*/ /** * Create a new SlotMgr object. * * @param buildStore The BuildStore that owns this manager. */ SlotMgr(BuildStore buildStore) { this.buildStore = buildStore; this.db = buildStore.getBuildStoreDB(); /* prepare the database statements */ insertTypePrepStmt = db.prepareStatement("insert into slotTypes values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)"); updateTypePrepStmt = db.prepareStatement( "update slotTypes set slotName = ?, slotDescr = ?, slotCard = ?, defaultValue = ? " + "where slotId = ?"); findTypeByNamePrepStmt = db.prepareStatement( "select slotId, slotType, slotDescr, slotPos, slotCard, defaultValue from slotTypes " + "where ownerType = ? and ownerId = ? and slotName = ? and trashed = 0"); doesSlotTypeExistPrepStmt = db.prepareStatement( "select slotId from slotTypes where slotId = ?"); findTypeByIdPrepStmt = db.prepareStatement( "select slotName, slotDescr, slotType, slotPos, slotCard, defaultValue, ownerType, ownerId from slotTypes " + "where slotId = ? and trashed = 0"); findTypeByPosPrepStmt = db.prepareStatement( "select slotId, slotName, slotDescr, slotType, slotPos, slotCard, defaultValue from slotTypes " + "where ownerType = ? and ownerId = ? and slotPos = ? and trashed = 0 order by slotId"); findTypeByAnyPosPrepStmt = db.prepareStatement( "select slotId, slotName, slotDescr, slotType, slotPos, slotCard, defaultValue from slotTypes " + "where ownerType = ? and ownerId = ? and trashed = 0 order by slotId"); trashTypePrepStmt = db.prepareStatement("update slotTypes set trashed = ? where slotId = ? and trashed = ?"); insertValuePrepStmt = db.prepareStatement("insert into slotValues values (?, ?, ?, ?)"); updateValuePrepStmt = db.prepareStatement("update slotValues set value = ? where ownerType = ? " + " and ownerId = ? and slotId = ?"); findValuePrepStmt = db.prepareStatement("select value from slotValues where ownerType = ? " + " and ownerId = ? and slotId = ?"); deleteValuePrepStmt = db.prepareStatement("delete from slotValues where ownerType = ? and ownerId = ? " + "and slotId = ?"); countSlotUsage = db.prepareStatement("select count(*) from slotValues where slotId = ?"); selectActionsWithMatchingSlotPrepStmt = db.prepareStatement( "select actionId from buildActions, slotValues where (ownerType = " + ISlotTypes.SLOT_OWNER_ACTION + ") and (actionId = ownerId) and (slotId = ?) and (trashed == 0) and (value like ?)"); selectActionsWithEqualSlotPrepStmt = db.prepareStatement( "select actionId from buildActions, slotValues where (ownerType = " + ISlotTypes.SLOT_OWNER_ACTION + ") and (actionId = ownerId) and (slotId = ?) and (trashed == 0) and (value = ?)"); /* define the default slots - these must match with the definitions in IActionMgr */ newSlot(ISlotTypes.SLOT_OWNER_ACTION, ActionTypeMgr.BUILTIN_SHELL_COMMAND_ID, "Input", "Files that are read by this shell action", ISlotTypes.SLOT_TYPE_FILEGROUP, ISlotTypes.SLOT_POS_INPUT, ISlotTypes.SLOT_CARD_OPTIONAL, null, null); newSlot(ISlotTypes.SLOT_OWNER_ACTION, ActionTypeMgr.BUILTIN_SHELL_COMMAND_ID, "Command", "The shell command to be executed by this action", ISlotTypes.SLOT_TYPE_TEXT, ISlotTypes.SLOT_POS_PARAMETER, ISlotTypes.SLOT_CARD_REQUIRED, null, null); newSlot(ISlotTypes.SLOT_OWNER_ACTION, ActionTypeMgr.BUILTIN_SHELL_COMMAND_ID, "Directory", "The file system directory in which the shell command is executed", ISlotTypes.SLOT_TYPE_DIRECTORY, ISlotTypes.SLOT_POS_PARAMETER, ISlotTypes.SLOT_CARD_REQUIRED, null, null); newSlot(ISlotTypes.SLOT_OWNER_ACTION, ActionTypeMgr.BUILTIN_SHELL_COMMAND_ID, "Output", "Files that are written by this shell action", ISlotTypes.SLOT_TYPE_FILEGROUP, ISlotTypes.SLOT_POS_OUTPUT, ISlotTypes.SLOT_CARD_OPTIONAL, null, null); } /*=====================================================================================* * PUBLIC METHODS *=====================================================================================*/ /** * Add a new slot to this element (either an actionType or a package). * * @param ownerType The type of thing to add this slot to (SLOT_OWNER_ACTION, * SLOT_OWNER_PACKAGE) * @param ownerId The new owner (actionType or package) to add the slot to. * @param slotName The name of the slot (must be unique within this actionType). * @param slotDescr A textual description of this slot (can be long and multi-line). * @param slotType The slot's type (SLOT_TYPE_FILEGROUP, etc). * @param slotPos The slot's position (SLOT_POS_INPUT, etc). * @param slotCard Either SLOT_CARD_OPTIONAL, SLOT_CARD_ONE, SLOT_CARD_MULTI. * @param defaultValue If not required, a default value. * @param enumValues For SLOT_TYPE_ENUMERATION, an array of valid values. * @return The newly-added slot ID, or: * ErrorCode.INVALID_NAME if slotName is not a valid slot identifier. * ErrorCode.ALREADY_USED if slotName is already in use (for this owner). * ErrorCode.INVALID_OP if slotType or slotPos are not valid/relevant, or * if enumValues does not contain a valid enumeration. * ErrorCode.OUT_OF_RANGE is the cardinality is invalid, or if a multi-slot * is selected and this is not an action slot, or there's already * a multi-slot for this action. * ErrorCode.BAD_VALUE if the default value is not valid for this type. * ErrorCode.NOT_FOUND if ownerType/ownerId are not valid. */ int newSlot(int ownerType, int ownerId, String slotName, String slotDescr, int slotType, int slotPos, int slotCard, Object defaultValue, String[] enumValues) { /* validate that the slot name is well-formed */ if (!BuildStoreUtils.isValidSlotName(slotName)) { return ErrorCode.INVALID_NAME; } /* validate slotType and slotPos */ if ((slotType < ISlotTypes.SLOT_TYPE_FILEGROUP) || (slotType > ISlotTypes.SLOT_TYPE_FILE)) { return ErrorCode.INVALID_OP; } if ((slotPos < ISlotTypes.SLOT_POS_INPUT) || (slotPos > ISlotTypes.SLOT_POS_LOCAL)) { return ErrorCode.INVALID_OP; } /* validate that the slot name is not already in use for this ownerType/ownerId */ if (getSlotByName(ownerType, ownerId, slotName) != null) { return ErrorCode.ALREADY_USED; } /* * Validate that file groups can only appear in input/output slots, and that no other types * can appear in these slots. */ if (slotType == ISlotTypes.SLOT_TYPE_FILEGROUP){ if ((slotPos != ISlotTypes.SLOT_POS_INPUT) && (slotPos != ISlotTypes.SLOT_POS_OUTPUT)) { return ErrorCode.INVALID_OP; } } else { if ((slotPos == ISlotTypes.SLOT_POS_INPUT) || (slotPos == ISlotTypes.SLOT_POS_OUTPUT)) { return ErrorCode.INVALID_OP; } } /* validate slot cardinality */ if ((slotCard < ISlotTypes.SLOT_CARD_OPTIONAL) || (slotCard > ISlotTypes.SLOT_CARD_MULTI)) { return ErrorCode.OUT_OF_RANGE; } /* special rules apply for multi-slots */ int err = validateMultiSlot(ownerType, ownerId, slotCard, slotType, slotPos); if (err != ErrorCode.OK) { return err; } /* All the inputs are valid, so add the new record to the database */ String defaultValueString = null; if (defaultValue != null) { try { defaultValueString = convertObjectToString(slotType, defaultValue); } catch (NumberFormatException ex) { return ErrorCode.BAD_VALUE; } } int newSlotId; try { insertTypePrepStmt.setInt(1, ownerType); insertTypePrepStmt.setInt(2, ownerId); insertTypePrepStmt.setString(3, slotName); insertTypePrepStmt.setString(4, slotDescr); insertTypePrepStmt.setInt(5, slotType); insertTypePrepStmt.setInt(6, slotPos); insertTypePrepStmt.setInt(7, slotCard); insertTypePrepStmt.setString(8, defaultValueString); insertTypePrepStmt.setInt(9, 0); db.executePrepUpdate(insertTypePrepStmt); newSlotId = db.getLastRowID(); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return newSlotId; } /*-------------------------------------------------------------------------------------*/ /** * Change the details of an existing slot (keyed by details.slotId). * * @param details The modified SlotDetails. * * @return ErrorCode.OK on success * ErrorCode.INVALID_NAME if details.slotName is not a valid slot identifier. * ErrorCode.ALREADY_USED if details.slotName is already in use (for this owner). * ErrorCode.INVALID_OP if details.slotType or details.slotPos have been changed. * ErrorCode.OUT_OF_RANGE is the cardinality is invalid, or if a multi-slot * is selected and this is not an action slot, or there's already * a multi-slot for this action. * ErrorCode.BAD_VALUE if the default value is not valid for this type. * ErrorCode.NOT_FOUND if ownerType or details.slotId are not valid. */ int changeSlot(SlotDetails details) { /* fetch the existing slot details, so we can validate the changes */ SlotDetails oldDetails = getSlotByID(details.slotId); if (oldDetails == null) { return ErrorCode.NOT_FOUND; } /* * Validate that fields that can't change, haven't changed. */ if ((details.slotPos != oldDetails.slotPos) || (details.slotType != oldDetails.slotType)) { return ErrorCode.INVALID_OP; } /* * Validate changes to the new slot name, but only if the name hasn't changed. */ if (!oldDetails.slotName.equals(details.slotName)) { if (!BuildStoreUtils.isValidSlotName(details.slotName)) { return ErrorCode.INVALID_NAME; } if (getSlotByName(oldDetails.ownerType, oldDetails.ownerId, details.slotName) != null) { return ErrorCode.ALREADY_USED; } } /* special rules apply for multi-slots */ int err = validateMultiSlot(oldDetails.ownerType, oldDetails.ownerId, details.slotCard, oldDetails.slotType, oldDetails.slotPos); if (err != ErrorCode.OK) { return err; } /* All the inputs are valid, so update the record in the database */ String defaultValueString = null; if (details.defaultValue != null) { try { defaultValueString = convertObjectToString(details.slotType, details.defaultValue); } catch (NumberFormatException ex) { return ErrorCode.BAD_VALUE; } } try { updateTypePrepStmt.setString(1, details.slotName); updateTypePrepStmt.setString(2, details.slotDescr); updateTypePrepStmt.setInt(3, details.slotCard); updateTypePrepStmt.setString(4, defaultValueString); updateTypePrepStmt.setInt(5, details.slotId); int rows = db.executePrepUpdate(updateTypePrepStmt); if (rows != 1) { return ErrorCode.NOT_FOUND; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /** * Return a slot's detailed information. * * @param slotId The slot to query. * @return A SlotDetails structure containing the specified slot's details, or null if * slotId does not refer to a valid slot. */ SlotDetails getSlotByID(int slotId) { ResultSet rs = null; SlotDetails details = null; try { findTypeByIdPrepStmt.setInt(1, slotId); rs = db.executePrepSelectResultSet(findTypeByIdPrepStmt); if (rs.next()) { String slotName = rs.getString(1); String slotDescr = rs.getString(2); int slotType = rs.getInt(3); int slotPos = rs.getInt(4); int slotCard = rs.getInt(5); Object defaultValue = convertStringToObject(slotType, rs.getString(6)); int ownerType = rs.getInt(7); int ownerId = rs.getInt(8); details = new SlotDetails(slotId, ownerType, ownerId, slotName, slotDescr, slotType, slotPos, slotCard, defaultValue, null); } rs.close(); return details; } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /** * Return all the slots associated with an owner. We assume that all input parameters * have been validated by the caller (actionTypeMgr or packageMgr). * * @param ownerType The type of thing that owns this slots (SLOT_OWNER_ACTION, * SLOT_OWNER_PACKAGE) * @param ownerId The ID of the owner (actionType/package) containing the slots. * @param slotPos The position (within the actionType) of the slot (SLOT_POS_INPUT, etc). * @return An array of slot details. */ SlotDetails[] getSlots(int ownerType, int ownerId, int slotPos) { PreparedStatement stmt; try { /* A slotPos of SLOT_POS_ANY requires a different database query */ if (slotPos == ISlotTypes.SLOT_POS_ANY) { stmt = findTypeByAnyPosPrepStmt; } else { stmt = findTypeByPosPrepStmt; stmt.setInt(3, slotPos); } stmt.setInt(1, ownerType); stmt.setInt(2, ownerId); /* * Fetch the results from the database, and form the SlotDetails[] */ ResultSet rs = db.executePrepSelectResultSet(stmt); List<SlotDetails> results = new ArrayList<SlotDetails>(); while (rs.next()) { int slotId = rs.getInt(1); String slotName = rs.getString(2); String slotDescr = rs.getString(3); int slotType = rs.getInt(4); int actualSlotPos = rs.getInt(5); int slotCard = rs.getInt(6); Object defaultValue = convertStringToObject(slotType, rs.getString(7)); SlotDetails details = new SlotDetails(slotId, ownerType, ownerId, slotName, slotDescr, slotType, actualSlotPos, slotCard, defaultValue, null); results.add(details); } rs.close(); return results.toArray(new SlotDetails[results.size()]); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /** * For the specified owner (actionType/package), return details of the named slot. * * @param ownerType The type of thing to add this slot to (SLOT_OWNER_ACTION, * SLOT_OWNER_PACKAGE) * @param ownerId The ID of the owner containing the slot. * @param slotName The name of the slot (within the scope of the owner). * @return The slot details, or null if ownerType, ownerId or slotName is invalid. */ SlotDetails getSlotByName(int ownerType, int ownerId, String slotName) { ResultSet rs = null; SlotDetails details = null; try { findTypeByNamePrepStmt.setInt(1, ownerType); findTypeByNamePrepStmt.setInt(2, ownerId); findTypeByNamePrepStmt.setString(3, slotName); rs = db.executePrepSelectResultSet(findTypeByNamePrepStmt); /* there should be only one result... */ if (rs.next()) { int slotId = rs.getInt(1); int slotType = rs.getInt(2); String slotDescr = rs.getString(3); int slotPos = rs.getInt(4); int slotCard = rs.getInt(5); Object defaultValue = convertStringToObject(slotType, rs.getString(5)); details = new SlotDetails(slotId, ownerType, ownerId, slotName, slotDescr, slotType, slotPos, slotCard, defaultValue, null); } rs.close(); return details; } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /** * Remove a slot from the owner. The slot can only be removed if there are no * actions/packages that define the slot value. * * @param slotId The ID of the slot to be trashed. * @return ErrorCode.OK on success, * ErrorCode.NOT_FOUND if slotId is invalid, or * ErrorCode.CANT_REMOVE if the slot is still in use. */ int trashSlot(int slotId) { try { /* ensure that there are no action or package instances using this slot */ countSlotUsage.setInt(1, slotId); ResultSet rs = db.executePrepSelectResultSet(countSlotUsage); int usageCount = rs.getInt(1); rs.close(); if (usageCount != 0) { return ErrorCode.CANT_REMOVE; } /* proceed to mark the slot as being trashed (if it's not already trashed) */ trashTypePrepStmt.setInt(1, 1); trashTypePrepStmt.setInt(2, slotId); trashTypePrepStmt.setInt(3, 0); int count = db.executePrepUpdate(trashTypePrepStmt); if (count != 1) { return ErrorCode.NOT_FOUND; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /** * Revive a slot that had previously been trashed. * * @param slotId The ID of the slot to be revived. * @return ErrorCode.OK on success, * ErrorCode.NOT_FOUND if slotId is invalid, or isn't trashed. */ public int reviveSlot(int slotId) { try { /* first, check if the slot exists */ doesSlotTypeExistPrepStmt.setInt(1, slotId); Integer result[] = db.executePrepSelectIntegerColumn(doesSlotTypeExistPrepStmt); if (result.length != 1) { return ErrorCode.NOT_FOUND; } /* proceed to mark the slot as being revived (if it's not already revived) */ trashTypePrepStmt.setInt(1, 0); trashTypePrepStmt.setInt(2, slotId); trashTypePrepStmt.setInt(3, 1); int count = db.executePrepUpdate(trashTypePrepStmt); if (count != 1) { return ErrorCode.CANT_REVIVE; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /** * For the specified action/sub-package, set a slot to the given value. * * @param ownerType Either SLOT_OWNER_ACTION or SLOT_OWNER_PACKAGE. * @param ownerId The action/sub-package that the slot is attached to. * @param slotId The slot that's connected to the action. * @param value The new value to be set (typically an Integer or String). * @return ErrorCode.OK on success, ErrorCode.NOT_FOUND if slotId isn't relevant for * actionType/package, or ErrorCode.BAD_VALUE if the value can't be assigned to * the specified slot. */ public int setSlotValue(int ownerType, int ownerId, int slotId, Object value) { /* * Assume that ownerType and ownerId was validated by our caller, but we need * to check that slotId is relevant for ownerType/ownerId. If not, return NOT_FOUND. */ SlotDetails details = getSlotByID(slotId); if (details == null) { return ErrorCode.NOT_FOUND; } /* * We store all data in string format, but based on the slot's type, we first need * to check whether the value is appropriate. */ String stringToSet; try { stringToSet = convertObjectToString(details.slotType, value); } catch (NumberFormatException ex) { return ErrorCode.BAD_VALUE; } /* * The value is known valid, so let's insert it into the database. First, try to update * an existing value, but if that fails, add a new entry. */ try { updateValuePrepStmt.setString(1, stringToSet); updateValuePrepStmt.setInt(2, ownerType); updateValuePrepStmt.setInt(3, ownerId); updateValuePrepStmt.setInt(4, slotId); int count = db.executePrepUpdate(updateValuePrepStmt); /* no existing record, insert instead */ if (count == 0) { insertValuePrepStmt.setInt(1, ownerType); insertValuePrepStmt.setInt(2, ownerId); insertValuePrepStmt.setInt(3, slotId); insertValuePrepStmt.setString(4, stringToSet); db.executePrepUpdate(insertValuePrepStmt); } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /** * For the specified action/sub-package, retrieve the specified slot's value. If the value * has not been explicitly set for this action, the slot default value will be returned. * * @param ownerType Either SLOT_OWNER_ACTION or SLOT_OWNER_PACKAGE. * @param ownerId The action that the slot is attached to. * @param slotId The slot that's connected to the action. * @return The slot's value (typically Integer or String), or null if actionId/slotId can't * be mapped to a valid slot. */ public Object getSlotValue(int ownerType, int ownerId, int slotId) { /* get details about this slot (default value, type, etc) */ SlotDetails slotDetails = getSlotByID(slotId); if (slotDetails == null) { return null; } /* * Check if there's already a value set of ownerType/actionId/slotId. */ String results[] = null; try { findValuePrepStmt.setInt(1, ownerType); findValuePrepStmt.setInt(2, ownerId); findValuePrepStmt.setInt(3, slotId); results = db.executePrepSelectStringColumn(findValuePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* * We found a result, so convert it to appropriate type (Integer, Boolean, etc). */ if (results.length == 1) { return convertStringToObject(slotDetails.slotType, results[0]); } /* * If there's no value set, use the default value for slotId. */ else { return slotDetails.defaultValue; } } /*-------------------------------------------------------------------------------------*/ /** * Determine whether the specified slot currently holds a value. * @param ownerType Either SLOT_OWNER_ACTION or SLOT_OWNER_PACKAGE * @param ownerId The action or package instance that the slot is attached to. * @param slotId The slot that's connected to the action or package instance. * @return True if there's an explicit (non-default) value in this slot, else false. * Also return false if memberId/slotId are invalid. */ public boolean isSlotSet(int ownerType, int ownerId, int slotId) { boolean result; try { findValuePrepStmt.setInt(1, ownerType); findValuePrepStmt.setInt(2, ownerId); findValuePrepStmt.setInt(3, slotId); ResultSet rs = db.executePrepSelectResultSet(findValuePrepStmt); result = rs.next(); rs.close(); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return result; } /*-------------------------------------------------------------------------------------*/ /** * Remove the value (if any) that has been inserted into this slot, therefore setting * this slot to its default value. If ownerId or slotId is invalid, silently do nothing. * @param ownerType Either SLOT_OWNER_ACTION or SLOT_OWNER_PACKAGE * @param ownerId The action or package instance that the slot is attached to. * @param slotId The slot that's connected to the action or package instance. */ public void clearSlotValue(int ownerType, int ownerId, int slotId) { /* * Simply delete the record from the database, if it exists. If inputs to * this method are invalid, this query has no effect. */ try { deleteValuePrepStmt.setInt(1, ownerType); deleteValuePrepStmt.setInt(2, ownerId); deleteValuePrepStmt.setInt(3, slotId); db.executePrepUpdate(deleteValuePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /** * Return an array of owners IDs for all owners of a specified type where the slot * matches an expected pattern (using % as the wildcard character). This * uses the underlying database "like" operator. * * @param ownerType The type of owner for the slot (SLOT_OWNER_ACTION, SLOT_OWNER_PACKAGE). * @param slotId ID of the slot to query (only ownerIDs that have this slot are considered). * @param match The match string (using % as the wildcard). * @return An array of owner IDs that match, or null if invalid inputs are provided. */ public Integer[] getOwnersWhereSlotIsLike(int ownerType, int slotId, String match) { /* validate inputs */ if (match == null) { return null; } SlotDetails details = getSlotByID(slotId); if (details == null) { return null; } /* search the database for actions with matching slots */ Integer results[]; try { if (ownerType == ISlotTypes.SLOT_OWNER_ACTION) { selectActionsWithMatchingSlotPrepStmt.setInt(1, slotId); selectActionsWithMatchingSlotPrepStmt.setString(2, match); results = db.executePrepSelectIntegerColumn(selectActionsWithMatchingSlotPrepStmt); } else { return null; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return results; } /*-------------------------------------------------------------------------------------*/ /** * Return an array of IDs for all owners (actions, packages) where the specified slot * exactly matches the expected value. * * @param ownerType The type of owner for the slot (SLOT_OWNER_ACTION, SLOT_OWNER_PACKAGE). * @param slotId ID of the slot to query (only ownerIDs that have this slot are considered). * @param match The match object (of a type that is relevant for this slot). * @return An array of owner IDs that match, or null if invalid inputs are provided. */ public Integer[] getOwnersWhereSlotEquals(int ownerType, int slotId, Object match) { /* validate inputs */ if (match == null) { return null; } SlotDetails details = getSlotByID(slotId); if (details == null) { return null; } /* convert the input value into its String format */ String matchString = null; try { matchString = convertObjectToString(details.slotType, match); } catch (NumberFormatException ex) { return null; } /* search for "equal" slots in the database */ Integer results[] = null; try { if (ownerType == ISlotTypes.SLOT_OWNER_ACTION) { selectActionsWithEqualSlotPrepStmt.setInt(1, slotId); selectActionsWithEqualSlotPrepStmt.setString(2, matchString); results = db.executePrepSelectIntegerColumn(selectActionsWithEqualSlotPrepStmt); } else { return null; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return results; } /*-------------------------------------------------------------------------------------*/ /** * @return The BuildStore that owns this SlotMgr object. */ IBuildStore getBuildStore() { return buildStore; } /*=====================================================================================* * PRIVATE METHODS *=====================================================================================*/ /** * Validate that it's OK for a slot to be a multi-slot. This checks that no other input * slot for the action is already a multi-slot. * * @param ownerType The type of the slot's owner (action, package). * @param ownerId ID of the action that this slot belongs to. * @param slotCard The new slot's cardinality. * @param slotType The type of the new slot. * @param slotPos The position of this slot. * @return ErrorCode.OK if the multi-slot is OK, else OUT_OF_RANGE. */ private int validateMultiSlot(int ownerType, int ownerId, int slotCard, int slotType, int slotPos) { /* not a multi-slot? We have no checking to do */ if (slotCard != ISlotTypes.SLOT_CARD_MULTI) { return ErrorCode.OK; } /* only actions can have multi-slots */ if (ownerType != ISlotTypes.SLOT_OWNER_ACTION) { return ErrorCode.OUT_OF_RANGE; } /* only input file groups can have multi cardinality */ if ((slotPos != ISlotTypes.SLOT_POS_INPUT) || (slotType != ISlotTypes.SLOT_TYPE_FILEGROUP)) { return ErrorCode.OUT_OF_RANGE; } /* only one input file group can have this cardinality - check all current slots first */ SlotDetails[] currentSlots = getSlots(ISlotTypes.SLOT_OWNER_ACTION, ownerId, ISlotTypes.SLOT_POS_INPUT); for (int i = 0; i < currentSlots.length; i++) { if (currentSlots[i].slotCard == ISlotTypes.SLOT_CARD_MULTI) { return ErrorCode.OUT_OF_RANGE; } } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /** * Given a string representation of a value, as stored in the database, convert it to * the appropriate Java type. * @param slotType Type of the slot (SLOT_TYPE_INTEGER, etc). * @param stringValue The slot value, as a String. * @return The Java Object reflecting the slot value (e.g. String, Integer, Boolean, etc). */ private Object convertStringToObject(int slotType, String stringValue) { /* null values don't have a type */ if (stringValue == null) { return null; } /* * For each slot type, convert from the normalize String value into an appropriate return type. */ switch (slotType) { case ISlotTypes.SLOT_TYPE_BOOLEAN: if (stringValue.equals("true")) { return Boolean.TRUE; } else { return Boolean.FALSE; } case ISlotTypes.SLOT_TYPE_FILEGROUP: case ISlotTypes.SLOT_TYPE_INTEGER: case ISlotTypes.SLOT_TYPE_DIRECTORY: case ISlotTypes.SLOT_TYPE_FILE: return Integer.valueOf(stringValue); case ISlotTypes.SLOT_TYPE_TEXT: return stringValue; default: return null; } } /*-------------------------------------------------------------------------------------*/ /** * Given a slot value, as passed by the caller, convert it into a String value that can * be stored in the database. The format/interpretation of this string will depend on * the slot's type. * @param slotType SLOT_TYPE_FILEGROUP, etc. * @param value The value (Integer, Boolean, String, etc) to be converted to a String. * @return The value in its String format. * @throws NumberFormatException If the input value is off a non-convertible type. */ private String convertObjectToString(int slotType, Object value) { if (value == null) { return null; } switch (slotType) { /* SLOT_TYPE_FILEGROUP must have value Integer */ case ISlotTypes.SLOT_TYPE_FILEGROUP: case ISlotTypes.SLOT_TYPE_DIRECTORY: case ISlotTypes.SLOT_TYPE_FILE: /* integers must be positive */ if (value instanceof Integer) { return value.toString(); } /* other object types are illegal */ else { throw new NumberFormatException("Illegal value type for SLOT_TYPE_FILEGROUP/FILE/DIRECTORY: " + value.getClass()); } /* For SLOT_TYPE_INTEGER, the input must be a String or Integer. */ case ISlotTypes.SLOT_TYPE_INTEGER: /* integers easily convert to String */ if (value instanceof Integer) { return value.toString(); } /* strings must contain a valid integer */ else if (value instanceof String) { int intVal = 0; try { intVal = Integer.parseInt((String) value); } catch (NumberFormatException ex) { throw new NumberFormatException("Illegal format for SLOT_TYPE_INTEGER: " + value); } return Integer.toString(intVal); } /* other object types are illegal */ else { throw new NumberFormatException("Illegal value type for SLOT_TYPE_INTEGER: " + value.getClass()); } /* For SLOT_TYPE_TEXT, the input must be a String. */ case ISlotTypes.SLOT_TYPE_TEXT: if (value instanceof String) { return (String)value; } /* other object types are illegal */ else { throw new NumberFormatException("Illegal value type for SLOT_TYPE_TEXT: " + value.getClass()); } case ISlotTypes.SLOT_TYPE_BOOLEAN: /* Boolean values easily convert to String */ if (value instanceof Boolean) { return value.toString(); } /* Integer value: 0 is false, all other values are true */ else if (value instanceof Integer) { if (((Integer)value).intValue() == 0) { return "false"; } else { return "true"; } } /* String value: many legal English words for true/false */ else if (value instanceof String) { String str = (String)value; if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("yes") || str.equalsIgnoreCase("on")) { return "true"; } else if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("no") || str.equalsIgnoreCase("off")) { return "false"; } else { throw new NumberFormatException("Illegal String value for SLOT_TYPE_BOOLEAN: " + str); } } /* other object types are illegal */ else { throw new NumberFormatException("Illegal value type for SLOT_TYPE_TEXT: " + value.getClass()); } /* all other slotTypes are illegal */ default: throw new NumberFormatException("Invalid slotType: " + slotType); } } /*-------------------------------------------------------------------------------------*/ }