/************************************************************************* * Copyright 2009-2016 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.identifier; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.eucalyptus.bootstrap.ServiceJarDiscovery; import com.eucalyptus.compute.common.CloudMetadata; import com.eucalyptus.compute.common.CloudMetadataLongIdentifierConfigurable; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.configurable.ConfigurableProperty; import com.eucalyptus.configurable.ConfigurablePropertyException; import com.eucalyptus.configurable.PropertyChangeListener; import com.eucalyptus.crypto.Crypto; import com.eucalyptus.system.Ats; import com.eucalyptus.util.FUtils; import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Splitter; 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.Sets; /** * */ @SuppressWarnings( { "Guava", "StaticPseudoFunctionalStyleMethod" } ) @ConfigurableClass( root = "cloud", description = "Properties for compute." ) public class ResourceIdentifiers { private static final Pattern resourcePattern = Pattern.compile( "[0-9a-fA-F]{8}|[0-9a-fA-F]{17}" ); private static final Set<String> configurableLongIdentifierResourcePrefixes = new CopyOnWriteArraySet<>( ); private static final Function<String,Set<String>> shortIdentifierPrefixes = FUtils.memoizeLast( ResourceIdentifiers::prefixes ); private static final Function<String,Set<String>> longIdentifierPrefixes = FUtils.memoizeLast( ResourceIdentifiers::prefixes ); private static final ConcurrentMap<String,ResourceIdentifierCanonicalizer> canonicalizers = Maps.newConcurrentMap(); private static final AtomicReference<ResourceIdentifierCanonicalizer> defaultCanonicalizer = new AtomicReference<>( new LowerResourceIdentifierCanonicalizer( ) ); @SuppressWarnings( "unused" ) @ConfigurableField( description = "Name of the canonicalizer for resource identifiers.", initial = LowerResourceIdentifierCanonicalizer.NAME, displayName = "identifier_canonicalizer", changeListener = ResourceIdentifierCanonicalizerChangeListener.class ) public static volatile String IDENTIFIER_CANONICALIZER = LowerResourceIdentifierCanonicalizer.NAME; @SuppressWarnings( "WeakerAccess" ) @ConfigurableField( description = "List of resource identifier prefixes for short identifiers or * for all", displayName = "short_identifier_prefixes", changeListener = ResourceIdentifierPrefixListChangeListener.class ) public static volatile String SHORT_IDENTIFIER_PREFIXES = ""; @SuppressWarnings( "WeakerAccess" ) @ConfigurableField( description = "List of resource identifier prefixes for long identifiers or * for all", displayName = "long_identifier_prefixes", changeListener = ResourceIdentifierPrefixListChangeListener.class ) public static volatile String LONG_IDENTIFIER_PREFIXES = ""; @SuppressWarnings( "WeakerAccess" ) @ConfigurableField( initial = "true", description = "Allow account settings to specify long identifier usage" ) public static volatile Boolean LONG_IDENTIFIER_ACCOUNT_SETTINGS_USED = true; static void register( final ResourceIdentifierCanonicalizer canonicalizer ) { canonicalizers.put( canonicalizer.getName( ).toLowerCase( ), canonicalizer ); } @SuppressWarnings( "unused" ) public static Optional<ResourceIdentifierCanonicalizer> getCanonicalizer( final String name ) { return Optional.fromNullable( canonicalizers.get( name.toLowerCase( ) ) ); } public static boolean useAccountLongIdentifierSettings() { return MoreObjects.firstNonNull( LONG_IDENTIFIER_ACCOUNT_SETTINGS_USED, Boolean.TRUE ); } /** * Generate a long or short identifier based on the prefix and configuration. */ public static ResourceIdentifier generate( final String prefix ) { return useLongIdentifierForPrefix( prefix ) ? generateLong( prefix ) : generateShort( prefix ); } @SuppressWarnings( "WeakerAccess" ) public static ResourceIdentifier generateShort( final String prefix ) { return parse( Crypto.generateId( prefix ) ); } @SuppressWarnings( "WeakerAccess" ) public static ResourceIdentifier generateLong( final String prefix ) { return parse( Crypto.generateLongId( prefix ) ); } public static String generateString( final String prefix ) { return generate( prefix ).getIdentifier( ); } public static String generateShortString( final String prefix ) { return generateShort( prefix ).getIdentifier( ); } public static String generateLongString( final String prefix ) { return generateLong( prefix ).getIdentifier( ); } public static ResourceIdentifier parse( final String expectedPrefix, final String identifierText ) throws InvalidResourceIdentifier { return doParse( expectedPrefix, identifierText ); } public static ResourceIdentifier parse( final String identifierText ) throws InvalidResourceIdentifier { return doParse( null, identifierText ); } /** * Converts the given identifiers to normal form. */ public static List<String> normalize( final String expectedPrefix, final Iterable<String> identifiers ) throws InvalidResourceIdentifier { return Lists.newArrayList( Iterables.transform( identifiers, normalize( expectedPrefix ) ) ); } public static List<String> normalize( final Iterable<String> identifiers ) throws InvalidResourceIdentifier { return Lists.newArrayList( Iterables.transform( identifiers, normalize( ) ) ); } @SuppressWarnings( "WeakerAccess" ) public static Function<String,String> normalize( ) throws InvalidResourceIdentifier { return ResourceIdentifierNormalizeTransform.ENFORCE; } public static Function<String,String> tryNormalize( ) throws InvalidResourceIdentifier { return ResourceIdentifierNormalizeTransform.ATTEMPT; } @SuppressWarnings( "WeakerAccess" ) public static Function<String,String> normalize( final String expectedPrefix ) { return identifier -> parse( expectedPrefix, identifier ).getIdentifier( ); } @SuppressWarnings( "ConstantConditions" ) private static ResourceIdentifier doParse( @Nullable final String expectedPrefix, @Nonnull final String identifierText ) throws InvalidResourceIdentifier { if ( identifierText == null ) throw new InvalidResourceIdentifier( identifierText ); if ( expectedPrefix != null && !identifierText.startsWith( expectedPrefix + '-' ) ) { throw new InvalidResourceIdentifier( identifierText ); } final int hexOffset = identifierText.lastIndexOf( '-' ) + 1; if ( hexOffset < 2 ) throw new InvalidResourceIdentifier( identifierText ); if ( !resourcePattern.matcher( identifierText.substring( hexOffset ) ).matches( ) ) { throw new InvalidResourceIdentifier( identifierText ); } final ResourceIdentifierCanonicalizer canonicalizer = defaultCanonicalizer.get( ); return new ResourceIdentifier( canonicalizer.canonicalizePrefix( identifierText.substring( 0, hexOffset -1 ) ) + "-" + canonicalizer.canonicalizeHex( identifierText.substring( hexOffset ) ) ); } public static boolean useLongIdentifierForPrefix( final String prefix ) { //noinspection ConstantConditions return !shortIdentifierPrefixes.apply( SHORT_IDENTIFIER_PREFIXES ).contains( prefix ) && longIdentifierPrefixes.apply( LONG_IDENTIFIER_PREFIXES ).contains( prefix ); } private static Set<String> prefixValues( final String prefixList ) { final Splitter propertySplitter = Splitter.on( CharMatcher.anyOf( " ,|;" ) ).omitEmptyStrings( ).trimResults( ); return Sets.newHashSet( propertySplitter.split( prefixList ) ); } private static Set<String> prefixes( final String prefixList ) { if ( "*".equals( prefixList ) ) { return configurableLongIdentifierResourcePrefixes; } else { return ImmutableSet.copyOf( prefixValues( prefixList ) ); } } private enum ResourceIdentifierNormalizeTransform implements Function<String,String> { ATTEMPT{ @Nonnull @Override public String apply( final String identifier ) { try { return ENFORCE.apply( identifier ); } catch ( InvalidResourceIdentifier e ) { return identifier; } } }, ENFORCE { @Nonnull @Override public String apply( final String identifier ) { return parse( identifier ).getIdentifier( ); } } ; @Nonnull @Override public abstract String apply( final String identifier ); } @SuppressWarnings( "WeakerAccess" ) public static final class ResourceIdentifierCanonicalizerChangeListener implements PropertyChangeListener<String> { @Override public void fireChange( final ConfigurableProperty t, final String newValue ) throws ConfigurablePropertyException { final Splitter propertySplitter = Splitter.on( CharMatcher.anyOf( " ,|;" ) ).omitEmptyStrings( ).trimResults( ); final Iterable<String> canonicalizerValues = propertySplitter.split( newValue ); for ( final String canonicalizer : canonicalizerValues ) { if ( !canonicalizers.containsKey( canonicalizer ) ) { throw new ConfigurablePropertyException( "Unknown resource identifier canonicalizer: " + canonicalizer + " in " + newValue ); } } defaultCanonicalizer.set( new DispatchingResourceIdentifierCanonicalizer( Iterables.transform( canonicalizerValues, Functions.forMap( canonicalizers ) ) ) ); } } @SuppressWarnings( "WeakerAccess" ) public static final class ResourceIdentifierPrefixListChangeListener implements PropertyChangeListener<String> { private static final Pattern prefixPattern = Pattern.compile( "[a-z](?:[a-z-]{0,30}[a-z])?" ); @Override public void fireChange( final ConfigurableProperty t, final String newValue ) throws ConfigurablePropertyException { if ( !"*".equals( newValue ) ) { final Set<String> prefixValues = prefixValues( newValue ); for ( final String prefixValue : prefixValues ) { if ( !prefixPattern.matcher( prefixValue ).matches( ) ) { throw new ConfigurablePropertyException( "Invalid resource identifier prefix: '" + prefixValue + "' in '" + newValue + "'" ); } } } } } enum ResourceIdentifierCanonicalizerToName implements Function<ResourceIdentifierCanonicalizer,String> { INSTANCE; @Nullable @Override public String apply( @Nullable final ResourceIdentifierCanonicalizer canonicalizer ) { return canonicalizer==null ? null : canonicalizer.getName( ); } } public static final class ConfigurableLongResourceIdentifierDiscovery extends ServiceJarDiscovery { @Override public boolean processClass( Class candidate ) throws Exception { if ( CloudMetadata.class.isAssignableFrom( candidate ) ) { final Ats ats = Ats.from( candidate ); final CloudMetadataLongIdentifierConfigurable configurable = ats.get( CloudMetadataLongIdentifierConfigurable.class ); if ( configurable != null ) { configurableLongIdentifierResourcePrefixes.add( configurable.prefix( ) ); configurableLongIdentifierResourcePrefixes.addAll( Arrays.asList( configurable.relatedPrefixes( ) ) ); return true; } } return false; } @Override public Double getPriority( ) { return 1.0d; } } }