diff --git a/src/main/java/bdv/img/remote/RemoteImageLoader.java b/src/main/java/bdv/img/remote/RemoteImageLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0a2965e7c65c432b3c5f1bcba83d1ff3cddda66
--- /dev/null
+++ b/src/main/java/bdv/img/remote/RemoteImageLoader.java
@@ -0,0 +1,239 @@
+package bdv.img.remote;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+import mpicbg.spim.data.View;
+import net.imglib2.FinalInterval;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.NativeImg;
+import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
+import net.imglib2.img.cell.CellImg;
+import net.imglib2.sampler.special.ConstantRandomAccessible;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.integer.UnsignedShortType;
+import net.imglib2.type.numeric.real.FloatType;
+import net.imglib2.type.volatiles.VolatileUnsignedShortType;
+import net.imglib2.util.IntervalIndexer;
+import net.imglib2.view.Views;
+
+import org.jdom2.Element;
+
+import bdv.ViewerImgLoader;
+import bdv.img.cache.VolatileCell;
+import bdv.img.cache.VolatileGlobalCellCache;
+import bdv.img.cache.VolatileGlobalCellCache.LoadingStrategy;
+import bdv.img.cache.VolatileImgCells;
+import bdv.img.cache.VolatileImgCells.CellCache;
+
+import com.google.gson.Gson;
+
+public class RemoteImageLoader implements ViewerImgLoader< UnsignedShortType, VolatileUnsignedShortType >
+{
+	protected String baseUrl;
+
+	protected RemoteImageLoaderMetaData metadata;
+
+	protected int[][] cellsDimensions;
+
+	protected VolatileGlobalCellCache< VolatileShortArray > cache;
+
+	private void open() throws IOException
+	{
+		final URL url = new URL( baseUrl + "?p=init" );
+		metadata = new Gson().fromJson(
+				new InputStreamReader( url.openStream() ),
+				RemoteImageLoaderMetaData.class );
+		cache = new VolatileGlobalCellCache< VolatileShortArray >(
+				new RemoteVolatileShortArrayLoader( this ),
+				metadata.numTimepoints,
+				metadata.numSetups,
+				metadata.maxNumLevels,
+				metadata.maxLevels,
+				10 );
+		cellsDimensions = metadata.createCellsDimensions();
+	}
+
+	@Override
+	public void init( final Element elem, final File basePath )
+	{
+		try
+		{
+			baseUrl = elem.getChildText( "baseUrl" );
+			open();
+		}
+		catch ( final Exception e )
+		{
+			throw new RuntimeException( e );
+		}
+	}
+
+	@Override
+	public Element toXml( final File basePath )
+	{
+		throw new UnsupportedOperationException( "not implemented" );
+	}
+
+	@Override
+	public RandomAccessibleInterval< FloatType > getFloatImage( final View view )
+	{
+		throw new UnsupportedOperationException( "not implemented" );
+	}
+
+	@Override
+	public RandomAccessibleInterval< UnsignedShortType > getImage( final View view )
+	{
+		return getImage( view, 0 );
+	}
+
+	@Override
+	public RandomAccessibleInterval< UnsignedShortType > getImage( final View view, final int level )
+	{
+		if ( ! existsImageData( view, level ) )
+		{
+			System.err.println( "image data for " + view.getBasename() + " level " + level + " could not be found. Partition file missing?" );
+			return getMissingDataImage( view, level, new UnsignedShortType() );
+		}
+		final CellImg< UnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > >  img = prepareCachedImage( view, level, LoadingStrategy.BLOCKING );
+		final UnsignedShortType linkedType = new UnsignedShortType( img );
+		img.setLinkedType( linkedType );
+		return img;
+	}
+
+	@Override
+	public RandomAccessibleInterval< VolatileUnsignedShortType > getVolatileImage( final View view, final int level )
+	{
+		if ( ! existsImageData( view, level ) )
+		{
+			System.err.println( "image data for " + view.getBasename() + " level " + level + " could not be found." );
+			return getMissingDataImage( view, level, new VolatileUnsignedShortType() );
+		}
+		final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > >  img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED );
+		final VolatileUnsignedShortType linkedType = new VolatileUnsignedShortType( img );
+		img.setLinkedType( linkedType );
+		return img;
+	}
+
+	public VolatileGlobalCellCache< VolatileShortArray > getCache()
+	{
+		return cache;
+	}
+
+	@Override
+	public double[][] getMipmapResolutions( final int setup )
+	{
+		return metadata.perSetupMipmapResolutions.get( setup );
+	}
+
+	public int[][] getSubdivisions( final int setup )
+	{
+		return metadata.perSetupSubdivisions.get( setup );
+	}
+
+	@Override
+	public int numMipmapLevels( final int setup )
+	{
+		return getMipmapResolutions( setup ).length;
+	}
+
+	/**
+	 * Checks whether the given image data is present on the server.
+	 *
+	 * @return true, if the given image data is present.
+	 */
+	public boolean existsImageData( final View view, final int level )
+	{
+		final int timepoint = view.getTimepointIndex();
+		final int setup = view.getSetupIndex();
+		return existsImageData( timepoint, setup, level );
+	}
+
+	/**
+	 * Checks whether the given image data is present on the server.
+	 *
+	 * @return true, if the given image data is present.
+	 */
+	public boolean existsImageData( final int timepoint, final int setup, final int level )
+	{
+		final int index = getViewInfoCacheIndex( timepoint, setup, level );
+		return metadata.existence[ index ];
+	}
+
+	/**
+	 * For images that are missing on the server, a constant image is created.
+	 * If the dimension of the missing image is present in
+	 * {@link RemoteImageLoaderMetaData#dimensions} then use that. Otherwise
+	 * create a 1x1x1 image.
+	 */
+	protected < T > RandomAccessibleInterval< T > getMissingDataImage( final View view, final int level, final T constant )
+	{
+		final int t = view.getTimepointIndex();
+		final int s = view.getSetupIndex();
+		final int index = getViewInfoCacheIndex( t, s, level );
+		long[] d = metadata.dimensions[ index ];
+		if ( d == null )
+			d = new long[] { 1, 1, 1 };
+		return Views.interval( new ConstantRandomAccessible< T >( constant, 3 ), new FinalInterval( d ) );
+	}
+
+	public long[] getImageDimension( final int timepoint, final int setup, final int level )
+	{
+		final int index = getViewInfoCacheIndex( timepoint, setup, level );
+		return metadata.dimensions[ index ];
+	}
+
+	private int getViewInfoCacheIndex( final int timepoint, final int setup, final int level )
+	{
+		return level + metadata.maxNumLevels * ( setup + metadata.numSetups * timepoint );
+	}
+
+	int getCellIndex( final int timepoint, final int setup, final int level, final long[] globalPosition )
+	{
+		final int index = getViewInfoCacheIndex( timepoint, setup, level );
+		final int[] cellSize = getSubdivisions( setup )[ level ];
+		final int[] cellPos = new int[] {
+				( int ) globalPosition[ 0 ] / cellSize[ 0 ],
+				( int ) globalPosition[ 1 ] / cellSize[ 1 ],
+				( int ) globalPosition[ 2 ] / cellSize[ 2 ] };
+		return IntervalIndexer.positionToIndex( cellPos, cellsDimensions[ index ] );
+	}
+
+	/**
+	 * (Almost) create a {@link CellImg} backed by the cache.
+	 * The created image needs a {@link NativeImg#setLinkedType(net.imglib2.type.Type) linked type} before it can be used.
+	 * The type should be either {@link UnsignedShortType} and {@link VolatileUnsignedShortType}.
+	 */
+	protected < T extends NativeType< T > > CellImg< T, VolatileShortArray, VolatileCell< VolatileShortArray > > prepareCachedImage( final View view, final int level, final LoadingStrategy loadingStrategy )
+	{
+		if ( cache == null )
+			throw new RuntimeException( "no connection open" );
+
+		final int timepoint = view.getTimepointIndex();
+		final int setup = view.getSetupIndex();
+		final long[] dimensions = getImageDimension( timepoint, setup, level );
+		final int[] cellDimensions = metadata.perSetupSubdivisions.get( setup )[ level ];
+
+		final CellCache< VolatileShortArray > c = cache.new VolatileCellCache( timepoint, setup, level, loadingStrategy );
+		final VolatileImgCells< VolatileShortArray > cells = new VolatileImgCells< VolatileShortArray >( c, 1, dimensions, cellDimensions );
+		final CellImg< T, VolatileShortArray, VolatileCell< VolatileShortArray > > img = new CellImg< T, VolatileShortArray, VolatileCell< VolatileShortArray > >( null, cells );
+		return img;
+	}
+
+	private final UnsignedShortType type = new UnsignedShortType();
+
+	private final VolatileUnsignedShortType volatileType = new VolatileUnsignedShortType();
+
+	@Override
+	public UnsignedShortType getImageType()
+	{
+		return type;
+	}
+
+	@Override
+	public VolatileUnsignedShortType getVolatileImageType()
+	{
+		return volatileType;
+	}
+}
diff --git a/src/main/java/bdv/img/remote/RemoteImageLoaderMetaData.java b/src/main/java/bdv/img/remote/RemoteImageLoaderMetaData.java
new file mode 100644
index 0000000000000000000000000000000000000000..4283ecf3ed92e6bcbe6a5de21b6fb41708bc506b
--- /dev/null
+++ b/src/main/java/bdv/img/remote/RemoteImageLoaderMetaData.java
@@ -0,0 +1,97 @@
+package bdv.img.remote;
+
+import java.util.ArrayList;
+
+import bdv.img.hdf5.Hdf5ImageLoader;
+
+public class RemoteImageLoaderMetaData
+{
+	protected final ArrayList< double[][] > perSetupMipmapResolutions;
+
+	protected final ArrayList< int[][] > perSetupSubdivisions;
+
+	protected int numTimepoints;
+
+	protected int numSetups;
+
+	protected int[] maxLevels;
+
+	protected int maxNumLevels;
+
+	/**
+	 * An array of long[] arrays with {@link #numTimepoints} *
+	 * {@link #numSetups} * {@link #maxNumLevels} entries. Every entry is the
+	 * dimensions of one image (identified by flattened index of level, setup,
+	 * and timepoint).
+	 */
+	protected long[][] dimensions;
+
+	/**
+	 * An array of Booleans with {@link #numTimepoints} * {@link #numSetups} *
+	 * {@link #maxNumLevels} entries. Every entry is the existence of one image
+	 * (identified by flattened index of level, setup, and timepoint).
+	 */
+	protected boolean[] existence;
+
+	public RemoteImageLoaderMetaData()
+	{
+		perSetupMipmapResolutions = new ArrayList< double[][] >();
+		perSetupSubdivisions = new ArrayList< int[][] >();
+	}
+
+	public RemoteImageLoaderMetaData( final Hdf5ImageLoader imgLoader, final int numTimepoints, final int numSetups )
+	{
+		perSetupMipmapResolutions = new ArrayList< double[][] >();
+		perSetupSubdivisions = new ArrayList< int[][] >();
+		this.numTimepoints = numTimepoints;
+		this.numSetups = numSetups;
+		maxLevels = new int[ numSetups ];
+		maxNumLevels = 0;
+		for ( int setup = 0; setup < numSetups; ++setup )
+		{
+			perSetupMipmapResolutions.add( imgLoader.getMipmapResolutions( setup ) );
+			perSetupSubdivisions.add( imgLoader.getSubdivisions( setup ) );
+			final int numLevels = imgLoader.numMipmapLevels( setup );
+			maxLevels[ setup ] = numLevels - 1;
+			if ( numLevels > maxNumLevels )
+				maxNumLevels = numLevels;
+		}
+		final int numImages = numTimepoints * numSetups * maxNumLevels;
+		dimensions = new long[ numImages ][];
+		existence = new boolean[ numImages ];
+
+		for ( int t = 0; t < numTimepoints; ++t )
+			for ( int s = 0; s < numSetups; ++s )
+				for ( int l = 0; l <= maxLevels[ s ]; ++l )
+				{
+					final int i = l + maxNumLevels * ( s + numSetups * t );
+					existence[ i ] = imgLoader.existsImageData( t, s, l );
+					dimensions[ i ] = imgLoader.getImageDimension( t, s, l );
+				}
+	}
+
+	/**
+	 * Create an array of int[] with {@link #numTimepoints} * {@link #numSetups}
+	 * * {@link #maxNumLevels} entries. Every entry is the dimensions in cells
+	 * (instead of pixels) of one image (identified by flattened index of level,
+	 * setup, and timepoint).
+	 */
+	protected int[][] createCellsDimensions()
+	{
+		final int numImages = numTimepoints * numSetups * maxNumLevels;
+		final int[][] cellsDimensions = new int[ numImages ][];
+		for ( int t = 0; t < numTimepoints; ++t )
+			for ( int s = 0; s < numSetups; ++s )
+				for ( int l = 0; l <= maxLevels[ s ]; ++l )
+				{
+					final int i = l + maxNumLevels * ( s + numSetups * t );
+					final long[] imageDimensions = dimensions[ i ];
+					final int[] cellSize = perSetupSubdivisions.get( s )[ l ];
+					cellsDimensions[ i ] = new int[] {
+							( int ) imageDimensions[ 0 ] / cellSize[ 0 ],
+							( int ) imageDimensions[ 1 ] / cellSize[ 1 ],
+							( int ) imageDimensions[ 2 ] / cellSize[ 2 ] };
+				}
+		return cellsDimensions;
+	}
+}
diff --git a/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..6db202a2bae109f96471e0a140995930257f52a9
--- /dev/null
+++ b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
@@ -0,0 +1,76 @@
+package bdv.img.remote;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
+import bdv.img.cache.CacheArrayLoader;
+
+public class RemoteVolatileShortArrayLoader implements CacheArrayLoader< VolatileShortArray >
+{
+	private VolatileShortArray theEmptyArray;
+
+	private final RemoteImageLoader imgLoader;
+
+	public RemoteVolatileShortArrayLoader( final RemoteImageLoader imgLoader )
+	{
+		theEmptyArray = new VolatileShortArray( 32 * 32 * 32, false );
+		this.imgLoader = imgLoader;
+	}
+
+	@Override
+	public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
+	{
+		final int index = imgLoader.getCellIndex( timepoint, setup, level, min );
+		final short[] data = new short[ dimensions[ 0 ] * dimensions[ 1 ] * dimensions[ 2 ] ];
+		try
+		{
+			final URL url = new URL( String.format( "%s?p=cell/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d",
+					imgLoader.baseUrl,
+					index,
+					timepoint,
+					setup,
+					level,
+					dimensions[ 0 ],
+					dimensions[ 1 ],
+					dimensions[ 2 ],
+					min[ 0 ],
+					min[ 1 ],
+					min[ 2 ] ) );
+			final InputStream s = url.openStream();
+			final byte[] buf = new byte[ data.length * 2 ];
+			for ( int i = 0, l = s.read( buf, 0, buf.length ); l != -1; i += l, l = s.read( buf, i, buf.length - i ) );
+			for ( int i = 0, j = 0; i < data.length; ++i, j += 2 )
+				data[ i ] = ( short ) ( ( ( buf[ j ] & 0xff ) << 8 ) | ( buf[ j + 1 ] & 0xff ) );
+			s.close();
+		}
+		catch ( final MalformedURLException e )
+		{
+			e.printStackTrace();
+		}
+		catch ( final IOException e )
+		{
+			e.printStackTrace();
+		}
+		return new VolatileShortArray( data, true );
+	}
+
+	@Override
+	public VolatileShortArray emptyArray( final int[] dimensions )
+	{
+		int numEntities = 1;
+		for ( int i = 0; i < dimensions.length; ++i )
+			numEntities *= dimensions[ i ];
+		if ( theEmptyArray.getCurrentStorageArray().length < numEntities )
+			theEmptyArray = new VolatileShortArray( numEntities, false );
+		return theEmptyArray;
+	}
+
+	@Override
+	public int getBytesPerElement() {
+		return 2;
+	}
+
+}
diff --git a/src/main/java/bdv/server/BigDataServer.java b/src/main/java/bdv/server/BigDataServer.java
index afe3a4cb4a6f32b86609c79fbee7d431769b5bbd..3356227d6978879ce9560a6ae4ae7f85fe717a42 100644
--- a/src/main/java/bdv/server/BigDataServer.java
+++ b/src/main/java/bdv/server/BigDataServer.java
@@ -1,35 +1,35 @@
-package server;
+package bdv.server;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.ObjectOutputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
+import java.io.PrintWriter;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.xml.parsers.ParserConfigurationException;
 
 import mpicbg.spim.data.SequenceDescription;
