com.foreach.common.spring.localization.text.AbstractLocalizedTextService Maven / Gradle / Ivy
/*
* Copyright 2014 the original author or authors
*
* Licensed 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 com.foreach.common.spring.localization.text;
import com.foreach.common.concurrent.SynchronousTaskExecutor;
import com.foreach.common.concurrent.locks.ObjectLock;
import com.foreach.common.concurrent.locks.ObjectLockRepository;
import com.foreach.common.concurrent.locks.ReentrantObjectLockRepository;
import com.foreach.common.spring.localization.Language;
import com.foreach.common.spring.localization.LanguageConfigurator;
import org.apache.log4j.Logger;
import java.util.*;
import java.util.concurrent.ExecutorService;
/**
*
* The AbstractLocalizedTextService is a basic implementation of the {@link LocalizedTextService} interface,
* providing all methods for lookup and creation of text items. It has a tight integration with the
* {@link LocalizedTextSetImpl} which provides an interface for quick text lookup in a collection of items.
*
*
* The service requires a {@link LocalizedTextDataStore} implementation for access to the data store. There
* are also two optional properties: flaggingExecutorService and localizedTextSetCache.
*
*
* The {@link #executorService} is the {@link java.util.concurrent.ExecutorService} that can be used to execute calls
* in the background. Calls to {@link #flagAsUsed(LocalizedText)}, {@link #saveLocalizedText(LocalizedText)} and
* {@link #deleteLocalizedText(LocalizedText)} will put additional calls on the ExecutorService (cache reloads and
* background updates). If no executorService is specified, all calls are executed synchronously.
*
*
* The {@link #textSetCache} is the {@link LocalizedTextSetCache} implementation that is used to cache all
* fetched LocalizedTextSet instances. Newly fetched instances will be stored in the cache and if an instance
* is found in the cache it will be returned directly without DAO interaction.
* See the {@link EternalLocalizedTextSetCache} for an implementation with eternal caching.
* By default no caching is being done.
*
*/
public abstract class AbstractLocalizedTextService implements LocalizedTextService
{
@SuppressWarnings("all")
protected final Logger LOG;
private final ObjectLockRepository textSetFetchLocks = new ReentrantObjectLockRepository();
private final LocalizedTextDataStore localizedTextDao;
private ExecutorService executorService = new SynchronousTaskExecutor();
private LocalizedTextSetCache textSetCache = new NoCachingLocalizedTextSetCache();
/**
* @param localizedTextDao DAO providing callback methods to the datastore.
*/
protected AbstractLocalizedTextService( LocalizedTextDataStore localizedTextDao ) {
this.localizedTextDao = localizedTextDao;
LOG = Logger.getLogger( this.getClass() );
}
/**
* @param executorService The ExecutorService to use for the {@link #flagAsUsed(LocalizedText)} calls.
*/
public final void setExecutorService( ExecutorService executorService ) {
this.executorService = executorService;
if ( this.executorService == null ) {
this.executorService = new SynchronousTaskExecutor();
}
}
/**
* @param textSetCache The LocalizedTextSetCache implementation to use for caching the textSet instances.
*/
public final void setTextSetCache( LocalizedTextSetCache textSetCache ) {
this.textSetCache = textSetCache;
if ( this.textSetCache == null ) {
this.textSetCache = new NoCachingLocalizedTextSetCache();
}
}
/**
* Gets a {@link LocalizedTextSetImpl} for a group of items. Synchronizes access to the same application/group combination.
* If the textSet is not found in the cache it will be fetched from the dataStore and stored in the
* cache. While this is happening, other threads trying to access the same textSet will wait.
*
*
* @param application Application to get the group of items from.
* @param group Name of the group.
* @return All items converted into a set instance.
*/
public final LocalizedTextSet getLocalizedTextSet( String application, String group ) {
LocalizedTextSet textSet = textSetCache.getLocalizedTextSet( application, group );
if ( textSet == null ) {
ObjectLock writeLock = textSetFetchLocks.getLock( application + group );
try {
writeLock.lock();
// Fetch again from cache to avoid double querying
textSet = textSetCache.getLocalizedTextSet( application, group );
if ( textSet == null ) {
textSet = new LocalizedTextSetImpl( application, group, this );
textSetCache.storeLocalizedTextSet( textSet );
}
}
finally {
writeLock.unlock();
}
}
return textSet;
}
/**
* Gets a list of all LocalizedText items for a given group.
*
* @param application Application to get the group of items from.
* @param group Name of the group.
* @return List of items.
*/
public final List getLocalizedTextItems( String application, String group ) {
return localizedTextDao.getLocalizedTextForGroup( application, group );
}
/**
* Flags a text item as used, this will also call a method on the DAO to flag the item in the data store.
*
* @param text Text item that should be flagged as used.
*/
public final void flagAsUsed( final LocalizedText text ) {
text.setUsed( true );
text.setUpdated( new Date() );
executorService.submit( new Runnable()
{
public void run() {
try {
localizedTextDao.flagAsUsed( text );
}
catch ( RuntimeException re ) {
LOG.error( "Failed to flag item as used: " + text, re );
}
}
} );
}
/**
* Creates a new text item with default values. If the item already exists in the datastore, the existing
* item will be returned instead. Otherwise this will also execute an insert call on the DAO.
* Note that no exceptions are being thrown in case saving fails. An ERROR message will be logged
* but the constructed text item will be returned.
* Unlike a normal save or delete, this method will not trigger a cache reload for the text set
* the item belongs to. Because this method does a lookup first, it is assumed that other instances
* will fetch the recently created item and return it instead of trying to save twice. In that case
* fetching the single item is cheaper than reloading the entire set.
*
* @param application Application in which to create the text item.
* @param group Group the text item should belong to.
* @param label Label of the text item.
* @param defaultValue Default value to be set for the text.
* @return Constructed and saved text item.
*/
public final synchronized LocalizedText saveDefaultText( String application,
String group,
String label,
String defaultValue ) {
// Look directly in the database to see if the text item does not yet exist
LocalizedText existing = localizedTextDao.getLocalizedText( application, group, label );
if ( existing != null ) {
LOG.debug( "Not creating text item with defaults because it already exists: " + existing );
return existing;
}
LocalizedText text = new LocalizedText();
text.setApplication( application );
text.setGroup( group );
text.setLabel( label );
for ( Language language : LanguageConfigurator.getLanguages() ) {
text.getFieldsForLanguage( language ).setText( defaultValue );
}
text.setUsed( true );
text.setAutoGenerated( true );
text.setCreated( new Date() );
LOG.debug( "Creating new text item with defaults: " + text );
try {
localizedTextDao.insertLocalizedText( text );
}
catch ( RuntimeException re ) {
LOG.error( "Insert of new text item failed ", re );
}
return text;
}
/**
* Gets a single LocalizedText item from the datastore.
*
* @param application Application in which to find the text item.
* @param group Group the text item belongs to.
* @param label Label of the text item.
* @return Text item or null if not found.
*/
public final LocalizedText getLocalizedText( String application, String group, String label ) {
return localizedTextDao.getLocalizedText( application, group, label );
}
/**
* Saves a LocalizedText item in the backing datastore. This will also trigger a reload of the cache for the
* set this item belongs to.
*
* @param text Text item to save.
*/
public final void saveLocalizedText( final LocalizedText text ) {
if ( text != null ) {
LocalizedText existing = getLocalizedText( text.getApplication(), text.getGroup(), text.getLabel() );
if ( existing == null ) {
localizedTextDao.insertLocalizedText( text );
}
else {
localizedTextDao.updateLocalizedText( text );
}
// Reload cache asynchronously
executorService.submit( new Runnable()
{
public void run() {
try {
textSetCache.reload( text.getApplication(), text.getGroup() );
}
catch ( RuntimeException re ) {
LOG.error( "Failed to reload cache ", re );
}
}
} );
}
}
/**
* Gets a list of all LocalizedText items containing a string.
*
* @param textToSearchFor String with the text to search for.
* @return List of items.
*/
public final List searchLocalizedTextItemsForText( String textToSearchFor ) {
return localizedTextDao.searchLocalizedText( textToSearchFor );
}
/**
* @return All applications with text items.
*/
public final List getApplications() {
return localizedTextDao.getApplications();
}
/**
* @param application Name of an application.
* @return List of text item groups for this application.
*/
public final List getGroups( String application ) {
return localizedTextDao.getGroups( application );
}
/**
* Deletes a LocalizedText item from the backing datastore. This will also trigger a reload of the cache for
* the set this items belongs to.
*
* @param text Text item to delete.
*/
public final void deleteLocalizedText( final LocalizedText text ) {
if ( text != null ) {
localizedTextDao.deleteLocalizedText( text );
// Reload cache asynchronously
executorService.submit( new Runnable()
{
public void run() {
try {
textSetCache.reload( text.getApplication(), text.getGroup() );
}
catch ( RuntimeException re ) {
LOG.error( "Failed to reload cache ", re );
}
}
} );
}
}
/**
* Converts all LocalizedTexts in the provided language to a Map. The LocalizedTexts to be converted can be
* found in the given LocalizedTextSet.
*
* @param localizedTextSet The LocalizedTextSet to convert.
* @param language The language specifying which LocalizedTexts should be converted.
* @return The Map with the LocalizedTexts. The keys of the map are the labels of the LocalizedText and the value is
* the translation of that label in the given Language.
*/
@Override
public final Map getLanguageMap( LocalizedTextSet localizedTextSet, Language language ) {
Map result = new HashMap<>();
for ( LocalizedText localizedText : localizedTextSet.getItems() ) {
String value = localizedText.getFieldsForLanguage( language ).getText();
result.put( localizedText.getLabel(), value );
}
return result;
}
private static final class NoCachingLocalizedTextSetCache implements LocalizedTextSetCache
{
public LocalizedTextSet getLocalizedTextSet( String application, String group ) {
return null;
}
public void storeLocalizedTextSet( LocalizedTextSet textSet ) {
}
public int size() {
return 0;
}
public void clear() {
}
public void reload() {
}
public void reload( String application, String group ) {
}
public Set getCachedTextSets() {
return Collections.emptySet();
}
}
}