/* * Copyright (C) 2008 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * 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/>. */ package org.novelang.produce; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import static com.google.common.base.Preconditions.checkArgument; import org.novelang.common.Renderable; import org.novelang.common.metadata.Page; import org.novelang.common.metadata.PageIdentifier; import org.novelang.logger.Logger; import org.novelang.logger.LoggerFactory; import org.novelang.outfit.TemporaryFileTools; import org.novelang.rendering.buffer.CisternOutputStream; import org.novelang.rendering.multipage.PagesExtractor; /** * Dispatches one or more {@link Page}s into {@code OutputStream}s, and feeds them with rendered * content. * This class is an abstraction over two different behaviors: single stream for * {@link org.novelang.daemon.DocumentHandler}, and multiple files corresponding to multiple pages * in case of a multipage document for {@link org.novelang.batch.DocumentGenerator}. * The purpose is to insert this specific logic inside the * {@link org.novelang.rendering.GenericRenderer} which creates the * {@link org.novelang.rendering.multipage.PagesExtractor} (as a * {@link org.novelang.rendering.FragmentWriter}) just before the rendering occurs. * * @author Laurent Caillette */ public abstract class StreamDirector { private static final Logger LOGGER = LoggerFactory.getLogger( StreamDirector.class ) ; /** * Buffer size for {@link org.novelang.rendering.buffer.CisternOutputStream}. */ private static final int BUFFER_SIZE_BYTES = 1024 * 1024 ; public void feedStreams( final Renderable renderable, final PagesExtractor pageIdentifierExtractor, final PageIdentifier pageIdentifier, final StreamFeeder streamFeeder ) throws Exception { if( pageIdentifierExtractor == null ) { feedDefaultPage( renderable, streamFeeder ) ; } else { final ImmutableMap< PageIdentifier,String > pageMap = pageIdentifierExtractor.extractPages( renderable.getDocumentTree() ) ; if( pageMap.isEmpty() ) { LOGGER.info( "The document is single-paged (no PageMap defined)." ) ; feedDefaultPage( renderable, streamFeeder ) ; } else { LOGGER.info( "Using pageMap: ", pageMap ) ; if( pageIdentifier == null ) { feedDefaultPage( renderable, streamFeeder ) ; if( supportsMultipage() ) { LOGGER.debug( "Feeding additional page(s)... " ) ; for( final PageIdentifier someIdentifier : pageMap.keySet() ) { feedPage( renderable, streamFeeder, Page.get( pageMap, someIdentifier ) ); } } } else { final Page page = Page.get( pageMap, pageIdentifier ) ; feedPage( renderable, streamFeeder, page ) ; } } } } private void feedDefaultPage( final Renderable renderable, final StreamFeeder streamFeeder ) throws Exception { feedPage( renderable, streamFeeder, null ) ; } private void feedPage( final Renderable renderable, final StreamFeeder streamFeeder, final Page page ) throws Exception { final OutputStream rawOutputStream = getOutputStream( page == null ? null : page.getPageIdentifier() ) ; final CisternOutputStream deferredOutputStream = new CisternOutputStream( TemporaryFileTools.TEMPORARY_FILE_SERVICE.createFileSupplier( "page", ".any" ), BUFFER_SIZE_BYTES ) ; try { streamFeeder.feed( renderable, deferredOutputStream, page ) ; deferredOutputStream.copy( rawOutputStream ) ; } finally { try { finishWith( deferredOutputStream ) ; } finally { if( deferredOutputStream.isOpen() ) { deferredOutputStream.close() ; } } } } protected abstract OutputStream getOutputStream( final PageIdentifier pageIdentifier ) throws IOException ; protected abstract void finishWith( final OutputStream outputStream ) throws IOException ; protected abstract boolean supportsMultipage() ; /** * Wires code from {@link org.novelang.produce.DocumentProducer} at the point it only deals * with {@code OutputStream}. */ public interface StreamFeeder { void feed( Renderable rendered, OutputStream outputStream, Page page ) throws Exception ; } public static StreamDirector forExistingStream( final OutputStream outputStream ) { return new StreamDirector() { @Override protected OutputStream getOutputStream( final PageIdentifier pageIdentifier ) { return outputStream ; } @Override protected void finishWith( final OutputStream leftUntouched ) { } @Override protected boolean supportsMultipage() { return false ; } } ; } public static StreamDirector forDirectory( final DocumentRequest documentRequest, final File directory ) { checkArgument( directory.isDirectory(), "Not a directory: '" + directory + "'" ) ; return new StreamDirector() { @Override protected OutputStream getOutputStream( final PageIdentifier pageIdentifier ) throws IOException { final String relativeFileName = documentRequest.getDocumentSourceName() + ( pageIdentifier == null ? "" : DocumentRequest.PAGEIDENTIFIER_PREFIX + pageIdentifier.getName() ) + "." + documentRequest.getRenditionMimeType().getFileExtension() ; final File outputFile = new File( directory, relativeFileName ) ; FileUtils.forceMkdir( outputFile.getParentFile() ); LOGGER.info( "Generating document file '", outputFile.getAbsolutePath(), "'..." ) ; return new FileOutputStream( outputFile ) ; } @Override protected void finishWith( final OutputStream outputStream ) throws IOException { outputStream.close() ; } @Override protected boolean supportsMultipage() { return true ; } } ; } }