/******************************************************************************* * Copyright (c) 2011 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: * "Peter Smith <psmith@arapiki.com>" - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.model.impl; import java.sql.PreparedStatement; import java.sql.SQLException; import com.buildml.model.FatalBuildStoreError; import com.buildml.model.IFileAttributeMgr; import com.buildml.model.IFileMgr; import com.buildml.model.types.FileSet; import com.buildml.utils.errors.ErrorCode; /** * A manager class (that supports the BuildStore class) that manages all BuildStore * information on which attributes are attached to the build system's files. * Each attribute must first be added to the system (by name), which provides a * unique ID number for the attribute. Attributes (and their int/string values) * can then be associated with paths. * <p> * There should be exactly one FileAttributeMgr object per BuildStore object. Use the * BuildStore's getFileAttributeMgr() method to obtain that one instance. * * @author "Peter Smith <psmith@arapiki.com>" */ /* package private */ class FileAttributeMgr implements IFileAttributeMgr { /** * Our database manager object, used to access the database content. This is provided * to us when the FileAttributeMgr object is first instantiated. */ private BuildStoreDB db; /** * The FileMgr object that these file attributes are associated with. */ private IFileMgr fileMgr; /** * Various prepared statements for database access. */ private PreparedStatement selectIdFromNamePrepStmt = null, selectNameFromIdPrepStmt = null, insertFileAttrsNamePrepStmt = null, selectOrderedNamePrepStmt = null, deleteFileAttrsNamePrepStmt = null, countFileAttrUsagePrepStmt = null, insertFileAttrsPrepStmt = null, selectValueFromFileAttrsPrepStmt = null, updateFileAttrsPrepStmt = null, deleteFileAttrsPrepStmt = null, deleteAllFileAttrsPrepStmt = null, findAttrsOnPathPrepStmt = null, findPathsWithAttrPrepStmt = null, findPathsWithAttrValuePrepStmt = null; /*-------------------------------------------------------------------------------------*/ /** * Create a new FileAttributeMgr object. * * @param buildStore The BuildStore object that owns this FileAttributeMgr object. * @param fileMgr The FileMgr object that these attributes are attached to */ public FileAttributeMgr(BuildStore buildStore, IFileMgr fileMgr) { this.db = buildStore.getBuildStoreDB(); this.fileMgr = fileMgr; /* Prepare our database statements */ selectIdFromNamePrepStmt = db.prepareStatement("select id from fileAttrsName where name = ?"); selectNameFromIdPrepStmt = db.prepareStatement("select name from fileAttrsName where id = ?"); selectOrderedNamePrepStmt = db.prepareStatement("select name from fileAttrsName order by name"); insertFileAttrsNamePrepStmt = db.prepareStatement("insert into fileAttrsName values (null, ?)"); deleteFileAttrsNamePrepStmt = db.prepareStatement("delete from fileAttrsName where name = ?"); countFileAttrUsagePrepStmt = db.prepareStatement("select count(*) from fileAttrs where attrId = ?"); insertFileAttrsPrepStmt = db.prepareStatement("insert into fileAttrs values (?, ?, ?)"); selectValueFromFileAttrsPrepStmt = db.prepareStatement( "select fileAttrs.value from files, fileAttrs " + "where (files.id = fileAttrs.pathId) and pathId = ? and attrId = ? and files.trashed = 0"); updateFileAttrsPrepStmt = db.prepareStatement("update fileAttrs set value = ? " + "where pathId = ? and attrId = ?"); deleteFileAttrsPrepStmt = db.prepareStatement("delete from fileAttrs where " + "pathId = ? and attrId = ?"); deleteAllFileAttrsPrepStmt = db.prepareStatement("delete from fileAttrs where pathId = ?"); findAttrsOnPathPrepStmt = db.prepareStatement( "select fileAttrs.attrId from files, fileAttrs " + "where (files.id = fileAttrs.pathId) and fileAttrs.pathId = ? and files.trashed = 0"); findPathsWithAttrPrepStmt = db.prepareStatement( "select fileAttrs.pathId from files, fileAttrs " + "where (files.id = fileAttrs.pathId) and fileAttrs.attrId = ? and files.trashed = 0"); findPathsWithAttrValuePrepStmt = db.prepareStatement( "select fileAttrs.pathId from files, fileAttrs " + "where (files.id = fileAttrs.pathId) and fileAttrs.attrId = ? and fileAttrs.value = ? " + "and files.trashed = 0"); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#newAttrName(java.lang.String) */ @Override public int newAttrName(String attrName) { /* if this name is already used, return an error */ int existingId = getAttrIdFromName(attrName); if (existingId != ErrorCode.NOT_FOUND) { return ErrorCode.ALREADY_USED; } /* else, add the name to the fileAttrsName table, and retrieve its unique ID */ try { insertFileAttrsNamePrepStmt.setString(1, attrName); db.executePrepUpdate(insertFileAttrsNamePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return db.getLastRowID(); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getAttrIdFromName(java.lang.String) */ @Override public int getAttrIdFromName(String attrName) { Integer results[]; try { selectIdFromNamePrepStmt.setString(1, attrName); results = db.executePrepSelectIntegerColumn(selectIdFromNamePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* if there's no entry at all, return NOT_FOUND */ if (results.length == 0) { return ErrorCode.NOT_FOUND; } /* if there's one entry, that's good - just return that number. */ if (results.length == 1) { return results[0]; } /* else, problem - too many records */ throw new FatalBuildStoreError("Too many records in fileAttrsName table for " + attrName); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getAttrNameFromId(int) */ @Override public String getAttrNameFromId(int attrId) { String results[]; try { selectNameFromIdPrepStmt.setInt(1, attrId); results = db.executePrepSelectStringColumn(selectNameFromIdPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* if there's no entry at all, return NOT_FOUND */ if (results.length == 0) { return null; } /* if there's one entry, that's good - just return that number. */ if (results.length == 1) { return results[0]; } /* else, problem - too many records */ throw new FatalBuildStoreError("Too many records in fileAttrsName table for " + attrId); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getAttrNames() */ @Override public String[] getAttrNames() { return db.executePrepSelectStringColumn(selectOrderedNamePrepStmt); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#deleteAttrName(java.lang.String) */ @Override public int deleteAttrName(String attrName) { /* attribute names can't be deleted if they're in use - check this first */ int attrId = getAttrIdFromName(attrName); try { countFileAttrUsagePrepStmt.setInt(1, attrId); /* * A select count(*) should always return 1 result. This count will be non-0 * if the attribute name is "in use" in the fileAttrs table. */ Integer results[] = db.executePrepSelectIntegerColumn(countFileAttrUsagePrepStmt); if (results[0] != 0) { return ErrorCode.CANT_REMOVE; } } catch (SQLException ex) { throw new FatalBuildStoreError("Unable to execute SQL statement", ex); } /* * Try to delete the attribute name record, but also take note of whether anything was deleted. * If nothing was deleted, the name is considered "not found" */ try { deleteFileAttrsNamePrepStmt.setString(1, attrName); int rowCount = db.executePrepUpdate(deleteFileAttrsNamePrepStmt); if (rowCount == 0) { return ErrorCode.NOT_FOUND; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#setAttr(int, int, java.lang.String) */ @Override public void setAttr(int pathId, int attrId, String attrValue) { /* if attrValue is null, that's equivalent to deleting the record */ if (attrValue == null) { deleteAttr(pathId, attrId); return; } /* * Try to update the record, but take note of whether it really was updated. * If not, we'll need to insert a new record. */ try { updateFileAttrsPrepStmt.setString(1, attrValue); updateFileAttrsPrepStmt.setInt(2, pathId); updateFileAttrsPrepStmt.setInt(3, attrId); int rowCount = db.executePrepUpdate(updateFileAttrsPrepStmt); /* did the record exist? If not, insert it */ if (rowCount == 0) { insertFileAttrsPrepStmt.setInt(1, pathId); insertFileAttrsPrepStmt.setInt(2, attrId); insertFileAttrsPrepStmt.setString(3, attrValue); db.executePrepUpdate(insertFileAttrsPrepStmt); } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#setAttr(int, int, int) */ @Override public int setAttr(int pathId, int attrId, int attrValue) { if (attrValue < 0) { return ErrorCode.BAD_VALUE; } setAttr(pathId, attrId, Integer.toString(attrValue)); return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getAttrAsString(int, int) */ @Override public String getAttrAsString(int pathId, int attrId) { String results[]; try { selectValueFromFileAttrsPrepStmt.setInt(1, pathId); selectValueFromFileAttrsPrepStmt.setInt(2, attrId); results = db.executePrepSelectStringColumn(selectValueFromFileAttrsPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* if there was no result, return null */ if (results.length == 0) { return null; } /* ok, there's exactly one result */ else if (results.length == 1) { return results[0]; } /* else, problem - too many records */ throw new FatalBuildStoreError("Too many records in fileAttrs table for " + pathId + " / " + attrId); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getAttrAsInteger(int, int) */ @Override public int getAttrAsInteger(int pathId, int attrId) { /* fetch the attribute's value as a String */ String result = getAttrAsString(pathId, attrId); /* if it's not set, return NOT_FOUND */ if (result == null) { return ErrorCode.NOT_FOUND; } /* if the string isn't in the format of an integer, return BAD_VALUE */ int iValue; try { iValue = Integer.valueOf(result); } catch (NumberFormatException ex) { return ErrorCode.BAD_VALUE; } /* we can only have positive integers */ if (iValue < 0) { return ErrorCode.BAD_VALUE; } /* return the attribute's value */ return iValue; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#deleteAttr(int, int) */ @Override public void deleteAttr(int pathId, int attrId) { /* delete the record, whether it exists in the database or not */ try { deleteFileAttrsPrepStmt.setInt(1, pathId); deleteFileAttrsPrepStmt.setInt(2, attrId); db.executePrepUpdate(deleteFileAttrsPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#deleteAllAttrOnPath(int) */ @Override public void deleteAllAttrOnPath(int pathId) { /* delete all records for this pathId (there are possibly none) */ try { deleteAllFileAttrsPrepStmt.setInt(1, pathId); db.executePrepUpdate(deleteAllFileAttrsPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getAttrsOnPath(int) */ @Override public Integer[] getAttrsOnPath(int pathId) { Integer results[]; try { findAttrsOnPathPrepStmt.setInt(1, pathId); results = db.executePrepSelectIntegerColumn(findAttrsOnPathPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return results; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getPathsWithAttr(int) */ @Override public FileSet getPathsWithAttr(int attrId) { Integer results[] = null; try { findPathsWithAttrPrepStmt.setInt(1, attrId); results = db.executePrepSelectIntegerColumn(findPathsWithAttrPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return new FileSet(fileMgr, results); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getPathsWithAttr(int, java.lang.String) */ @Override public FileSet getPathsWithAttr(int attrId, String value) { Integer results[] = null; try { findPathsWithAttrValuePrepStmt.setInt(1, attrId); findPathsWithAttrValuePrepStmt.setString(2, value); results = db.executePrepSelectIntegerColumn(findPathsWithAttrValuePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return new FileSet(fileMgr, results); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.impl.IFileAttributeMgr#getPathsWithAttr(int, int) */ @Override public FileSet getPathsWithAttr(int attrId, int value) { return getPathsWithAttr(attrId, String.valueOf(value)); } /*-------------------------------------------------------------------------------------*/ }