Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.randombits.confluence.metadata.impl;
import com.atlassian.cache.Cache;
import com.atlassian.cache.CacheManager;
import com.atlassian.confluence.core.ContentEntityManager;
import com.atlassian.confluence.core.ContentEntityObject;
import com.atlassian.confluence.core.ContentPropertyManager;
import com.atlassian.confluence.core.VersionHistorySummary;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.event.PluginEventManager;
import com.atlassian.plugin.tracker.DefaultPluginModuleTracker;
import com.atlassian.plugin.tracker.PluginModuleTracker;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.BaseException;
import org.apache.log4j.Logger;
import org.randombits.confluence.metadata.*;
import org.randombits.confluence.metadata.event.MetadataUpdatedEvent;
import org.randombits.confluence.metadata.event.MetadataUpdatedEventEmitter;
import org.randombits.confluence.metadata.impl.handler.DirectTypeHandler;
import org.randombits.confluence.metadata.FieldNotFoundException;
import org.randombits.confluence.metadata.indexing.IndexManager;
import org.randombits.confluence.metadata.xstream.MetadataXStream;
import org.randombits.storage.Aliasable;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Qualifier;
import com.opensymphony.xwork.ActionContext;
import java.io.InvalidClassException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import static org.apache.log4j.Level.WARN;
public class DefaultMetadataManager implements MetadataManager, DisposableBean, MetadataUpdatedEventEmitter {
private static final Logger LOG = Logger.getLogger( MetadataManager.class );
private static final String METADATA_PREFIX = "~metadata";
private static final String METADATA_CACHE_KEY = MetadataManager.class.getName();
private static final String WORKFLOWS_PUBLISHED_ACTION_NAME = "releaseview";
private final CacheManager cacheManager;
private final ContentPropertyManager contentPropertyManager;
private final ContentEntityManager contentEntityManager;
private final PluginModuleTracker typeHandlerTracker;
private final EventPublisher eventPublisher;
private XStream xStream;
private List typeHandlers;
private IndexManager indexManager;
public DefaultMetadataManager(
CacheManager cacheManager,
PluginAccessor pluginAccessor,
PluginEventManager pluginEventManager,
ContentPropertyManager contentPropertyManager,
@Qualifier("contentEntityManager") ContentEntityManager contentEntityManager,
IndexManager indexManager,
EventPublisher eventPublisher) {
this.cacheManager = cacheManager;
this.contentPropertyManager = contentPropertyManager;
this.contentEntityManager = contentEntityManager;
this.indexManager = indexManager;
this.eventPublisher = eventPublisher;
typeHandlers = new ArrayList();
xStream = new MetadataXStream();
xStream.setClassLoader( getClass().getClassLoader() );
applyAliases( this );
initTypeHandlers();
typeHandlerTracker = new DefaultPluginModuleTracker( pluginAccessor, pluginEventManager, TypeHandlerModuleDescriptor.class,
new PluginModuleTracker.Customizer() {
@Override
public TypeHandlerModuleDescriptor adding( TypeHandlerModuleDescriptor moduleDescriptor ) {
addTypeHandler( moduleDescriptor.getModule() );
return moduleDescriptor;
}
@Override
public void removed( TypeHandlerModuleDescriptor moduleDescriptor ) {
removeTypeHandler( moduleDescriptor.getModule() );
}
} );
}
protected void initTypeHandlers() {
addDirectTypeHandler(
String.class, Character.class, char.class,
Byte.class, byte.class, Short.class, short.class, Integer.class, int.class, Long.class, long.class,
Float.class, float.class, Double.class, double.class, BigDecimal.class, BigInteger.class,
Boolean.class, boolean.class,
Date.class);
}
private void addDirectTypeHandler( Class>... type ) {
addTypeHandler(new DirectTypeHandler(type));
}
public void addTypeHandler( TypeHandler handler ) {
typeHandlers.add(handler);
if ( handler instanceof HasAlias)
( (HasAlias) handler ).applyAliases( this );
}
public void removeTypeHandler( TypeHandler handler ) {
typeHandlers.remove(handler);
}
/**
* Returns the provided 'storable' value as the original value.
*
* @param storable The stored value.
* @return The original, or null if it is not supported.
*/
public Object fromStorable( Object storable ) throws TypeConversionException {
if ( storable == null )
return null;
for ( TypeHandler handler : typeHandlers ) {
if ( handler.supportsStorable(storable) )
return handler.getOriginal(storable);
}
return null;
}
public Object toStorable( Object original ) throws TypeConversionException {
if ( original == null )
return null;
for ( TypeHandler handler : typeHandlers ) {
if ( handler.supportsOriginal(original) )
return handler.getStorable(original);
}
return null;
}
public void applyAliases( XStream xStream ) {
applyAliases( new AliasableXStream( xStream ) );
}
/**
* Applies the standard metadata aliases to the {@link org.randombits.storage.Aliasable} instance.
*
* @param aliasable The {@link org.randombits.storage.Aliasable} instance to alias.
*/
public void applyAliases( Aliasable aliasable ) {
// Alias the standard reference classes.
for ( TypeHandler handler : typeHandlers ) {
if ( handler instanceof HasAlias)
( (HasAlias) handler ).applyAliases( aliasable );
}
}
/**
* Returns a content property value for the specified content/key combo.
*
* @param content The content object.
* @param key The key to retrieve.
* @return The property value.
*/
private String getProperty( ContentEntityObject content, String key ) {
return contentPropertyManager.getTextProperty(content, key);
}
private ContentEntityObject findVersion( ContentEntityObject content, int version ) {
if ( content.getVersion() == version )
return content;
if ( !content.isLatestVersion() )
content = (ContentEntityObject) content.getLatestVersion();
return findVersionNoChecks(content, version);
}
private ContentEntityObject findVersionNoChecks( ContentEntityObject content, int version ) {
List summaries = contentEntityManager.getVersionHistorySummaries( content );
for ( VersionHistorySummary summary : summaries ) {
if ( summary.getVersion() == version )
return contentEntityManager.getById( summary.getId() );
}
return null;
}
private Cache getCache() throws InvalidClassException {
return cacheManager.getCache(METADATA_CACHE_KEY);
}
/**
* Loads a read-only, cached copy of the current data for the specified
* content. This is much more efficient than
* {@link #loadWritableData(ContentEntityObject)} when you only need read
* access to the current data.
*
* @param content The content object.
* @return The current metadata.
*/
public MetadataStorage loadReadableData( ContentEntityObject content ) {
Map data = null;
Cache cache = null;
Long id = null;
MetadataStorage metadata;
// Comala Workflows page version override if viewing the published version of a page.
if (ActionContext.getContext() != null && WORKFLOWS_PUBLISHED_ACTION_NAME.equals( ActionContext.getContext().getName() ) ) {
ContentEntityObject publishedContent = findVersionNoChecks(content, content.getVersion());
if( publishedContent != null ){
content = publishedContent;
}
}
if ( content != null ) {
try {
id = content.getId();
cache = getCache();
data = (Map) cache.get( id );
} catch ( InvalidClassException ice ) {
// InvalidClassException could happen when there is serialiation problem.
// Users in clustered environment had reported this problem before.
if (LOG.isEnabledFor(WARN)) {
LOG.warn("Could not find cache for ID '" + id + "': " + ice.getMessage(), ice);
}
}
}
if ( null == content ) {
// TODO [20140217 YCL] What's the reason content is null? Someone please answer. This is causing NPE at loadDataXML().
}
if ( data == null ) {
data = loadDataMap( content );
cache.put( id, data );
}
if ( data != null ) {
metadata = new StandardMetadataStorage( this, content, data, true );
} else {
metadata = new EmptyMetadataStorage();
}
return metadata;
}
/**
* Loads the data for the specified content object. If the version number is
* not valid, null is returned.
*
* @param content The content object.
* @param version The version to retrieve.
* @return the data for the specified version of the content object.
*/
public MetadataStorage loadWritableData( ContentEntityObject content, int version ) {
content = findVersion( content, version );
if ( content != null )
return loadWritableData( content );
return null;
}
/**
* Loads the data into a MetadataStorage instance. The storage will load any
* legacy data, if it exists.
*
* @param content The content object.
* @return The metadata storage.
*/
public MetadataStorage loadWritableData( ContentEntityObject content ) {
return loadNewWritableData(content, content);
}
/**
* Loads the data from an existing content to a new content
* into a MetadataStorage instance. The storage will load any
* legacy data, if it exists.
*
* @param contentFrom The content object.
* @param contentTo The content object.
* @return The metadata storage.
*/
public MetadataStorage loadNewWritableData( ContentEntityObject contentFrom, ContentEntityObject contentTo ) {
MetadataStorage metadata = null;
if ( contentFrom != null ) {
Map data = loadDataMap( contentFrom );
if ( data == null )
data = new HashMap();
metadata = new StandardMetadataStorage( this, contentTo, data, false );
} else {
metadata = new EmptyMetadataStorage();
}
return metadata;
}
private Map loadDataMap( ContentEntityObject content ) {
// look for the data.
Map data = null;
String xml = loadDataXML( content );
if ( xml != null ) {
try {
data = (Map) xStream.fromXML( xml );
} catch ( BaseException e ) {
LOG.error( "Conversion issue while loading metadata for content: " + content + "; version: "
+ content.getVersion(), e );
data = new MetadataMap();
}
}
if (data == null) {
data = new MetadataMap();
}
return data;
}
/**
* Loads the data for the specified content object, returning it as an XML
* string. This method will not load any legacy data.
*
* @param content The content object.
* @return The XML String, or null if no data has been saved.
*/
public String loadDataXML( ContentEntityObject content ) {
int version = content.getVersion();
ContentEntityObject latest = (ContentEntityObject) content.getLatestVersion();
for ( int i = version; i >= 1; i-- ) {
String xml = getProperty( latest, METADATA_PREFIX + "." + i );
if ( xml != null ) {
return xml;
}
}
// If we get this far, we couldn't find any metadata.
return null;
}
/**
* Saves the data as the next version. This is useful for pre-saving data
* when other processes will manage the actual creation of the next content
* version.
*
* @param data The data to save.
*/
public void saveNextData( MetadataStorage data ) {
this.saveData( data, false );
}
/**
* Saves the metadata. If overwriteCurrent is
* true, the current set of existing data will be
* overwritten. If not, it will be saved to a version one number greater
* than the current version for the content on the assumption that the
* content object will be saved to a new version immediately afterwards.
*
* @param data The metadata to save.
* @param currentContentVersion If true the previous version will be
* overwritten.
*/
public void saveData( MetadataStorage data, boolean currentContentVersion ) {
saveData(data, currentContentVersion, true);
}
/**
* Saves the metadata. If overwriteCurrent is
* true, the current set of existing data will be
* overwritten. If not, it will be saved to a version one number greater
* than the current version for the content on the assumption that the
* content object will be saved to a new version immediately afterwards.
*
* @param data The metadata to save.
* @param currentContentVersion If true the previous version will be
* overwritten.
* @param buildIndex If true will trigger buildIndex()
*/
public void saveData(MetadataStorage data, boolean currentContentVersion, boolean buildIndex) {
ContentEntityObject content = data.getContent();
if (content != null) {
ContentEntityObject latest = (ContentEntityObject) content.getLatestVersion();
updateMetadata(data, latest, latest.getVersion(), currentContentVersion, buildIndex);
}
}
/**
* Save the metadata for {set-data} macro. It will compare the version of metadata
* to be saved with the latest version of metadata. It will not update the
* metadata if the version is not match. This is to prevent {set-data} macro
* to override metadata version when user view the page history.
* @see https://tools.servicerocket.com/browse/AN22-358
*
* @param data The metadata to be saved
*/
@Deprecated
public void saveSetData(MetadataStorage data) {
ContentEntityObject content = data.getContent();
if (content != null) {
ContentEntityObject latestEntityObject = (ContentEntityObject) content.getLatestVersion();
int latestVersion = latestEntityObject.getVersion();
if(content.getVersion() == latestVersion) {
updateMetadata(data, latestEntityObject, latestVersion, true, true);
}
}
}
private void updateMetadata(MetadataStorage metadata, ContentEntityObject latestEntityObject, int latestVersion, boolean currentContentVersion, boolean buildIndex) {
if (latestVersion == 0 || !currentContentVersion) {
latestVersion++;
}
String xml = xStream.toXML(toMetadataMap(metadata.getBaseMap()));
contentPropertyManager.setTextProperty(latestEntityObject, METADATA_PREFIX + "." + latestVersion, xml);
eventPublisher.publish(new MetadataUpdatedEvent(this, metadata, buildIndex));
clearCache(latestEntityObject);
}
/**
* Clears the cache for the specified content.
*
* @param content The content to clear the metadata cache for.
*/
private void clearCache( ContentEntityObject content ) {
try {
Cache readThroughCache = getCache();
Long id = content.getId();
readThroughCache.remove( id );
} catch(InvalidClassException ice) {
if (LOG.isEnabledFor(WARN)) {
LOG.warn("Could not clear cache for content '" + content.getIdAsString() + "': " + ice.getMessage(), ice);
}
}
}
/**
* By default, the metadata manager will cache up to 1000 data items for 60
* minutes. This will clear any cached data from memory.
*/
public void clearCache() {
try {
getCache().removeAll();
} catch(InvalidClassException ice) {
if (LOG.isEnabledFor(WARN)) {
LOG.warn("Could not clear cache: " + ice.getMessage(), ice);
}
}
}
/**
* Clears the data storage area for the next version of the content object.
* This is useful when data has been pre-saved and the overall page-save
* fails.
*
* @param content The content object.
*/
public void clearNextData( ContentEntityObject content ) {
if ( !content.isLatestVersion() )
content = (ContentEntityObject) content.getLatestVersion();
contentPropertyManager.setTextProperty(content, METADATA_PREFIX + "." + ( content.getVersion() + 1 ), null);
}
/**
* Converts a regular map structure into a MetadataMap structure for
* storage.
*
* @param baseMap The Map to convert.
* @return the map as a {@link org.randombits.confluence.metadata.MetadataMap}.
*/
private MetadataMap toMetadataMap( Map baseMap ) {
if ( baseMap == null )
return null;
if ( baseMap instanceof MetadataMap)
return (MetadataMap) baseMap;
MetadataMap metadataMap = new MetadataMap();
for ( Map.Entry e : baseMap.entrySet() ) {
Object value = e.getValue();
if ( value instanceof Map )
metadataMap.put( e.getKey(), toMetadataMap( (Map) value ) );
else
metadataMap.put( e.getKey(), e.getValue() );
}
return metadataMap;
}
/**
* Adds an alias for the specified class. Aliases are used in the
* serialization/deserialization process.
*
* @param alias The alias to add.
* @param aliasedClass The class being aliased.
*/
public void addAlias( String alias, Class> aliasedClass ) {
xStream.alias( alias, aliasedClass );
}
/**
* Returns a read-only {@link org.randombits.confluence.metadata.MetadataStorage} instance based on the
* provided {@link Map}. Trying to save the map will fail, since it is not
* associated with a particular piece of content.
*
* @param map The map to use.
* @return The {@link org.randombits.confluence.metadata.MetadataStorage} instance.
*/
public MetadataStorage asMetadataStorage( Map map ) {
return new StandardMetadataStorage( this, null, map, true );
}
public boolean isStorable( Object value ) {
for ( TypeHandler handler : typeHandlers ) {
if ( handler.supportsStorable( value ) )
return true;
}
return false;
}
@Override
public String toXML( Object value ) throws TypeConversionException {
value = toStorable( value );
if ( value != null ) {
try {
return xStream.toXML( value );
} catch ( Exception e ) {
throw new TypeConversionException( e.getMessage(), e );
}
} else {
return null;
}
}
@Override
public Object fromXML( String xml ) throws TypeConversionException {
if ( xml != null ) {
try {
Object value = xStream.fromXML( xml );
return fromStorable( value );
} catch ( Exception e ) {
throw new TypeConversionException( e.getMessage(), e );
}
}
return null;
}
private static class AliasableXStream implements Aliasable {
XStream xStream;
AliasableXStream( XStream xStream ) {
this.xStream = xStream;
}
/**
* Adds the specified alias for the class.
*
* @param alias The alias.
* @param aliasedClass The class to alias.
*/
public void addAlias( String alias, Class> aliasedClass ) {
xStream.alias( alias, aliasedClass );
}
}
@Override
public void destroy() throws Exception {
typeHandlers.clear();
typeHandlerTracker.close();
}
@Override
public T queryIndex(String contentId, String fieldName, Class extends T> clazz) throws IllegalArgumentException, FieldNotFoundException {
return indexManager.query(contentId, fieldName, clazz);
}
@Override
public T queryIndex(String contentId, List path, String fieldName, Class extends T> clazz) throws IllegalArgumentException, FieldNotFoundException {
return indexManager.query(contentId, path, fieldName, clazz);
}
}