/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/kernel/branches/SAK-18678/api/src/main/java/org/sakaiproject/site/api/Site.java $ * $Id: Site.java 81275 2010-08-14 09:24:56Z david.horwitz@uct.ac.za $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2008 Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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 org.sakaiproject.messagebundle.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.messagebundle.api.MessageBundleService; import org.sakaiproject.messagebundle.api.MessageBundleProperty; import org.hibernate.*; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; import org.springframework.beans.BeanUtils; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import java.sql.SQLException; import java.util.*; import java.util.Map.Entry; /** * Responsible for managing the message bundle data in a database. Provides search capabilities * for finding keys based on values. * * Created by IntelliJ IDEA. * User: jbush * Date: Mar 16, 2010 * Time: 1:38:05 PM * To change this template use File | Settings | File Templates. */ public class MessageBundleServiceImpl extends HibernateDaoSupport implements MessageBundleService { private static Log logger = LogFactory.getLog(MessageBundleServiceImpl.class); /** * list of bundles that we've already indexed, only want to update once per startup */ private Set<String> indexedList = new HashSet<String>(); /** * number of millis before running saveOrUpdateTask again to clear queue */ private long scheduleDelay = 5000; /** * whether or not to save bundle data right away, or queue up for processing in another thread. */ private boolean scheduleSaves = true; /** * Timer used to schedule processing */ private Timer timer = new Timer(true); /** * Queue of method invocations to save or update message bundle data */ private List queue = Collections.synchronizedList(new ArrayList()); public void init() { timer.schedule(new SaveOrUpdateTask(), 0, scheduleDelay); } public int getSearchCount(String searchQuery, String module, String baseName, String locale) { List<String> values = new ArrayList<String>(); List<BasicType> types = new ArrayList<BasicType>(); StringBuffer queryString = new StringBuffer(""); try { if (searchQuery != null && searchQuery.length() > 0) { queryString.append("(defaultValue like ? OR value like ? OR propertyName = ?)"); values.add("%" + searchQuery + "%"); values.add("%" + searchQuery + "%"); values.add(searchQuery); types.add(StandardBasicTypes.STRING); types.add(StandardBasicTypes.STRING); types.add(StandardBasicTypes.STRING); } if (module != null && module.length() > 0) { if (queryString.length() > 0) { queryString.append(" AND "); } queryString.append("moduleName = ? "); values.add(module); types.add(StandardBasicTypes.STRING); } if (baseName != null && baseName.length() > 0) { if (queryString.length() > 0) { queryString.append(" AND "); } queryString.append("baseName = ?"); values.add(baseName); types.add(StandardBasicTypes.STRING); } if (locale != null && locale.length() > 0) { if (queryString.length() > 0) { queryString.append(" AND "); } queryString.append("locale = ?"); values.add(locale); types.add(StandardBasicTypes.STRING); } if (queryString.length() > 0) { queryString.insert(0, "select count(*) from MessageBundleProperty where "); } else { queryString.insert(0, "select count(*) from MessageBundleProperty"); } Integer count = null; try { Query query = getSession().createQuery(queryString.toString()); query.setParameters(values.toArray(), (Type[]) types.toArray(new Type[types.size()])); count= (Integer) query.uniqueResult(); } catch (HibernateException e) { throw new RuntimeException(e.getMessage(), e); } return count.intValue(); } catch (Exception e) { logger.error("problem searching the message bundle data", e); } return 0; } /** * schedule timer task to save/update the bundle data. We are using Timer to offload the work, * otherwise intial loads of tools will appear very slow, this way it happens in the background. * In the original rSmart impl JMS was used, but since the MessageService is in contrib not core * we need another solution to avoid that dependency. Currently we are using a java.util.Timer and * scheduled Timer task to queue up and process the calls to this method. This is a similar * strategy to that used in BaseDigestService. * * @param baseName * @param moduleName * @param newBundle * @param loc */ public void saveOrUpdate(String baseName, String moduleName, ResourceBundle newBundle, Locale loc) { String keyName = getIndexKeyName(baseName, moduleName, loc.toString()); if (indexedList.contains(keyName)) { logger.debug("skip saveOrUpdate() as its already happened once for :" + keyName); return; } if (scheduleSaves) { queueBundle(baseName, moduleName, convertResourceBundleToMap(newBundle),loc); } else { saveOrUpdateInternal(baseName, moduleName, convertResourceBundleToMap(newBundle), loc.toString()); } } public void saveOrUpdate(String baseName, String moduleName, ResourceBundle newBundle, Locale loc, boolean newThread) { if (newThread && scheduleSaves) { saveOrUpdate(baseName, moduleName, newBundle, loc); } else { saveOrUpdateInternal(baseName, moduleName, convertResourceBundleToMap(newBundle), loc.toString()); } } /** * internal work for responding to a save or update request. This method will add new bundles data * if it doesn't exist, otherwise updates the data preserving any current value if its been modified. * This approach allows for upgrades to automatically detect and persist new keys, as well as updating * any default values that flow in from an upgrade. * @param baseName * @param moduleName * @param newBundle * @param loc */ protected void saveOrUpdateInternal(String baseName, String moduleName, Map<String, String> newBundle, String loc) { String keyName = getIndexKeyName(baseName, moduleName, loc); if (indexedList.contains(keyName)) { logger.debug("skip saveOrUpdate() as its already happened once for :" + keyName); return; } Set<Entry<String, String>> entrySet = newBundle.entrySet(); Iterator<Entry<String, String>> entries = entrySet.iterator(); while (entries.hasNext()) { Entry<String, String> entry = entries.next(); String key = entry.getKey(); MessageBundleProperty mbp = new MessageBundleProperty(); mbp.setBaseName(baseName); mbp.setModuleName(moduleName); mbp.setLocale(loc.toString()); mbp.setPropertyName(key); mbp.setDefaultValue(entry.getValue()); MessageBundleProperty existingMbp = getProperty(mbp); if (existingMbp != null) { //don"t update id or value, we don't want to loose that data BeanUtils.copyProperties(mbp, existingMbp, new String[]{"id","value"}); logger.debug("updating message bundle data for : " + getIndexKeyName(mbp.getBaseName(),mbp.getModuleName(), mbp.getLocale())); updateMessageBundleProperty(existingMbp); } else { logger.debug("adding message bundle data for : " + getIndexKeyName(mbp.getBaseName(),mbp.getModuleName(), mbp.getLocale())); updateMessageBundleProperty(mbp); } } indexedList.add(getIndexKeyName(baseName, moduleName, loc)); } @SuppressWarnings("unchecked") public List<MessageBundleProperty> search(String searchQuery, String module, String baseName, String locale) { List<String> values = new ArrayList<String>(); StringBuffer queryString = new StringBuffer(""); try { if (searchQuery != null && searchQuery.length() > 0) { queryString.append("(defaultValue like ? OR value like ? OR propertyName = ?)"); values.add("%" + searchQuery + "%"); values.add("%" + searchQuery + "%"); values.add(searchQuery); } if (module != null && module.length() > 0) { if (queryString.length() > 0) { queryString.append(" AND "); } queryString.append("moduleName = ? "); values.add(module); } if (baseName != null && baseName.length() > 0) { if (queryString.length() > 0) { queryString.append(" AND "); } queryString.append("baseName = ?"); values.add(baseName); } if (locale != null && locale.length() > 0) { if (queryString.length() > 0) { queryString.append(" AND "); } queryString.append("locale = ?"); values.add(locale); } if (queryString.length() > 0) { queryString.insert(0, "from MessageBundleProperty where "); } else { queryString.insert(0, "from MessageBundleProperty"); } return getHibernateTemplate().find(queryString.toString(), values.toArray() ); } catch (Exception e) { logger.error("problem searching the message bundle data", e); } return new ArrayList<MessageBundleProperty>(); } private Map<String, String> convertResourceBundleToMap(ResourceBundle resource) { Map<String, String> map = new HashMap<String, String>(); Enumeration<String> keys = resource.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); map.put(key, resource.getString(key)); } return map; } public MessageBundleProperty getMessageBundleProperty(long id) { return (MessageBundleProperty) getHibernateTemplate().get(MessageBundleProperty.class, id); } public void updateMessageBundleProperty(MessageBundleProperty mbp) { if (mbp.getDefaultValue() == null) { mbp.setDefaultValue(""); } getHibernateTemplate().saveOrUpdate(mbp); } public void deleteMessageBundleProperty(MessageBundleProperty mbp) { getHibernateTemplate().delete(mbp); } public MessageBundleProperty getProperty(MessageBundleProperty mbp) { Object[] values = new Object[]{mbp.getBaseName(), mbp.getModuleName(), mbp.getPropertyName(), mbp.getLocale()}; String sql = "from MessageBundleProperty where baseName = ? and moduleName = ? and propertyName = ? and locale = ?"; List<?> results = getHibernateTemplate().find(sql, values ); if (results.size() == 0) { logger.debug("can't find a default value for : " + mbp); return null; } return (MessageBundleProperty) results.get(0); } public Map<String,String> getBundle(String baseName, String moduleName, Locale loc) { Object[] values = new Object[]{baseName, moduleName, loc.toString()}; String sql = "from MessageBundleProperty where baseName = ? and moduleName = ? and locale = ? and value != null"; List<?> results = getHibernateTemplate().find(sql, values ); Map<String,String> map = new HashMap<String,String>(); if (results.size() == 0) { logger.debug("can't find any overridden values for: " + getIndexKeyName(baseName, moduleName, loc.toString())); return map; } for (Iterator<?> i=results.iterator();i.hasNext();) { MessageBundleProperty mbp = (MessageBundleProperty) i.next(); map.put(mbp.getPropertyName(), mbp.getValue()); } return map; } protected String getIndexKeyName(String baseName, String moduleName, String loc) { return moduleName + "_" + baseName + "_" +loc; } public int getModifiedPropertiesCount() { String query = "select count(*) from MessageBundleProperty where value != null"; return executeCountQuery(query); } @SuppressWarnings("unchecked") public List<MessageBundleProperty> getAllProperties(String locale, String module) { if ((locale == null || locale.length() == 0) && (module == null || module.length() == 0)) { return getHibernateTemplate().find("from MessageBundleProperty"); } else if (module == null || module.length() == 0) { return getHibernateTemplate().find("from MessageBundleProperty where locale = ?", locale); } else if (locale == null || locale.length() == 0) { return getHibernateTemplate().find("from MessageBundleProperty where moduleName = ?", module); } else { return getHibernateTemplate().find("from MessageBundleProperty where locale = ? and moduleName = ?", new String[]{locale, module}); } } public int revertAll(final String locale) { HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(org.hibernate.Session session) throws HibernateException, SQLException { String hql = "update MessageBundleProperty set value = null where locale = :locale"; org.hibernate.Query query = session.createQuery(hql); query.setString("locale", locale); int rowCount = query.executeUpdate(); return Integer.valueOf(rowCount); } }; try { return ((Integer) getHibernateTemplate().execute(callback)).intValue(); } catch (HibernateObjectRetrievalFailureException e) { logger.debug("",e); } return 0; } public int importProperties(List<MessageBundleProperty> properties) { int rows = 0; for (MessageBundleProperty property: properties) { MessageBundleProperty loadedMbp = getProperty(property); if (loadedMbp != null) { BeanUtils.copyProperties(property, loadedMbp, new String[]{"id"}); updateMessageBundleProperty(loadedMbp); } else { updateMessageBundleProperty(property); } rows++; } return rows; } @SuppressWarnings("unchecked") public List<String> getAllModuleNames() { List<String> retValue = getHibernateTemplate().find("select distinct(moduleName) from MessageBundleProperty order by moduleName"); if (retValue == null) return new ArrayList(); //force deep load retValue.size(); return retValue; } @SuppressWarnings("unchecked") public List<String> getAllBaseNames() { List<String> retValue = getHibernateTemplate().find("select distinct(baseName) from MessageBundleProperty order by baseName"); if (retValue == null) return new ArrayList(); //force deep load retValue.size(); return retValue; } public void revert(MessageBundleProperty mbp) { mbp.setValue(null); getHibernateTemplate().update(mbp); } protected int executeCountQuery(String query) { Integer count = null; try { count = (Integer) getSession().createQuery(query).uniqueResult(); } catch (HibernateException e) { throw new RuntimeException(e.getMessage(),e); } return count.intValue(); } @SuppressWarnings("unchecked") public List<MessageBundleProperty> getModifiedProperties(int sortOrder, int sortField, int startingIndex, int pageSize) { String orderBy = "asc"; if (sortOrder == SORT_ORDER_DESCENDING) { orderBy = "desc"; } String sortFieldName = "id"; if (sortField == SORT_FIELD_MODULE) { sortFieldName = "moduleName"; } if (sortField == SORT_FIELD_PROPERTY) { sortFieldName = "propertyName"; } if (sortField == SORT_FIELD_LOCALE) { sortFieldName = "locale"; } if (sortField == SORT_FIELD_BASENAME) { sortFieldName = "baseName"; } org.hibernate.Query query = null; String queryString = "from MessageBundleProperty where value != null order by " + sortFieldName + " " + orderBy; try { query = getSession().createQuery(queryString); query.setFirstResult(startingIndex); query.setMaxResults(pageSize); return query.list(); } catch (HibernateException e) { throw new RuntimeException(e.getMessage(), e); } } /** * queues up a call to add or update message bundle data * @param baseName * @param moduleName * @param bundleData * @param loc */ protected void queueBundle(String baseName, String moduleName, Map<String,String> bundleData, Locale loc) { SaveOrUpdateCall call = new SaveOrUpdateCall(); call.baseName =baseName; call.moduleName = moduleName; call.bundleData = bundleData; call.loc = loc; queue.add(call); } @SuppressWarnings("unchecked") public List<String> getLocales() { return getHibernateTemplate().find("select distinct(locale) from MessageBundleProperty"); } public void setScheduleDelay(long scheduleDelay) { this.scheduleDelay = scheduleDelay; } // represents one method call, encapsulate this so we can queue them up class SaveOrUpdateCall { String baseName; String moduleName; Map<String,String> bundleData; Locale loc; } /** * */ class SaveOrUpdateTask extends TimerTask { public SaveOrUpdateTask(){ } /** * step through queue and call the real saveOrUpdateInternal method to do the work */ public void run() { List queueList = new ArrayList(queue); for (Iterator i = queueList.iterator(); i.hasNext(); ) { SaveOrUpdateCall call = (SaveOrUpdateCall) i.next(); try { saveOrUpdateInternal(call.baseName, call.moduleName, call.bundleData, call.loc.toString()); } catch (Throwable e) { logger.error("problem saving bundle data:", e); } finally { queue.remove(call); } } } } }