///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as published // by the Free Software Foundation; version 3 of the License. // // This community edition 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, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.database; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import org.projectforge.access.AccessChecker; import org.projectforge.access.AccessException; import org.projectforge.continuousdb.DatabaseUpdateDao; import org.projectforge.continuousdb.UpdateEntry; import org.projectforge.continuousdb.UpdaterConfiguration; import org.projectforge.core.BaseDO; import org.projectforge.plugins.core.AbstractPlugin; import org.projectforge.plugins.core.PluginsRegistry; import org.projectforge.registry.Registry; import org.projectforge.user.Login; import org.projectforge.user.PFUserContext; import org.projectforge.user.PFUserDO; import org.projectforge.user.ProjectForgeGroup; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; /** * For manipulating the database (patching data etc.) * @author Kai Reinhard (k.reinhard@micromata.de) * */ public class MyDatabaseUpdateDao extends DatabaseUpdateDao { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(MyDatabaseUpdateDao.class); // private static String CATALOG = null; // private static String SCHEMA_PATTERN = null; // private static String TABLE_TYPE = "TABLE"; public MyDatabaseUpdateDao(final UpdaterConfiguration configuration) { super(configuration); } private AccessChecker accessChecker; private static PFUserDO SYSTEM_ADMIN_PSEUDO_USER = new PFUserDO().setUsername("System admin user only for internal usage"); public static PFUserDO __internalGetSystemAdminPseudoUser() { return SYSTEM_ADMIN_PSEUDO_USER; } @Override protected void accessCheck(final boolean writeaccess) { if (PFUserContext.getUser() == SYSTEM_ADMIN_PSEUDO_USER) { // No access check for the system admin pseudo user. return; } if (Login.getInstance().isAdminUser(PFUserContext.getUser()) == false) { throw new AccessException(AccessChecker.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, ProjectForgeGroup.ADMIN_GROUP.getKey()); } accessChecker.checkRestrictedOrDemoUser(); } /** */ public List<DatabaseUpdateDO> getUpdateHistory() { accessCheck(false); final JdbcTemplate jdbc = new JdbcTemplate(getDataSource()); final List<Map<String, Object>> dbResult = jdbc.queryForList("select * from t_database_update order by update_date desc"); final List<DatabaseUpdateDO> result = new ArrayList<DatabaseUpdateDO>(); for (final Map<String, Object> map : dbResult) { final DatabaseUpdateDO entry = new DatabaseUpdateDO(); entry.setUpdateDate((Date) map.get("update_date")); entry.setRegionId((String) map.get("region_id")); entry.setVersionString((String) map.get("version")); entry.setExecutionResult((String) map.get("execution_result")); final PFUserDO executedByUser = Registry.instance().getUserGroupCache().getUser((Integer) map.get("executed_by_user_fk")); entry.setExecutedBy(executedByUser); entry.setDescription((String) map.get("description")); result.add(entry); } return result; } /** * @see org.projectforge.continuousdb.DatabaseUpdateDao#createMissingIndices() */ @Override public int createMissingIndices() { int result = super.createMissingIndices(); if (createIndex("idx_timesheet_user_time", "t_timesheet", "user_id, start_time") == true) { ++result; } for (final AbstractPlugin plugin : PluginsRegistry.instance().getPlugins()) { if (plugin.isInitialized() == false) { // Plug-in not (yet) initialized, skip. this is normal on first start-up phase. continue; } final UpdateEntry updateEntry = plugin.getInitializationUpdateEntry(); if (updateEntry != null) { result += updateEntry.createMissingIndices(); } } return result; } /** * There is a bug for Hibernate history with Javassist: Sometimes the data base objects are serialized with the default toString() method * instead of using the plain id. This method fixes all wrong data base history entries. */ public int fixDBHistoryEntries() { accessCheck(true); return internalFixDBHistoryEntries(); } /** * Without access checking. * @see #fixDBHistoryEntries() */ @SuppressWarnings({ "unchecked", "rawtypes"}) public int internalFixDBHistoryEntries() { log.info("Fix all broken history entries (if exist)."); final int counter[] = new int[1]; counter[0] = 0; final JdbcTemplate jdbc = new JdbcTemplate(getDataSource()); try { String sql = " from t_history_property_delta where old_value like 'org.projectforge.%' or new_value like 'org.projectforge.%'"; jdbc.query("select id, old_value, new_value, property_type" + sql, new ResultSetExtractor() { @Override public Object extractData(final ResultSet rs) throws SQLException, DataAccessException { while (rs.next() == true) { final int id = rs.getInt("ID"); final String oldValue = rs.getString("OLD_VALUE"); final String newValue = rs.getString("NEW_VALUE"); final Serializable oldId = getObjectId(oldValue); final Serializable newId = getObjectId(newValue); final String propertyType = rs.getString("PROPERTY_TYPE"); final int pos = propertyType.indexOf("_$$_javassist_"); final String newPropertyType; if (pos > 0) { newPropertyType = propertyType.substring(0, pos); } else { newPropertyType = null; } if (oldId == null && newId == null) { continue; } final StringBuffer buf = new StringBuffer(); boolean first = true; buf.append("update t_history_property_delta set "); if (oldId != null) { buf.append("OLD_VALUE='").append(oldId).append("'"); first = false; } if (newId != null) { if (first == false) { buf.append(", "); } else { first = false; } buf.append("NEW_VALUE='").append(newId).append("'"); } if (newPropertyType != null) { if (first == false) { buf.append(", "); } else { first = false; } buf.append("PROPERTY_TYPE='").append(newPropertyType).append("'"); } buf.append(" where ID=").append(id); final String sql = buf.toString(); log.info(sql); jdbc.execute(sql); counter[0]++; } return null; } }); int no = jdbc.queryForInt("select count(*)" + sql); if (no > 0) { log.warn("" + no + " of data base history entries aren't fixed."); } sql = " from t_history_property_delta where property_type like '%_$$_javassist_%'"; jdbc.query("select id, property_type" + sql, new ResultSetExtractor() { @Override public Object extractData(final ResultSet rs) throws SQLException, DataAccessException { while (rs.next() == true) { final int id = rs.getInt("ID"); final String propertyType = rs.getString("PROPERTY_TYPE"); final int pos = propertyType.indexOf("_$$_javassist_"); if (pos < 0) { log.error("Oups, should not occur."); continue; } final String newPropertyType = propertyType.substring(0, pos); final String sql = "update t_history_property_delta set PROPERTY_TYPE='" + newPropertyType + "' where id=" + id; log.info(sql); jdbc.execute(sql); counter[0]++; } return null; } }); no = jdbc.queryForInt("select count(*)" + sql); if (no > 0) { log.warn("" + no + " of data base history entries aren't fixed."); } } catch (final Exception ex) { log.error(ex.getMessage(), ex); return 0; } return counter[0]; } private Serializable getObjectId(final String serializedObject) { if (serializedObject == null || serializedObject.startsWith("org.projectforge.") == false || serializedObject.indexOf('@') < 0) { return null; } final String className = serializedObject.substring(0, serializedObject.indexOf('@')); Class< ? > clazz = null; try { clazz = Class.forName(className); } catch (final ClassNotFoundException ex) { log.error("Can't load class. " + ex, ex); return null; } if (BaseDO.class.isAssignableFrom(clazz) == false) { log.error("Unsupported class: " + clazz); } final Integer id = DatabaseUpdateHelper.getId(serializedObject); return id; } public void setAccessChecker(final AccessChecker accessChecker) { this.accessChecker = accessChecker; } }