/*
* Copyright (c) 2012, Paul Merlin. All Rights Reserved.
*
* 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 org.qi4j.entitystore.jclouds;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.jclouds.ContextBuilder;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.apis.Apis;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.InputStreamMap;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.providers.Providers;
import org.qi4j.api.configuration.Configuration;
import org.qi4j.api.entity.EntityDescriptor;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.service.ServiceActivation;
import org.qi4j.io.Input;
import org.qi4j.io.Inputs;
import org.qi4j.io.Output;
import org.qi4j.io.Outputs;
import org.qi4j.io.Receiver;
import org.qi4j.io.Sender;
import org.qi4j.spi.entitystore.EntityNotFoundException;
import org.qi4j.spi.entitystore.EntityStoreException;
import org.qi4j.spi.entitystore.helpers.MapEntityStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.contains;
/**
* JClouds implementation of MapEntityStore.
*/
// TODO Expose Location in configuration
// To be done once JClouds 1.5 has stabilized their Location API.
// A list of ISO-3166 country codes.
// It defines where your entities are allowed to be stored.
// @UseDefaults Property<List<String>> geopoliticalBoundaries(); ???
// SEE http://www.jclouds.org/documentation/reference/location-metadata-design
public class JCloudsMapEntityStoreMixin
implements ServiceActivation, MapEntityStore
{
private static final Logger LOGGER = LoggerFactory.getLogger( "org.qi4j.entitystore.jclouds" );
private static final Map<String, ApiMetadata> allApis = Maps.uniqueIndex( Apis.viewableAs( BlobStoreContext.class ), Apis
.idFunction() );
private static final Map<String, ProviderMetadata> appProviders = Maps.uniqueIndex( Providers.viewableAs( BlobStoreContext.class ), Providers
.idFunction() );
private static final Set<String> allKeys = ImmutableSet.copyOf( Iterables.concat( appProviders.keySet(), allApis.keySet() ) );
@This
private Configuration<JCloudsMapEntityStoreConfiguration> configuration;
private BlobStoreContext storeContext;
private String container;
@Override
public void activateService()
throws Exception
{
configuration.refresh();
String provider = configuration.get().provider().get();
String identifier = configuration.get().identifier().get();
String credentials = configuration.get().credential().get();
Map<String, String> properties = configuration.get().properties().get();
container = configuration.get().container().get();
if( provider != null )
{
checkArgument( contains( allKeys, provider ), "provider %s not in supported list: %s", provider, allKeys );
}
else
{
provider = "transient";
}
if( container == null )
{
container = "qi4j-entities";
}
storeContext = ContextBuilder.newBuilder( provider ).
credentials( identifier, credentials ).
overrides( asProperties( properties ) ).
buildView( BlobStoreContext.class );
BlobStore blobStore = storeContext.getBlobStore();
if( !blobStore.containerExists( container ) )
{
if( !blobStore.createContainerInLocation( null, container ) )
{
throw new EntityStoreException( "Unable to create JClouds Blob Container, cannot continue." );
}
else
{
LOGGER.debug( "Created new container: {}", container );
}
}
LOGGER.info( "Activated using {} cloud provider [id:{}]", provider, identifier );
}
private Properties asProperties( Map<String, String> map )
{
Properties props = new Properties();
for( Map.Entry<String, String> eachEntry : map.entrySet() )
{
props.put( eachEntry.getKey(), eachEntry.getValue() );
}
return props;
}
@Override
public void passivateService()
throws Exception
{
if( storeContext != null )
{
storeContext.close();
storeContext = null;
container = null;
}
}
@Override
public Reader get( EntityReference entityReference )
throws EntityStoreException
{
InputStreamMap isMap = storeContext.createInputStreamMap( container );
InputStream input = isMap.get( entityReference.identity() );
if( input == null )
{
throw new EntityNotFoundException( entityReference );
}
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Inputs.byteBuffer( input, 4096 ).transferTo( Outputs.byteBuffer( baos ) );
return new StringReader( baos.toString( "UTF-8" ) );
}
catch( IOException ex )
{
throw new EntityStoreException( "Unable to read entity state for: " + entityReference, ex );
}
finally
{
try
{
input.close();
}
catch( IOException ignored )
{
}
}
}
@Override
public void applyChanges( MapChanges changes )
throws IOException
{
final BlobMap blobMap = storeContext.createBlobMap( container );
changes.visitMap( new MapChanger()
{
@Override
public Writer newEntity( final EntityReference ref, EntityDescriptor entityDescriptor )
throws IOException
{
return new StringWriter()
{
@Override
public void close()
throws IOException
{
super.close();
Blob blob = blobMap.blobBuilder().payload( toString() ).build();
blobMap.put( ref.identity(), blob );
}
};
}
@Override
public Writer updateEntity( final EntityReference ref, EntityDescriptor entityDescriptor )
throws IOException
{
if( !blobMap.containsKey( ref.identity() ) )
{
throw new EntityNotFoundException( ref );
}
return new StringWriter()
{
@Override
public void close()
throws IOException
{
super.close();
Blob blob = blobMap.blobBuilder().payload( toString() ).build();
blobMap.put( ref.identity(), blob );
}
};
}
@Override
public void removeEntity( EntityReference ref, EntityDescriptor entityDescriptor )
throws EntityNotFoundException
{
if( !blobMap.containsKey( ref.identity() ) )
{
throw new EntityNotFoundException( ref );
}
blobMap.remove( ref.identity() );
}
} );
}
@Override
public Input<Reader, IOException> entityStates()
{
return new Input<Reader, IOException>()
{
@Override
public <ReceiverThrowableType extends Throwable> void transferTo( Output<? super Reader, ReceiverThrowableType> output )
throws IOException, ReceiverThrowableType
{
output.receiveFrom( new Sender<Reader, IOException>()
{
@Override
public <ReceiverThrowableType extends Throwable> void sendTo( Receiver<? super Reader, ReceiverThrowableType> receiver )
throws ReceiverThrowableType, IOException
{
InputStreamMap isMap = storeContext.createInputStreamMap( container );
for( InputStream eachInput : isMap.values() )
{
try
{
receiver.receive( new InputStreamReader( eachInput, "UTF-8" ) );
}
finally
{
try
{
eachInput.close();
}
catch( IOException ignored )
{
}
}
}
}
} );
}
};
}
}