/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.appdef.server.session; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.TreeMap; import org.hibernate.Criteria; import org.hibernate.SessionFactory; import org.hibernate.criterion.Expression; import org.hibernate.criterion.Order; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionImplementor; import org.hibernate.id.IdentifierGenerator; import org.hyperic.hq.appdef.shared.AppdefEntityID; import org.hyperic.hq.appdef.shared.AppdefEntityNotFoundException; import org.hyperic.hq.appdef.shared.AppdefEntityValue; import org.hyperic.hq.appdef.shared.CPropKeyNotFoundException; import org.hyperic.hq.authz.shared.PermissionException; import org.hyperic.hq.dao.HibernateDAO; import org.hyperic.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import edu.emory.mathcs.backport.java.util.Arrays; @Repository public class CpropDAO extends HibernateDAO<Cprop> { private JdbcTemplate jdbcTemplate; private static final int CHUNKSIZE = 1000; // Max size for each row private static final String CPROP_TABLE = "EAM_CPROP"; private static final String CPROPKEY_TABLE = "EAM_CPROP_KEY"; private CpropKeyDAO cPropKeyDAO; @Autowired public CpropDAO(SessionFactory f, JdbcTemplate jdbcTemplate, CpropKeyDAO cPropKeyDAO) { super(Cprop.class, f); this.jdbcTemplate = jdbcTemplate; this.cPropKeyDAO = cPropKeyDAO; } @SuppressWarnings("unchecked") public List<Cprop> findByKeyName(CpropKey key, boolean asc) { Criteria c = createCriteria().add(Expression.eq("key", key)).addOrder( asc ? Order.asc("propValue") : Order.desc("propValue")); return c.list(); } public void deleteValues(final int appdefType, final int id) { jdbcTemplate.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement stmt = con.prepareStatement("DELETE FROM " + CPROP_TABLE + " WHERE keyid IN " + "(SELECT id FROM " + CPROPKEY_TABLE + " WHERE appdef_type = ?) " + "AND appdef_id = ?"); stmt.setInt(1, appdefType); stmt.setInt(2, id); return stmt; } }); } public String setValue(final AppdefEntityID aID, int typeId, String key, String val) throws CPropKeyNotFoundException, AppdefEntityNotFoundException, PermissionException { CpropKey propKey = getKey(aID, typeId, key); Integer pk = propKey.getId(); final int keyId = pk.intValue(); // no need to grab the for update since we are in a transaction // and therefore automatically get a shared lock String sql = new StringBuilder().append("SELECT PROPVALUE FROM ").append(CPROP_TABLE) .append(" WHERE KEYID=").append(keyId).append(" AND APPDEF_ID=").append(aID.getID()) .toString(); List<String> oldvals = this.jdbcTemplate.queryForList(sql,String.class); String oldval = null; if(! oldvals.isEmpty()) { oldval = oldvals.get(0); } if (val == null && oldval == null) { //We don't need to change anything here return null; } else if (val != null && val.equals(oldval)) { return val; } if (oldval != null) { String deleteSql = new StringBuilder().append("DELETE FROM ").append(CPROP_TABLE) .append(" WHERE KEYID=").append(keyId).append(" AND APPDEF_ID=") .append(aID.getID()).toString(); jdbcTemplate.update(deleteSql); } // Optionally add new values if (val != null) { final String[] chunks = chunk(val, CHUNKSIZE); StringBuilder insertSQL = new StringBuilder().append("INSERT INTO ") .append(CPROP_TABLE); final Cprop nprop = new Cprop(); insertSQL.append(" (id,keyid,appdef_id,value_idx,PROPVALUE) VALUES ").append( "(?, ?, ?, ?, ?)"); jdbcTemplate.batchUpdate(insertSQL.toString(), new BatchPreparedStatementSetter() { public void setValues(PreparedStatement pstmt, int i) throws SQLException { pstmt.setInt(2, keyId); pstmt.setInt(3, aID.getID()); int id = generateId("org.hyperic.hq.appdef.server.session.Cprop", nprop) .intValue(); pstmt.setInt(1, id); pstmt.setInt(4, i); pstmt.setString(5, chunks[i]); } public int getBatchSize() { return chunks.length; } }); } return oldval; } /** * Generate a new ID for a class of the given type. * * @param className the persisted class name, as per the .hbm descriptor: * e.g. org.hyperic.hq.appdef.server.session.CpropKey * @param o The object which will be getting the new ID * * @return an Integer id for the new object. If your class uses Long IDs * then that's too bad ... we'll have to write another method. */ private Integer generateId(String className, Object o) { SessionFactoryImplementor factImpl = (SessionFactoryImplementor)sessionFactory; IdentifierGenerator gen = factImpl.getIdentifierGenerator(className); SessionImplementor sessImpl = (SessionImplementor) factImpl.getCurrentSession(); return (Integer)gen.generate(sessImpl, o); } public String getValue(AppdefEntityValue aVal, String key) throws CPropKeyNotFoundException, AppdefEntityNotFoundException, PermissionException { final AppdefEntityID aID = aVal.getID(); AppdefResourceType recType = aVal.getAppdefResourceType(); int typeId = recType.getId().intValue(); CpropKey propKey = this.getKey(aID, typeId, key); Integer pk = propKey.getId(); final int keyId = pk.intValue(); StringBuffer buf = new StringBuffer(); List<String> propvals = this.jdbcTemplate.query(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement stmt = con.prepareStatement("SELECT PROPVALUE FROM " + CPROP_TABLE + " WHERE KEYID=? AND APPDEF_ID=? " + "ORDER BY VALUE_IDX"); stmt.setInt(1, keyId); stmt.setInt(2, aID.getID()); return stmt; } }, new RowMapper<String>() { public String mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getString(1); } }); if (propvals.isEmpty()) { return null; } for (String propval : propvals) { buf.append(propval); } return buf.toString(); } public Map<AppdefEntityID, Properties> getAllEntries(String ... keys) { if (keys == null || keys.length == 0) { return Collections.emptyMap(); } final Map<AppdefEntityID, Properties> rtn = new HashMap<AppdefEntityID, Properties>(); final String keySql = StringUtil.implode(Arrays.asList(keys), "','"); final StringBuilder sql = new StringBuilder() .append("SELECT B.appdef_id, A.appdef_type, A.propkey, B.propvalue, B.value_idx ") .append("FROM ").append(CPROPKEY_TABLE).append(" A, ").append(CPROP_TABLE).append(" B ") .append("WHERE B.keyid=A.id AND A.propkey in ('").append(keySql).append("')"); final List<Map<String, Object>> props = jdbcTemplate.queryForList(sql.toString()); final Map<AppdefEntityID, Map<String, IndexToChunk>> tmp = new HashMap<AppdefEntityID, Map<String, IndexToChunk>>(); for (Map<String,Object> prop: props) { final int id = Integer.valueOf(prop.get("appdef_id").toString()); final int appdefType = Integer.valueOf(prop.get("appdef_type").toString()); final AppdefEntityID aeid = new AppdefEntityID(appdefType, id); final String keyName = (String) prop.get("propkey"); final String chunk = (String) prop.get("propvalue"); final int index = Integer.valueOf(prop.get("value_idx").toString()); Map<String, IndexToChunk> keyToChunks = tmp.get(aeid); if (keyToChunks == null) { keyToChunks = new HashMap<String, IndexToChunk>(); tmp.put(aeid, keyToChunks); } IndexToChunk indexToChunk = keyToChunks.get(keyName); if (indexToChunk == null) { indexToChunk = new IndexToChunk(); keyToChunks.put(keyName, indexToChunk); } indexToChunk.add(index, chunk); } for (final Entry<AppdefEntityID, Map<String, IndexToChunk>> entry : tmp.entrySet()) { final AppdefEntityID aeid = entry.getKey(); final Map<String, IndexToChunk> value = entry.getValue(); for (final Entry<String, IndexToChunk> e : value.entrySet()) { String keyName = e.getKey(); String keyValue = e.getValue().toString(); Properties properties = rtn.get(aeid); if (properties == null) { properties = new Properties(); rtn.put(aeid, properties); } properties.setProperty(keyName, keyValue); } } return rtn; } private class IndexToChunk { private Map<Integer, String> indexes = new TreeMap<Integer, String>(); private void add(int index, String chunk) { indexes.put(index, chunk); } public String toString() { final StringBuilder rtn = new StringBuilder(); for (String chunk : indexes.values()) { rtn.append(chunk); } return rtn.toString(); } } public Properties getEntries(AppdefEntityID aID, String column) { Properties res = new Properties(); List<Map<String, Object>> props = jdbcTemplate.queryForList( "SELECT A." + column + ", B.propvalue FROM " + CPROPKEY_TABLE + " A, " + CPROP_TABLE + " B WHERE " + "B.keyid=A.id AND A.appdef_type=? " + "AND B.appdef_id=? " + "ORDER BY B.value_idx",aID.getType(),aID.getId()); //Props share the same value_idx when chunked, so there is no guarantee that //you don't end up with the ordered set being propA.chunk0,propB.chunk0,propA.chunk1 Map<String,StringBuilder> propChunks = new HashMap<String,StringBuilder>(); for(Map<String,Object> prop: props) { String keyName = (String)prop.get(column); String valChunk = (String)prop.get("propvalue"); StringBuilder fullVal = propChunks.get(keyName); if(fullVal == null) { fullVal = new StringBuilder(); } fullVal.append(valChunk); propChunks.put(keyName, fullVal); } for(Map.Entry<String,StringBuilder> propChunk :propChunks.entrySet()) { res.setProperty(propChunk.getKey(), propChunk.getValue().toString()); } return res; } /** * Split a string into a list of same sized chunks, and a chunk of * potentially different size at the end, which contains the remainder. * * e.g. chunk("11223", 2) -> { "11", "22", "3" } * * @param src String to chunk * @param chunkSize The max size of any chunk * * @return an array containing the chunked string */ private String[] chunk(String src, int chunkSize) { String[] res; int strLen, nAlloc; if (chunkSize <= 0) { throw new IllegalArgumentException("chunkSize must be >= 1"); } strLen = src.length(); nAlloc = strLen / chunkSize; if ((strLen % chunkSize) != 0) { nAlloc++; } res = new String[nAlloc]; for (int i = 0; i < nAlloc; i++) { int begIdx, endIdx; begIdx = i * chunkSize; endIdx = (i + 1) * chunkSize; if (endIdx > strLen) endIdx = strLen; res[i] = src.substring(begIdx, endIdx); } return res; } private CpropKey getKey(AppdefEntityID aID, int typeId, String key) throws CPropKeyNotFoundException, AppdefEntityNotFoundException, PermissionException { CpropKey res = cPropKeyDAO.findByKey(aID.getType(), typeId, key); if (res == null) { String msg = "Key, '" + key + "', does " + "not exist for aID=" + aID + ", typeId=" + typeId; throw new CPropKeyNotFoundException(msg); } return res; } }