blob: 6fa4a17b70a0448da66a300481afbc439fd6ea50 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* $Id$ */
package org.apache.fop.afp;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.afp.AFPResourceLevel.ResourceType;
import org.apache.fop.afp.fonts.AFPFont;
import org.apache.fop.afp.fonts.CharacterSet;
import org.apache.fop.afp.modca.AbstractNamedAFPObject;
import org.apache.fop.afp.modca.AbstractPageObject;
import org.apache.fop.afp.modca.IncludeObject;
import org.apache.fop.afp.modca.IncludedResourceObject;
import org.apache.fop.afp.modca.PageSegment;
import org.apache.fop.afp.modca.Registry;
import org.apache.fop.afp.modca.ResourceGroup;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.util.AFPResourceAccessor;
import org.apache.fop.afp.util.AFPResourceUtil;
import org.apache.fop.apps.io.InternalResourceResolver;
/**
* Manages the creation and storage of document resources
*/
public class AFPResourceManager {
/** logging instance */
private static Log log = LogFactory.getLog(AFPResourceManager.class);
/** The AFP datastream (document tree) */
private DataStream dataStream;
/** Resource creation factory */
private final Factory factory;
private final AFPStreamer streamer;
private final AFPDataObjectFactory dataObjectFactory;
/** Maintain a reference count of instream objects for referencing purposes */
private int instreamObjectCount;
/** Mapping of resourceInfo to AbstractCachedObject */
private final Map<AFPResourceInfo, AbstractCachedObject> includeObjectCache
= new java.util.HashMap<AFPResourceInfo, AbstractCachedObject>();
private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults();
/**
* Main constructor
*/
public AFPResourceManager(InternalResourceResolver resourceResolver) {
this.factory = new Factory();
this.streamer = new AFPStreamer(factory, resourceResolver);
this.dataObjectFactory = new AFPDataObjectFactory(factory);
}
/**
* Sets the outputstream
*
* @param paintingState the AFP painting state
* @param outputStream the outputstream
* @return a new AFP DataStream
* @throws IOException thrown if an I/O exception of some sort has occurred
*/
public DataStream createDataStream(AFPPaintingState paintingState, OutputStream outputStream)
throws IOException {
this.dataStream = streamer.createDataStream(paintingState);
streamer.setOutputStream(outputStream);
return this.dataStream;
}
/**
* Returns the AFP DataStream
*
* @return the AFP DataStream
*/
public DataStream getDataStream() {
return this.dataStream;
}
/**
* Tells the streamer to write
*
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
public void writeToStream() throws IOException {
streamer.close();
}
/**
* Sets the default resource group URI.
*
* @param uri the default resource group URI
*/
public void setDefaultResourceGroupUri(URI uri) {
streamer.setDefaultResourceGroupUri(uri);
}
/**
* Tries to create an include of a data object that has been previously added to the
* AFP data stream. If no such object was available, the method returns false which serves
* as a signal that the object has to be created.
* @param dataObjectInfo the data object info
* @return true if the inclusion succeeded, false if the object was not available
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
public boolean tryIncludeObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
updateResourceInfoUri(resourceInfo);
return includeCachedObject(resourceInfo, dataObjectInfo.getObjectAreaInfo());
}
/**
* Creates a new data object in the AFP datastream
*
* @param dataObjectInfo the data object info
*
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
public void createObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
if (tryIncludeObject(dataObjectInfo)) {
//Object has already been produced and is available by inclusion, so return early.
return;
}
AbstractNamedAFPObject namedObj = null;
AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
boolean useInclude = true;
Registry.ObjectType objectType = null;
// new resource so create
if (dataObjectInfo instanceof AFPImageObjectInfo) {
AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo;
namedObj = dataObjectFactory.createImage(imageObjectInfo);
} else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)dataObjectInfo;
namedObj = dataObjectFactory.createGraphic(graphicsObjectInfo);
} else {
// natively embedded data object
namedObj = dataObjectFactory.createObjectContainer(dataObjectInfo);
objectType = dataObjectInfo.getObjectType();
useInclude = objectType != null && objectType.isIncludable();
}
AFPResourceLevel resourceLevel = resourceInfo.getLevel();
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
useInclude &= resourceGroup != null;
if (useInclude) {
final boolean usePageSegment = dataObjectInfo.isCreatePageSegment();
// if it is to reside within a resource group at print-file or external level
if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) {
if (usePageSegment) {
String pageSegmentName = "S10" + namedObj.getName().substring(3);
namedObj.setName(pageSegmentName);
PageSegment seg = new PageSegment(pageSegmentName);
seg.addObject(namedObj);
namedObj = seg;
}
// wrap newly created data object in a resource object
namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType);
}
// add data object into its resource group destination
resourceGroup.addObject(namedObj);
includeObject(namedObj, dataObjectInfo);
} else {
// not to be included so inline data object directly into the current page
dataStream.getCurrentPage().addObject(namedObj);
}
}
private abstract class AbstractCachedObject {
protected String objectName;
protected AFPDataObjectInfo dataObjectInfo;
public AbstractCachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
this.objectName = objectName;
this.dataObjectInfo = dataObjectInfo;
}
protected abstract void includeObject();
}
private class CachedPageSegment extends AbstractCachedObject {
public CachedPageSegment(String objectName, AFPDataObjectInfo dataObjectInfo) {
super(objectName, dataObjectInfo);
}
protected void includeObject() {
includePageSegment(dataObjectInfo, objectName);
}
}
private class CachedObject extends AbstractCachedObject {
public CachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
super(objectName, dataObjectInfo);
}
protected void includeObject() {
AFPResourceManager.this.includeObject(dataObjectInfo, objectName);
}
}
private void includeObject(AbstractNamedAFPObject namedObj, AFPDataObjectInfo dataObjectInfo) {
// create the include object
String objectName = namedObj.getName();
AbstractCachedObject cachedObject;
if (dataObjectInfo.isCreatePageSegment()) {
cachedObject = new CachedPageSegment(objectName, dataObjectInfo);
} else {
cachedObject = new CachedObject(objectName, dataObjectInfo);
}
cachedObject.includeObject();
includeObjectCache.put(dataObjectInfo.getResourceInfo(), cachedObject);
//The data field of dataObjectInfo is not further required
// therefore we are safe to null the reference, saving memory
dataObjectInfo.setData(null);
}
/**
* TODO
* @param resourceInfo
* @return
*/
public boolean isObjectCached(AFPResourceInfo resourceInfo) {
return includeObjectCache.containsKey(resourceInfo);
}
/**
* TODO
* @param resourceInfo
* @param areaInfo
* @return
*/
public boolean includeCachedObject(AFPResourceInfo resourceInfo, AFPObjectAreaInfo areaInfo) {
String objectName;
AbstractCachedObject cachedObject = includeObjectCache.get(resourceInfo);
if (cachedObject != null) {
if (areaInfo != null) {
cachedObject.dataObjectInfo.setObjectAreaInfo(areaInfo);
}
cachedObject.includeObject();
return true;
} else {
return false;
}
}
private void updateResourceInfoUri(AFPResourceInfo resourceInfo) {
String uri = resourceInfo.getUri();
if (uri == null) {
uri = "/";
}
// if this is an instream data object adjust the uri to ensure that its unique
if (uri.endsWith("/")) {
uri += "#" + (++instreamObjectCount);
resourceInfo.setUri(uri);
}
}
private void includeObject(AFPDataObjectInfo dataObjectInfo,
String objectName) {
IncludeObject includeObject = dataObjectFactory.createInclude(objectName, dataObjectInfo);
dataStream.getCurrentPage().addObject(includeObject);
}
/**
* Handles font embedding. If a font is embeddable and has not already been embedded it will be.
* @param afpFont the AFP font to be checked for embedding
* @param charSet the associated character set
* @throws IOException if there's a problem while embedding the external resources
*/
public void embedFont(AFPFont afpFont, CharacterSet charSet)
throws IOException {
if (afpFont.isEmbeddable()) {
//Embed fonts (char sets and code pages)
if (charSet.getResourceAccessor() != null) {
AFPResourceAccessor accessor = charSet.getResourceAccessor();
createIncludedResource(
charSet.getName(), accessor,
ResourceObject.TYPE_FONT_CHARACTER_SET);
createIncludedResource(
charSet.getCodePage(), accessor,
ResourceObject.TYPE_CODE_PAGE);
}
}
}
private void includePageSegment(AFPDataObjectInfo dataObjectInfo,
String pageSegmentName) {
int x = dataObjectInfo.getObjectAreaInfo().getX();
int y = dataObjectInfo.getObjectAreaInfo().getY();
AbstractPageObject currentPage = dataStream.getCurrentPage();
boolean createHardPageSegments = true;
currentPage.createIncludePageSegment(pageSegmentName, x, y, createHardPageSegments);
}
/**
* Creates an included resource object by loading the contained object from a file.
* @param resourceName the name of the resource
* @param accessor resource accessor to access the resource with
* @param resourceObjectType the resource object type ({@link ResourceObject}.*)
* @throws IOException if an I/O error occurs while loading the resource
*/
public void createIncludedResource(String resourceName, AFPResourceAccessor accessor,
byte resourceObjectType) throws IOException {
URI uri;
try {
uri = new URI(resourceName.trim());
} catch (URISyntaxException e) {
throw new IOException("Could not create URI from resource name: " + resourceName
+ " (" + e.getMessage() + ")");
}
createIncludedResource(resourceName, uri, accessor, resourceObjectType);
}
/**
* Creates an included resource object by loading the contained object from a file.
* @param resourceName the name of the resource
* @param uri the URI for the resource
* @param accessor resource accessor to access the resource with
* @param resourceObjectType the resource object type ({@link ResourceObject}.*)
* @throws IOException if an I/O error occurs while loading the resource
*/
public void createIncludedResource(String resourceName, URI uri, AFPResourceAccessor accessor,
byte resourceObjectType) throws IOException {
AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
AFPResourceInfo resourceInfo = new AFPResourceInfo();
resourceInfo.setLevel(resourceLevel);
resourceInfo.setName(resourceName);
resourceInfo.setUri(uri.toASCIIString());
AbstractCachedObject cachedObject = includeObjectCache.get(resourceInfo);
if (cachedObject == null) {
if (log.isDebugEnabled()) {
log.debug("Adding included resource: " + resourceName);
}
IncludedResourceObject resourceContent = new IncludedResourceObject(
resourceName, accessor, uri);
ResourceObject resourceObject = factory.createResource(resourceName);
resourceObject.setDataObject(resourceContent);
resourceObject.setType(resourceObjectType);
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
resourceGroup.addObject(resourceObject);
//TODO what is the data object?
cachedObject = new CachedObject(resourceName, null);
// record mapping of resource info to data object resource name
includeObjectCache.put(resourceInfo, cachedObject);
} else {
//skip, already created
}
}
/**
* Creates an included resource extracting the named resource from an external source.
* @param resourceName the name of the resource
* @param uri the URI for the resource
* @param accessor resource accessor to access the resource with
* @throws IOException if an I/O error occurs while loading the resource
*/
public void createIncludedResourceFromExternal(final String resourceName,
final URI uri, final AFPResourceAccessor accessor) throws IOException {
AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
AFPResourceInfo resourceInfo = new AFPResourceInfo();
resourceInfo.setLevel(resourceLevel);
resourceInfo.setName(resourceName);
resourceInfo.setUri(uri.toASCIIString());
AbstractCachedObject cachedObject = includeObjectCache.get(resourceInfo);
if (cachedObject == null) {
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
//resourceObject delegates write commands to copyNamedResource()
//The included resource may already be wrapped in a resource object
AbstractNamedAFPObject resourceObject = new AbstractNamedAFPObject(null) {
@Override
protected void writeContent(OutputStream os) throws IOException {
InputStream inputStream = null;
try {
inputStream = accessor.createInputStream(uri);
BufferedInputStream bin = new BufferedInputStream(inputStream);
AFPResourceUtil.copyNamedResource(resourceName, bin, os);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
//bypass super.writeStart
@Override
protected void writeStart(OutputStream os) throws IOException { }
//bypass super.writeEnd
@Override
protected void writeEnd(OutputStream os) throws IOException { }
};
resourceGroup.addObject(resourceObject);
cachedObject = new CachedObject(resourceName, null);
includeObjectCache.put(resourceInfo, cachedObject);
}
}
/**
* Sets resource level defaults. The existing defaults over merged with the ones passed in
* as parameter.
* @param defaults the new defaults
*/
public void setResourceLevelDefaults(AFPResourceLevelDefaults defaults) {
this.resourceLevelDefaults.mergeFrom(defaults);
}
/**
* Returns the resource level defaults in use with this resource manager.
* @return the resource level defaults
*/
public AFPResourceLevelDefaults getResourceLevelDefaults() {
return this.resourceLevelDefaults;
}
}