/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You 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 com.esri.gpt.catalog.arcims; import com.esri.gpt.catalog.context.CatalogIndexException; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.security.identity.IdentityAdapter; import com.esri.gpt.framework.security.principal.Group; import com.esri.gpt.framework.security.principal.Publisher; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.sql.BaseDao; import com.esri.gpt.framework.sql.IClobMutator; import com.esri.gpt.framework.sql.ManagedConnection; import com.esri.gpt.framework.util.Val; import java.io.IOException; import java.sql.*; import java.util.logging.Level; import java.util.logging.Logger; /** * Manages ArcIMS Metadata Server like tables in the absence of the metadata * server. */ public class ImsMetadataProxyDao extends BaseDao { /** * class variables ========================================================= */ private static final Logger LOGGER = Logger.getLogger(ImsMetadataProxyDao.class.getName()); /** * instance variables ====================================================== */ private Publisher publisher; private boolean groupsLoaded; /** * constructors ============================================================ */ /** * Constructs with an associated request context. * * @param requestContext the request context */ public ImsMetadataProxyDao(RequestContext requestContext, Publisher publisher) { super(requestContext); this.publisher = publisher; } /** * properties ============================================================== */ private String getResourceTableName() { return getRequestContext().getCatalogConfiguration().getResourceTableName(); } private String getResourceDataTableName() { return getRequestContext().getCatalogConfiguration().getResourceDataTableName(); } /** * methods ================================================================= */ /** * Authorizes a request. * * @param request the underlying request * @param the UUID of the subject record * @throws ImsServiceException typically related to an authorization related * exception * @throws SQLException if a database exception occurs */ private void authorize(ImsRequest request, String uuid) throws ImsServiceException, SQLException { boolean checkOwner = false; if (request instanceof PutMetadataRequest) { checkOwner = true; } else if (request instanceof GetDocumentRequest) { if (!this.publisher.getIsAdministrator()) { checkOwner = true; } } else if (request instanceof DeleteMetadataRequest) { if (!this.publisher.getIsAdministrator()) { checkOwner = true; } } else if (request instanceof TransferOwnershipRequest) { if (!this.publisher.getIsAdministrator()) { throw new ImsServiceException("TransferOwnershipRequest: not authorized."); } } if (checkOwner) { int ownerID = this.queryOwnerByUuid(uuid); if ((ownerID != -1) && (ownerID != this.publisher.getLocalID())) { String username = Val.chkStr(queryUsernameByID(ownerID)); if (!belongsToTheGroup(username)) { String msg = "The document is owned by another user: " + username + ", " + uuid; throw new ImsServiceException(msg); } } } } /** * Deletes a record from the proxied ArcIMS metadata table. * * @param request the underlying request * @param uuid the UUID for the record to delete * @return the number of rows affected * @throws ImsServiceException typically related to an authorization related * exception * @throws SQLException if a database exception occurs */ protected int deleteRecord(DeleteMetadataRequest request, String uuid) throws ImsServiceException, SQLException { Connection con = null; boolean autoCommit = true; PreparedStatement st = null; int nRows = 0; try { this.authorize(request, uuid); ImsMetadataAdminDao adminDao = new ImsMetadataAdminDao(getRequestContext()); nRows = adminDao.deleteRecord(uuid); } catch (CatalogIndexException ex) { if (con != null) { con.rollback(); } throw new ImsServiceException(ex.getMessage(), ex); } catch (ImsServiceException ex) { if (con != null) { con.rollback(); } throw ex; } catch (SQLException ex) { if (con != null) { con.rollback(); } throw ex; } finally { closeStatement(st); if (con != null) { con.setAutoCommit(autoCommit); } } return nRows; } /** * Determines if a document UUID exists within the proxied ArcIMS metadata * table. * * @param con the JDBC connection * @param uuid the document UUID to check * @return true if the document UUID exists * @throws SQLException if a database exception occurs */ private long doesRecordExist(String table, String uuid) throws SQLException { long id = -1; PreparedStatement st = null; try { Connection con = returnConnection().getJdbcConnection(); String sSql = "SELECT ID FROM " + table + " WHERE DOCUUID=?"; logExpression(sSql); st = con.prepareStatement(sSql); st.setString(1, uuid); ResultSet rs = st.executeQuery(); if (rs.next()) { id = rs.getLong(1); } } finally { closeStatement(st); } return id; } /** * Inserts or updates a record within the proxied ArcIMS metadata table. * * @param request the underlying request * @param info the information for the document to be inserted * @return the number of rows affected * @throws ImsServiceException typically related to an authorization related * exception * @throws SQLException if a database exception occurs */ protected int insertRecord(PutMetadataRequest request, PutMetadataInfo info) throws ImsServiceException, SQLException { Connection con = null; boolean autoCommit = true; PreparedStatement st = null; ResultSet rs = null; // initialize int nRows = 0; String sXml = info.getXml(); String sUuid = info.getUuid(); String sName = info.getName(); String sThumbnailBinary = info.getThumbnailBinary(); String sTable = this.getResourceTableName(); String sDataTable = this.getResourceDataTableName(); long id = doesRecordExist(sTable, sUuid); try { ManagedConnection mc = returnConnection(); con = mc.getJdbcConnection(); autoCommit = con.getAutoCommit(); con.setAutoCommit(false); if (id < 0) { // insert a record StringBuffer sql = new StringBuffer(); sql.append("INSERT INTO ").append(sTable); sql.append(" ("); sql.append("DOCUUID,"); sql.append("TITLE,"); sql.append("OWNER"); sql.append(")"); sql.append(" VALUES(?,?,?)"); logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); int n = 1; st.setString(n++, sUuid); st.setString(n++, sName); st.setInt(n++, this.publisher.getLocalID()); nRows = st.executeUpdate(); closeStatement(st); if (nRows > 0) { if (getIsDbCaseSensitive(this.getRequestContext())) { st = con.prepareStatement("SELECT id FROM " + sTable + " WHERE UPPER(docuuid)=?"); } else { st = con.prepareStatement("SELECT id FROM " + sTable + " WHERE docuuid=?"); } st.setString(1, sUuid.toUpperCase()); rs = st.executeQuery(); rs.next(); id = rs.getLong(1); closeStatement(st); request.setActionStatus(ImsRequest.ACTION_STATUS_OK); sql = new StringBuffer(); sql.append("INSERT INTO ").append(sDataTable); sql.append(" (DOCUUID,ID,XML)"); sql.append(" VALUES(?,?,?)"); logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); st.setString(1, sUuid); st.setLong(2, id); st.setString(3, sXml); st.executeUpdate(); } } else { // update a record this.authorize(request, sUuid); StringBuffer sql = new StringBuffer(); sql.append("UPDATE ").append(sTable); sql.append(" SET "); if (!request.getLockTitle()) { // update title only if it's allowed to (occurs only during synchronization) sql.append("TITLE=?, "); } sql.append("OWNER=?, "); sql.append("UPDATEDATE=?"); sql.append(" WHERE DOCUUID=?"); logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); int n = 1; if (!request.getLockTitle()) { // update title only if it's allowed to (occurs only during synchronization) st.setString(n++, sName); } st.setInt(n++, this.publisher.getLocalID()); st.setTimestamp(n++, new Timestamp(System.currentTimeMillis())); st.setString(n++, sUuid); nRows = st.executeUpdate(); if (nRows > 0) { request.setActionStatus(ImsRequest.ACTION_STATUS_REPLACED); } closeStatement(st); sql = new StringBuffer(); if (doesRecordExist(sDataTable, sUuid) >= 0) { sql.append("UPDATE ").append(sDataTable); sql.append(" SET DOCUUID=?, XML=?, THUMBNAIL=?"); sql.append(" WHERE ID=?"); } else { sql.append("INSERT INTO ").append(sDataTable); sql.append(" (DOCUUID, XML,THUMBNAIL,ID)"); sql.append(" VALUES(?,?,?,?)"); } logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); st.setString(1, sUuid); st.setString(2, sXml); st.setBytes(3, null); st.setLong(4, id); st.executeUpdate(); } con.commit(); } catch (ImsServiceException ex) { if (con != null) { con.rollback(); } throw ex; } catch (SQLException ex) { if (con != null) { con.rollback(); } throw ex; } finally { closeResultSet(rs); closeStatement(st); if (con != null) { con.setAutoCommit(autoCommit); } } // thumbnail binary if ((sThumbnailBinary != null) && (sThumbnailBinary.length() > 0)) { this.updateThumbnail(sThumbnailBinary, sUuid); } return nRows; } /** * Queries the owner id associated with a document UUID. * * @param uuid the document UUID * @return the owner name (empty string if not found) * @throws SQLException if a database exception occurs */ private int queryOwnerByUsername(String username) throws SQLException { int ownerID = -1; PreparedStatement st = null; try { username = Val.chkStr(username); String table = this.getRequestContext().getCatalogConfiguration().getUserTableName(); if (username.length() > 0) { Connection con = returnConnection().getJdbcConnection(); StringBuilder sql = new StringBuilder(); sql.append("SELECT USERID FROM ").append(table); if (getIsDbCaseSensitive(this.getRequestContext())) { sql.append(" WHERE UPPER(USERNAME)=?"); } else { sql.append(" WHERE USERNAME=?"); } logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); st.setString(1, username.toUpperCase()); ResultSet rs = st.executeQuery(); if (rs.next()) { ownerID = rs.getInt(1); } } } finally { closeStatement(st); } return ownerID; } /** * Queries the owner id associated with a document UUID. * * @param uuid the document UUID * @return the owner name (empty string if not found) * @throws SQLException if a database exception occurs */ private int queryOwnerByUuid(String uuid) throws SQLException { int ownerID = -1; PreparedStatement st = null; try { uuid = Val.chkStr(uuid); if (uuid.length() > 0) { Connection con = returnConnection().getJdbcConnection(); StringBuilder sql = new StringBuilder(); sql.append("SELECT OWNER FROM ").append(getResourceTableName()); sql.append(" WHERE DOCUUID=?"); logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); st.setString(1, uuid); ResultSet rs = st.executeQuery(); if (rs.next()) { ownerID = rs.getInt(1); } } } finally { closeStatement(st); } return ownerID; } /** * Queries the owner id associated with a document UUID. * * @param uuid the document UUID * @return the owner name (empty string if not found) * @throws SQLException if a database exception occurs */ private String queryUsernameByID(int userID) throws SQLException { String username = ""; PreparedStatement st = null; try { String table = this.getRequestContext().getCatalogConfiguration().getUserTableName(); Connection con = returnConnection().getJdbcConnection(); StringBuilder sql = new StringBuilder(); sql.append("SELECT USERNAME FROM ").append(table); sql.append(" WHERE USERID=?"); logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); st.setInt(1, userID); ResultSet rs = st.executeQuery(); if (rs.next()) { username = rs.getString(1); } } finally { closeStatement(st); } return username; } /** * Reads a record from the proxied ArcIMS metadata table. * * @param request the underlying request * @param uuid the UUID for the record to read * @throws ImsServiceException typically related to an authorization related * exception * @throws SQLException if a database exception occurs */ protected void readRecord(GetDocumentRequest request, String uuid) throws ImsServiceException, SQLException { PreparedStatement st = null; try { this.authorize(request, uuid); ManagedConnection mc = returnConnection(); Connection con = mc.getJdbcConnection(); IClobMutator cm = mc.getClobMutator(); StringBuffer sql = new StringBuffer(); sql.append("SELECT UPDATEDATE"); sql.append(" FROM ").append(getResourceTableName()).append(" WHERE DOCUUID=?"); logExpression(sql.toString()); st = con.prepareStatement(sql.toString()); st.setString(1, uuid); ResultSet rs = st.executeQuery(); if (rs.next()) { request.setUpdateTimestamp(rs.getTimestamp(1)); request.setActionStatus(ImsRequest.ACTION_STATUS_OK); closeStatement(st); sql = new StringBuffer(); sql.append("SELECT XML"); sql.append(" FROM ").append(getResourceDataTableName()).append(" WHERE DOCUUID=?"); st = con.prepareStatement(sql.toString()); st.setString(1, uuid); rs = st.executeQuery(); if (rs.next()) { request.setXml(cm.get(rs, 1)); } } } finally { closeStatement(st); } } /** * Transfers ownership for a record in the proxied ArcIMS metadata table. * * @param request the underlying request * @param uuid the UUID for the record to read * @return the number of rows affected * @throws ImsServiceException typically related to an authorization related * exception * @throws SQLException if a database exception occurs */ protected int transferOwnership(TransferOwnershipRequest request, String uuid, String newOwner) throws ImsServiceException, SQLException { PreparedStatement st = null; try { this.authorize(request, uuid); int ownerID = this.queryOwnerByUsername(newOwner); if (ownerID == -1) { throw new ImsServiceException("Unrecognized publisher: " + newOwner); } StringBuilder sql = new StringBuilder(); sql.append("UPDATE ").append(this.getResourceTableName()); sql.append(" SET OWNER=? WHERE DOCUUID=?"); logExpression(sql.toString()); Connection con = returnConnection().getJdbcConnection(); st = con.prepareStatement(sql.toString()); st.setInt(1, ownerID); st.setString(2, uuid); int nRows = st.executeUpdate(); if (nRows > 0) { request.setActionStatus(ImsRequest.ACTION_STATUS_REPLACED); } return nRows; } finally { closeStatement(st); } } private void updateThumbnail(String base64Thumbnail, String uuid) { PreparedStatement st = null; try { byte[] bytes = (new sun.misc.BASE64Decoder()).decodeBuffer(base64Thumbnail); StringBuilder sql = new StringBuilder(); sql.append("UPDATE ").append(this.getResourceDataTableName()); sql.append(" SET THUMBNAIL=? WHERE DOCUUID=?"); logExpression(sql.toString()); Connection con = returnConnection().getJdbcConnection(); st = con.prepareStatement(sql.toString()); st.setBytes(1, bytes); st.setString(2, uuid); st.executeUpdate(); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Error converting base64 thumbnail to bytes.", e); } catch (SQLException e) { LOGGER.log(Level.SEVERE, "Error saving thumbnail blob to database.", e); } finally { closeStatement(st); } } /** * Checks if publisher belongs to the group * @param groupName group name * @return <code>true</code> if publisher belongs to the group * @throws ImsServiceException * @throws SQLException */ private boolean belongsToTheGroup(String groupName) throws ImsServiceException, SQLException { assertUserGroups(); boolean isPartOfTheGroup = false; for (Group grp : publisher.getGroups().values()) { if (groupName.equals(grp.getName())) { isPartOfTheGroup = true; break; } } return isPartOfTheGroup; } /** * Asserts user's groups * * @throws SQLException if accessing database fails * @throws ImsServiceException if any other problem occurs */ private void assertUserGroups() throws ImsServiceException, SQLException { if (!groupsLoaded) { if (publisher.getGroups().isEmpty()) { loadUserGroups(publisher); } groupsLoaded = true; } } /** * Loads user's groups * * @param user user * @throws SQLException if accessing database fails * @throws ImsServiceException if any other problem occurs */ private void loadUserGroups(User user) throws SQLException, ImsServiceException { try { RequestContext requestContext = RequestContext.extract(null); IdentityAdapter identityAdapter = requestContext.newIdentityAdapter(); identityAdapter.readUserGroups(user); } catch (Exception ex) { throw new ImsServiceException("Error evaluation asserting groups."); } } }