/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 de.unioninvestment.eai.portal.portlet.crud.domain.support; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.ehcache.CacheException; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import net.sf.ehcache.constructs.blocking.BlockingCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder; import de.unioninvestment.eai.portal.portlet.crud.domain.database.ConnectionPool; import de.unioninvestment.eai.portal.portlet.crud.domain.database.ConnectionPoolFactory; @Component @SuppressWarnings("unchecked") public class DefaultQueryOptionListRepository implements QueryOptionListRepository { private static final Logger CACHE_LOGGER = LoggerFactory .getLogger("de.unioninvestment.crud2go.optionListCache"); ConnectionPoolFactory connectionPoolFactory; Ehcache underlyingCache; BlockingCache cache; @Autowired public DefaultQueryOptionListRepository( ConnectionPoolFactory connectionPoolFactory, @Qualifier("optionListCache") Ehcache cache) { this.connectionPoolFactory = connectionPoolFactory; this.underlyingCache = cache; this.cache = new BlockingCache(underlyingCache); } @Override public Map<String, String> getOptions(String dataSource, String query, boolean useCache) { if (useCache) { return getOptionsWithCaching(dataSource, query); } else { return getOptionsFromDatabase(dataSource, query); } } @Override public Map<String, String> getOptionsFromCache(String dataSource, String query) { String key = createKey(dataSource, query); Element element = underlyingCache.get(key); return element == null ? null : (Map<String, String>) element .getObjectValue(); } private Map<String, String> getOptionsWithCaching(String dataSource, String query) { Serializable key = createKey(dataSource, query); Element element = cache.get(key); // creates a lock if the element does // not exist Map<String, String> options = null; if (element == null) { try { // Value not cached - fetch it options = getOptionsFromDatabase(dataSource, query); element = new Element(key, options); CACHE_LOGGER.debug("Caching option list [{}] ({} elements)", key, options.size()); } catch (final Throwable throwable) { // Could not fetch - Ditch the entry from the cache and rethrow // release the lock you acquired element = new Element(key, null); throw new CacheException( "Could not fetch object for cache entry with key \"" + key + "\".", throwable); } finally { cache.put(element); } } else { options = (Map<String, String>) element.getObjectValue(); CACHE_LOGGER.debug("Using cached option list [{}] ({} elements)", key, options.size()); } return options; } static String createKey(String dataSource, String query) { String normalizedQuery = normalizeQuery(query); return dataSource + "|" + normalizedQuery; } private static String normalizeQuery(String query) { Iterable<String> lines = Splitter.on('\n').trimResults() .omitEmptyStrings().split(query); return Joiner.on(' ').join(lines); } private Map<String, String> getOptionsFromDatabase(String dataSource, String query) { ConnectionPool pool = connectionPoolFactory.getPool(dataSource); String nullSafeQuery = nullSafeQuery(query); final LinkedHashMap<String, String> newOptions = new LinkedHashMap<String, String>(); pool.executeWithJdbcTemplate(nullSafeQuery, new RowMapper<Object>() { @Override public Object mapRow(ResultSet rs, int rowNum) throws SQLException { newOptions.put(rs.getString("key"), rs.getString("title")); return null; } }); return newOptions; } /** * Sorgt dafür, dass keine leeren Keys oder Values in der Liste stehen. * * @param query * Abfrage * @return NULL-Sichere-Abfrage */ static String nullSafeQuery(String query) { return MessageFormat .format("select {0},{1} from ({2}) opt where {0} is not null and {1} is not null", QueryBuilder.quote("KEY"), QueryBuilder.quote("TITLE"), query); } @Override public boolean isQueryInCache(String dataSource, String query) { return cache.isKeyInCache(createKey(dataSource, query)); } @Override public boolean remove(String datasSource, String query) { String key = createKey(datasSource, query); boolean removed = cache.remove(key); if (removed) { CACHE_LOGGER.debug("Removed option list from cache [{}]", key); } return removed; } public int removeAll(String dataSource, Pattern pattern) { List<Object> keysToRemove = findAllMatchingKeys(dataSource, pattern); int count = 0; for (Object cacheKey : keysToRemove) { boolean removed = cache.remove(cacheKey); if (removed) { count++; CACHE_LOGGER.debug("Removed option list from cache [{}]", cacheKey); } } return count; } List<Object> findAllMatchingKeys(String dataSource, Pattern pattern) { List<Object> cacheKeys = cache.getKeys(); List<Object> keysToRemove = new LinkedList<Object>(); for (Object cacheKey : cacheKeys) { Iterator<String> spliterator = Splitter.on('|') .split((String) cacheKey).iterator(); String ds = spliterator.next(); if (ds.equals(dataSource)) { String query = spliterator.next(); Matcher matcher = pattern.matcher(query); if (matcher.find()) { keysToRemove.add(cacheKey); } } } return keysToRemove; } }