-import net.imglib2.img.basictypeaccess.array.ShortArray;
+import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
 
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.xml.sax.SAXException;
+import org.jdom2.JDOMException;
 
-import viewer.SequenceViewsLoader;
-import viewer.hdf5.Hdf5ImageLoader;
-import viewer.hdf5.Hdf5ImageLoader.DimensionsInfo;
-import viewer.hdf5.img.Hdf5Cell;
-import viewer.hdf5.img.Hdf5GlobalCellCache;
+import bdv.SequenceViewsLoader;
+import bdv.img.cache.VolatileCell;
+import bdv.img.cache.VolatileGlobalCellCache;
+import bdv.img.cache.VolatileGlobalCellCache.LoadingStrategy;
+import bdv.img.hdf5.Hdf5ImageLoader;
+import bdv.img.remote.RemoteImageLoaderMetaData;
 
-public class CellServer
+import com.google.gson.Gson;
+
+public class BigDataServer
 {
 	public static void main( final String[] args ) throws Exception
 	{
-		final String fn = args.length > 0 ? args[ 0 ] : "/Users/pietzsch/Desktop/Valia/valia.xml";
+		final String fn = args.length > 0 ? args[ 0 ] : "/Users/pietzsch/Desktop/data/fibsem.xml";
 		final Server server = new Server( 8080 );
 		server.setHandler( new CellHandler( fn ) );
 		server.start();
@@ -38,28 +38,20 @@ public class CellServer
 
 	static class CellHandler extends AbstractHandler
 	{
-		final Hdf5ImageLoader imgLoader;
+		private final VolatileGlobalCellCache< VolatileShortArray > cache;
 
-		final Hdf5GlobalCellCache< ShortArray > cache;
+		private final String metadataJson;
 
-		final byte[] initObjects;
+		private final RemoteImageLoaderMetaData metadata;
 
-		public CellHandler( final String xmlFilename ) throws ParserConfigurationException, SAXException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
+		public CellHandler( final String xmlFilename ) throws JDOMException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
 		{
 			final SequenceViewsLoader loader = new SequenceViewsLoader( xmlFilename );
 			final SequenceDescription seq = loader.getSequenceDescription();
-			imgLoader = ( Hdf5ImageLoader ) seq.imgLoader;
+			final Hdf5ImageLoader imgLoader = ( Hdf5ImageLoader ) seq.imgLoader;
 			cache = imgLoader.getCache();
-
-			final ArrayList< double[][] > perSetupResolutions = new ArrayList< double[][] >();
-			for ( int setup = 0; setup < seq.numViewSetups(); ++setup )
-				perSetupResolutions.add( imgLoader.getMipmapResolutions( setup ) );
-			final ByteArrayOutputStream bs = new ByteArrayOutputStream();
-			final ObjectOutputStream os = new ObjectOutputStream( bs );
-			os.writeInt( seq.numTimepoints() );
-			os.writeObject( perSetupResolutions );
-			os.close();
-			initObjects = bs.toByteArray();
+			metadata = new RemoteImageLoaderMetaData( imgLoader, seq.numTimepoints(), seq.numViewSetups() );
+			metadataJson = new Gson().toJson( metadata );
 		}
 
 		@Override
@@ -75,13 +67,20 @@ public class CellServer
 				final int timepoint = Integer.parseInt( parts[ 2 ] );
 				final int setup = Integer.parseInt( parts[ 3 ] );
 				final int level = Integer.parseInt( parts[ 4 ] );
-				Hdf5Cell< ShortArray > cell = cache.getGlobalIfCached( timepoint, setup, level, index );
+				VolatileCell< VolatileShortArray > cell = cache.getGlobalIfCached( timepoint, setup, level, index, LoadingStrategy.BLOCKING );
 				if ( cell == null )
 				{
-					final int[] cellDims = new int[] { Integer.parseInt( parts[ 5 ] ), Integer.parseInt( parts[ 6 ] ), Integer.parseInt( parts[ 7 ] ) };
-					final long[] cellMin = new long[] { Long.parseLong( parts[ 8 ] ), Long.parseLong( parts[ 9 ] ), Long.parseLong( parts[ 10 ] ) };
-					cell = cache.loadGlobal( cellDims, cellMin, timepoint, setup, level, index );
+					final int[] cellDims = new int[] {
+							Integer.parseInt( parts[ 5 ] ),
+							Integer.parseInt( parts[ 6 ] ),
+							Integer.parseInt( parts[ 7 ] ) };
+					final long[] cellMin = new long[] {
+							Long.parseLong( parts[ 8 ] ),
+							Long.parseLong( parts[ 9 ] ),
+							Long.parseLong( parts[ 10 ] ) };
+					cell = cache.createGlobal( cellDims, cellMin, timepoint, setup, level, index, LoadingStrategy.BLOCKING );
 				}
+
 				final short[] data = cell.getData().getCurrentStorageArray();
 				final byte[] buf = new byte[ 2 * data.length ];
 				for ( int i = 0, j = 0; i < data.length; i++ )
@@ -98,37 +97,15 @@ public class CellServer
 				final OutputStream os = response.getOutputStream();
 				os.write( buf );
 				os.close();
-
-//				final byte[] buf = new byte[ 4096 ];
-//				for ( int i = 0, l = Math.min( buf.length / 2, data.length - i ); i < data.length; i += l )
-//				{
-//					ByteBuffer.wrap( buf ).asShortBuffer().put( data, i, l );
-//					os.write( buf, 0, 2 * l );
-//				}
-//				os.close();
-			}
-			else if ( parts[ 0 ].equals( "dim" ) )
-			{
-				final int timepoint = Integer.parseInt( parts[ 1 ] );
-				final int setup = Integer.parseInt( parts[ 2 ] );
-				final int level = Integer.parseInt( parts[ 3 ] );
-				final DimensionsInfo info = imgLoader.getDimensionsInfo( timepoint, setup, level );
-
-				response.setContentType( "application/octet-stream" );
-				response.setStatus( HttpServletResponse.SC_OK );
-				baseRequest.setHandled( true );
-				final ObjectOutputStream os = new ObjectOutputStream( response.getOutputStream() );
-				os.writeObject( info );
-				os.close();
 			}
 			else if ( parts[ 0 ].equals( "init" ) )
 			{
 				response.setContentType( "application/octet-stream" );
 				response.setStatus( HttpServletResponse.SC_OK );
 				baseRequest.setHandled( true );
-				final OutputStream os = response.getOutputStream();
-				os.write( initObjects );
-				os.close();
+				final PrintWriter ow = response.getWriter();
+				ow.write( metadataJson );
+				ow.close();
 			}
 		}
 	}