///////////////////////////////////////////////////////////////////////////// // // 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.user; import java.util.Date; import java.util.List; import java.util.Map; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.Validate; import org.projectforge.access.AccessChecker; import org.projectforge.common.GZIPHelper; import org.projectforge.common.XStreamHelper; import org.projectforge.core.BaseDO; import org.projectforge.core.BaseDao; import org.projectforge.task.TaskDO; import org.projectforge.task.TaskDao; import org.projectforge.task.TaskFilter; import org.projectforge.timesheet.TimesheetPrefData; import org.projectforge.web.scripting.RecentScriptCalls; import org.projectforge.web.scripting.ScriptCallData; import org.projectforge.xstream.JodaDateMidnightConverter; import org.projectforge.xstream.JodaDateTimeConverter; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.thoughtworks.xstream.XStream; /** * Stores all user persistent objects such as filter settings, personal settings and persists them to the database as xml (compressed (gzip * and base64) for larger xml content). * * @author Kai Reinhard (k.reinhard@micromata.de) * */ @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public class UserXmlPreferencesDao extends HibernateDaoSupport { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(UserXmlPreferencesDao.class); private AccessChecker accessChecker; private UserDao userDao; private final XStream xstream; public UserXmlPreferencesDao() { xstream = XStreamHelper.createXStream(); xstream.processAnnotations(new Class< ? >[] { UserXmlPreferencesMap.class, TaskFilter.class, TimesheetPrefData.class, ScriptCallData.class, RecentScriptCalls.class}); registerConverter(UserDao.class, PFUserDO.class, 20); registerConverter(GroupDao.class, GroupDO.class, 19); registerConverter(TaskDao.class, TaskDO.class, 18); xstream.registerConverter(new JodaDateTimeConverter()); xstream.registerConverter(new JodaDateMidnightConverter()); } /** * Process the given classes before marshaling and unmarshaling by XStream. This method is usable by plugins. * @param classes */ public void processAnnotations(final Class< ? >... classes) { xstream.processAnnotations(classes); } /** * Register converters before marshaling and unmarshaling by XStream. This method is usable by plugins. * @param daoClass Class of the dao. * @param doClass Class of the DO which will be converted. * @param priority The priority needed by xtream for using converters in the demanded order. * @see UserXmlPreferencesBaseDOSingleValueConverter#UserXmlPreferencesBaseDOSingleValueConverter(Class, Class) */ public void registerConverter(final Class< ? extends BaseDao< ? >> daoClass, final Class< ? extends BaseDO< ? >> doClass, final int priority) { xstream.registerConverter(new UserXmlPreferencesBaseDOSingleValueConverter(daoClass, doClass), priority); } public void setAccessChecker(final AccessChecker accessChecker) { this.accessChecker = accessChecker; } /** * @param userDao the userDao to set */ public void setUserDao(final UserDao userDao) { this.userDao = userDao; } /** * Throws AccessException if the context user is not admin user and not owner of the UserXmlPreferences, meaning the given userId must be * the id of the context user. * @param userId */ private UserXmlPreferencesDO getUserPreferencesByUserId(final Integer userId, final String key, final boolean checkAccess) { if (checkAccess == true) { checkAccess(userId); } @SuppressWarnings("unchecked") final List<UserXmlPreferencesDO> list = getHibernateTemplate().find("from UserXmlPreferencesDO u where u.user.id = ? and u.key = ?", new Object[] { userId, key}); Validate.isTrue(list.size() <= 1); if (list.size() == 1) { return list.get(0); } else return null; } /** * Throws AccessException if the context user is not admin user and not owner of the UserXmlPreferences, meaning the given userId must be * the id of the context user. * @param userId */ @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public List<UserXmlPreferencesDO> getUserPreferencesByUserId(final Integer userId) { checkAccess(userId); @SuppressWarnings("unchecked") final List<UserXmlPreferencesDO> list = getHibernateTemplate().find("from UserXmlPreferencesDO u where u.user.id = ?", userId); return list; } /** * Checks if the given userIs is equals to the context user or the if the user is an admin user. If not a AccessException will be thrown. * @param userId */ public void checkAccess(final Integer userId) { Validate.notNull(userId); final PFUserDO user = PFUserContext.getUser(); if (ObjectUtils.equals(userId, user.getId()) == false) { accessChecker.checkIsLoggedInUserMemberOfAdminGroup(); } } /** * Here you can update user preferences formats by manipulation the stored xml string. * @param userPrefs * @param logError */ public Object deserialize(final UserXmlPreferencesDO userPrefs, final boolean logError) { String xml = null; try { UserXmlPreferencesMigrationDao.migrate(userPrefs); xml = userPrefs.getSerializedSettings(); if (xml == null || xml.length() == 0) { return null; } if (xml.startsWith("!") == true) { // Uncompress value: final String uncompressed = GZIPHelper.uncompress(xml.substring(1)); xml = uncompressed; } final Object value = XStreamHelper.fromXml(xstream, xml); return value; } catch (final Throwable ex) { if (logError == true) { log.warn("Can't deserialize user preferences: " + ex.getMessage() + " for user: " + userPrefs.getUserId() + ":" + userPrefs.getKey() + " (may-be ok after a new ProjectForge release). xml=" + xml); } return null; } } public String serialize(final UserXmlPreferencesDO userPrefs, final Object value) { final String xml = XStreamHelper.toXml(xstream, value); if (xml.length() > 1000) { // Compress value: final String compressed = GZIPHelper.compress(xml); userPrefs.setSerializedSettings("!" + compressed); } else { userPrefs.setSerializedSettings(xml); } return xml; } // REQUIRES_NEW needed for avoiding a lot of new data base connections from HibernateFilter. @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void saveOrUpdateUserEntries(final Integer userId, final UserXmlPreferencesMap data, final boolean checkAccess) { for (final Map.Entry<String, Object> prefEntry : data.getPersistentData().entrySet()) { final String key = prefEntry.getKey(); if (data.isModified(key) == true) { try { saveOrUpdate(userId, key, prefEntry.getValue(), checkAccess); } catch (final Throwable ex) { log.warn(ex.getMessage(), ex); } data.setModified(key, false); } } } /** * @param sheet * @param userId If null, then user will be set to null; * @see BaseDao#getOrLoad(Integer) */ public void setUser(final UserXmlPreferencesDO userPrefs, final Integer userId) { final PFUserDO user = userDao.getOrLoad(userId); userPrefs.setUser(user); } @Transactional(readOnly = false, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public void saveOrUpdate(final Integer userId, final String key, final Object entry, final boolean checkAccess) { if (accessChecker.isDemoUser(userId) == true) { // Do nothing. return; } boolean isNew = false; UserXmlPreferencesDO userPrefs = getUserPreferencesByUserId(userId, key, checkAccess); final Date date = new Date(); if (userPrefs == null) { isNew = true; userPrefs = new UserXmlPreferencesDO(); userPrefs.setCreated(date); userPrefs.setUser(userDao.internalGetById(userId)); userPrefs.setKey(key); } final String xml = serialize(userPrefs, entry); userPrefs.setLastUpdate(date); userPrefs.setVersion(); if (isNew == true) { if (log.isDebugEnabled() == true) { log.debug("Storing new user preference for user '" + userId + "': " + xml); } getHibernateTemplate().save(userPrefs); } else { if (log.isDebugEnabled() == true) { log.debug("Updating user preference for user '" + userPrefs.getUserId() + "': " + xml); } getHibernateTemplate().update(userPrefs); } } @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ) public void remove(final Integer userId, final String key) { if (accessChecker.isDemoUser(userId) == true) { // Do nothing. return; } final UserXmlPreferencesDO userPreferencesDO = getUserPreferencesByUserId(userId, key, true); if (userPreferencesDO != null) { getHibernateTemplate().delete(userPreferencesDO); } } }