/*************************************************************************
* Copyright 2009-2013 Eucalyptus Systems, Inc.
*
* This program 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 program 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/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.compute.common.internal.tags;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.log4j.Logger;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import com.eucalyptus.compute.common.CloudMetadata;
import com.eucalyptus.entities.AbstractPersistent;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.util.Classes;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
/**
* Support functionality for each resource that supports tagging
*/
public abstract class TagSupport {
private static final Logger log = Logger.getLogger( TagSupport.class );
private static final ConcurrentMap<String,TagSupport> supportByIdentifierPrefix = Maps.newConcurrentMap();
private static final ConcurrentMap<Class<? extends CloudMetadata>,TagSupport> supportByClass = Maps.newConcurrentMap();
private static final Splitter idSplitter = Splitter.on( '-' ).limit( 2 );
private final Class<? extends AbstractPersistent> resourceClass;
private final Class<? extends CloudMetadata> cloudMetadataClass;
private final Set<String> identifierPrefixes;
private final String resourceClassIdField;
private final String tagClassResourceField;
private final String notFoundErrorCode;
private final String notFoundFormatString;
protected <T extends AbstractPersistent & CloudMetadata> TagSupport( @Nonnull final Class<T> resourceClass,
@Nonnull final Set<String> identifierPrefixes,
@Nonnull final String resourceClassIdField,
@Nonnull final String tagClassResourceField,
@Nonnull final String notFoundErrorCode,
@Nonnull final String notFoundFormatString ) {
this.resourceClass = resourceClass;
this.cloudMetadataClass = subclassFor( resourceClass );
this.identifierPrefixes = ImmutableSet.copyOf( identifierPrefixes );
this.resourceClassIdField = resourceClassIdField;
this.tagClassResourceField = tagClassResourceField;
this.notFoundErrorCode = notFoundErrorCode;
this.notFoundFormatString = notFoundFormatString;
}
protected <T extends AbstractPersistent & CloudMetadata> TagSupport( @Nonnull final Class<T> resourceClass,
@Nonnull final String identifierPrefix,
@Nonnull final String resourceClassIdField,
@Nonnull final String tagClassResourceField,
@Nonnull final String notFoundErrorCode,
@Nonnull final String notFoundFormatString ) {
this(
resourceClass,
Collections.singleton( identifierPrefix ),
resourceClassIdField,
tagClassResourceField,
notFoundErrorCode,
notFoundFormatString );
}
public abstract Tag createOrUpdate( CloudMetadata metadata,
OwnerFullName ownerFullName,
String key,
String value );
public abstract Tag example( @Nonnull CloudMetadata metadata,
@Nonnull OwnerFullName ownerFullName,
@Nullable String key,
@Nullable String value );
public abstract Tag example( @Nonnull OwnerFullName ownerFullName );
protected <T extends Tag> T example( @Nonnull T tag,
@Nonnull OwnerFullName ownerFullName ) {
tag.setOwner( ownerFullName );
return tag;
}
public final long count( @Nonnull CloudMetadata metadata,
@Nonnull OwnerFullName ownerFullName ) {
final Tag example = example( metadata, ownerFullName, null, null );
return Tags.count( example );
}
public abstract CloudMetadata lookup( String identifier ) throws TransactionException;
public final String getNotFoundErrorCode( ){
return notFoundErrorCode;
}
public final String getNotFoundFormatString( ) {
return notFoundFormatString;
}
/**
* Get the tags for the given resources, grouped by ID and ordered for display.
*
* @param owner The account for the tags
* @param identifiers The resource identifiers for the tags
* @return The tag map with an entry for each requested resource
*/
public Map<String,List<Tag>> getResourceTagMap( final OwnerFullName owner,
final Iterable<String> identifiers ) {
final int identifiersSize = Iterables.size( identifiers );
final Map<String,List<Tag>> tagMap = Maps.newHashMapWithExpectedSize( identifiersSize );
for ( final String id : identifiers ) {
tagMap.put( id, Lists.<Tag>newArrayList() );
}
if ( !tagMap.isEmpty() ) {
final Tag example = example( owner );
final Criterion idRestriction = identifiersSize < 1000 ?
Property.forName( tagClassResourceField ).in( DetachedCriteria.forClass( resourceClass )
.add( Restrictions.in( resourceClassIdField, Lists.newArrayList( identifiers ) ) )
.setProjection( Projections.id( ) ) ) :
Restrictions.conjunction( );
try {
final List<Tag> tags = Tags.list( example, Predicates.alwaysTrue(), idRestriction, Collections.<String,String>emptyMap() );
for ( final Tag tag : tags ) {
final List<Tag> keyTags = tagMap.get( tag.getResourceId( ) );
if ( keyTags != null ) {
keyTags.add( tag );
}
}
} catch ( Exception e ) {
log.error( e, e );
}
Ordering<Tag> order = Ordering.natural().onResultOf( Tags.key() );
for ( final String id : identifiers ) {
Collections.sort( tagMap.get( id ), order );
}
}
return tagMap;
}
@Nonnull
public Class<? extends CloudMetadata> getCloudMetadataClass() {
return cloudMetadataClass;
}
@Nonnull
public Set<String> getIdentifierPrefixes() {
return identifierPrefixes;
}
public static TagSupport forResourceClass( @Nonnull final Class<? extends CloudMetadata> metadataClass ) {
return supportByClass.get( subclassFor( metadataClass ) );
}
public static TagSupport fromResource( @Nonnull final CloudMetadata metadata ) {
return supportByClass.get( subclassFor( metadata.getClass() ) );
}
public static TagSupport fromIdentifier( @Nonnull final String id ) {
return supportByIdentifierPrefix.get( Iterables.getFirst( idSplitter.split( id ), "" ) );
}
static void registerTagSupport( @Nonnull final TagSupport tagSupport ) {
supportByClass.put( tagSupport.getCloudMetadataClass(), tagSupport );
for ( final String idPrefix : tagSupport.getIdentifierPrefixes() ) {
supportByIdentifierPrefix.put( idPrefix, tagSupport );
}
}
@SuppressWarnings( "unchecked" )
private static Class<? extends CloudMetadata> subclassFor( Class<? extends CloudMetadata> metadataInstance ) {
return metadataClassMap.getUnchecked( metadataInstance );
}
Class<? extends AbstractPersistent> getResourceClass() {
return resourceClass;
}
String getResourceClassIdField() {
return resourceClassIdField;
}
String getTagClassResourceField() {
return tagClassResourceField;
}
private static final LoadingCache<Class,Class> metadataClassMap = CacheBuilder.newBuilder().build(
new CacheLoader<Class,Class>() {
@Override
public Class load( final Class instanceClass ) {
final List<Class<?>> interfaces = Lists.newArrayList();
for ( final Class clazz : Classes.interfaceAncestors().apply( instanceClass ) ) {
interfaces.add( clazz );
}
Collections.reverse( interfaces );
return Iterables.find( interfaces,
Predicates.and(
Predicates.not( Predicates.<Class<?>>equalTo( CloudMetadata.class ) ),
Classes.subclassOf( CloudMetadata.class ) ) );
}
});
}