You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AFPResourceManager.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.afp;
  19. import java.io.BufferedInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.net.URI;
  24. import java.net.URISyntaxException;
  25. import java.util.Map;
  26. import org.apache.commons.io.IOUtils;
  27. import org.apache.commons.logging.Log;
  28. import org.apache.commons.logging.LogFactory;
  29. import org.apache.fop.afp.AFPResourceLevel.ResourceType;
  30. import org.apache.fop.afp.fonts.AFPFont;
  31. import org.apache.fop.afp.fonts.CharacterSet;
  32. import org.apache.fop.afp.modca.AbstractNamedAFPObject;
  33. import org.apache.fop.afp.modca.AbstractPageObject;
  34. import org.apache.fop.afp.modca.IncludeObject;
  35. import org.apache.fop.afp.modca.IncludedResourceObject;
  36. import org.apache.fop.afp.modca.PageSegment;
  37. import org.apache.fop.afp.modca.Registry;
  38. import org.apache.fop.afp.modca.ResourceGroup;
  39. import org.apache.fop.afp.modca.ResourceObject;
  40. import org.apache.fop.afp.util.AFPResourceAccessor;
  41. import org.apache.fop.afp.util.AFPResourceUtil;
  42. import org.apache.fop.apps.io.InternalResourceResolver;
  43. /**
  44. * Manages the creation and storage of document resources
  45. */
  46. public class AFPResourceManager {
  47. /** logging instance */
  48. private static Log log = LogFactory.getLog(AFPResourceManager.class);
  49. /** The AFP datastream (document tree) */
  50. private DataStream dataStream;
  51. /** Resource creation factory */
  52. private final Factory factory;
  53. private final AFPStreamer streamer;
  54. private final AFPDataObjectFactory dataObjectFactory;
  55. /** Maintain a reference count of instream objects for referencing purposes */
  56. private int instreamObjectCount;
  57. /** Mapping of resourceInfo to AbstractCachedObject */
  58. private final Map<AFPResourceInfo, AbstractCachedObject> includeObjectCache
  59. = new java.util.HashMap<AFPResourceInfo, AbstractCachedObject>();
  60. private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults();
  61. /**
  62. * Main constructor
  63. */
  64. public AFPResourceManager(InternalResourceResolver resourceResolver) {
  65. this.factory = new Factory();
  66. this.streamer = new AFPStreamer(factory, resourceResolver);
  67. this.dataObjectFactory = new AFPDataObjectFactory(factory);
  68. }
  69. /**
  70. * Sets the outputstream
  71. *
  72. * @param paintingState the AFP painting state
  73. * @param outputStream the outputstream
  74. * @return a new AFP DataStream
  75. * @throws IOException thrown if an I/O exception of some sort has occurred
  76. */
  77. public DataStream createDataStream(AFPPaintingState paintingState, OutputStream outputStream)
  78. throws IOException {
  79. this.dataStream = streamer.createDataStream(paintingState);
  80. streamer.setOutputStream(outputStream);
  81. return this.dataStream;
  82. }
  83. /**
  84. * Returns the AFP DataStream
  85. *
  86. * @return the AFP DataStream
  87. */
  88. public DataStream getDataStream() {
  89. return this.dataStream;
  90. }
  91. /**
  92. * Tells the streamer to write
  93. *
  94. * @throws IOException thrown if an I/O exception of some sort has occurred.
  95. */
  96. public void writeToStream() throws IOException {
  97. streamer.close();
  98. }
  99. /**
  100. * Sets the default resource group URI.
  101. *
  102. * @param uri the default resource group URI
  103. */
  104. public void setDefaultResourceGroupUri(URI uri) {
  105. streamer.setDefaultResourceGroupUri(uri);
  106. }
  107. /**
  108. * Tries to create an include of a data object that has been previously added to the
  109. * AFP data stream. If no such object was available, the method returns false which serves
  110. * as a signal that the object has to be created.
  111. * @param dataObjectInfo the data object info
  112. * @return true if the inclusion succeeded, false if the object was not available
  113. * @throws IOException thrown if an I/O exception of some sort has occurred.
  114. */
  115. public boolean tryIncludeObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
  116. AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
  117. updateResourceInfoUri(resourceInfo);
  118. return includeCachedObject(resourceInfo, dataObjectInfo.getObjectAreaInfo());
  119. }
  120. /**
  121. * Creates a new data object in the AFP datastream
  122. *
  123. * @param dataObjectInfo the data object info
  124. *
  125. * @throws IOException thrown if an I/O exception of some sort has occurred.
  126. */
  127. public void createObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
  128. if (tryIncludeObject(dataObjectInfo)) {
  129. //Object has already been produced and is available by inclusion, so return early.
  130. return;
  131. }
  132. AbstractNamedAFPObject namedObj = null;
  133. AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
  134. boolean useInclude = true;
  135. Registry.ObjectType objectType = null;
  136. // new resource so create
  137. if (dataObjectInfo instanceof AFPImageObjectInfo) {
  138. AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo;
  139. namedObj = dataObjectFactory.createImage(imageObjectInfo);
  140. } else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
  141. AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)dataObjectInfo;
  142. namedObj = dataObjectFactory.createGraphic(graphicsObjectInfo);
  143. } else {
  144. // natively embedded data object
  145. namedObj = dataObjectFactory.createObjectContainer(dataObjectInfo);
  146. objectType = dataObjectInfo.getObjectType();
  147. useInclude = objectType != null && objectType.isIncludable();
  148. }
  149. AFPResourceLevel resourceLevel = resourceInfo.getLevel();
  150. ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
  151. useInclude &= resourceGroup != null;
  152. if (useInclude) {
  153. final boolean usePageSegment = dataObjectInfo.isCreatePageSegment();
  154. // if it is to reside within a resource group at print-file or external level
  155. if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) {
  156. if (usePageSegment) {
  157. String pageSegmentName = "S10" + namedObj.getName().substring(3);
  158. namedObj.setName(pageSegmentName);
  159. PageSegment seg = new PageSegment(pageSegmentName);
  160. seg.addObject(namedObj);
  161. namedObj = seg;
  162. }
  163. // wrap newly created data object in a resource object
  164. namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType);
  165. }
  166. // add data object into its resource group destination
  167. resourceGroup.addObject(namedObj);
  168. includeObject(namedObj, dataObjectInfo);
  169. } else {
  170. // not to be included so inline data object directly into the current page
  171. dataStream.getCurrentPage().addObject(namedObj);
  172. }
  173. }
  174. private abstract class AbstractCachedObject {
  175. protected String objectName;
  176. protected AFPDataObjectInfo dataObjectInfo;
  177. public AbstractCachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
  178. this.objectName = objectName;
  179. this.dataObjectInfo = dataObjectInfo;
  180. }
  181. protected abstract void includeObject();
  182. }
  183. private class CachedPageSegment extends AbstractCachedObject {
  184. public CachedPageSegment(String objectName, AFPDataObjectInfo dataObjectInfo) {
  185. super(objectName, dataObjectInfo);
  186. }
  187. protected void includeObject() {
  188. includePageSegment(dataObjectInfo, objectName);
  189. }
  190. }
  191. private class CachedObject extends AbstractCachedObject {
  192. public CachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
  193. super(objectName, dataObjectInfo);
  194. }
  195. protected void includeObject() {
  196. AFPResourceManager.this.includeObject(dataObjectInfo, objectName);
  197. }
  198. }
  199. private void includeObject(AbstractNamedAFPObject namedObj, AFPDataObjectInfo dataObjectInfo) {
  200. // create the include object
  201. String objectName = namedObj.getName();
  202. AbstractCachedObject cachedObject;
  203. if (dataObjectInfo.isCreatePageSegment()) {
  204. cachedObject = new CachedPageSegment(objectName, dataObjectInfo);
  205. } else {
  206. cachedObject = new CachedObject(objectName, dataObjectInfo);
  207. }
  208. cachedObject.includeObject();
  209. includeObjectCache.put(dataObjectInfo.getResourceInfo(), cachedObject);
  210. //The data field of dataObjectInfo is not further required
  211. // therefore we are safe to null the reference, saving memory
  212. dataObjectInfo.setData(null);
  213. }
  214. /**
  215. * TODO
  216. * @param resourceInfo
  217. * @return
  218. */
  219. public boolean isObjectCached(AFPResourceInfo resourceInfo) {
  220. return includeObjectCache.containsKey(resourceInfo);
  221. }
  222. /**
  223. * TODO
  224. * @param resourceInfo
  225. * @param areaInfo
  226. * @return
  227. */
  228. public boolean includeCachedObject(AFPResourceInfo resourceInfo, AFPObjectAreaInfo areaInfo) {
  229. String objectName;
  230. AbstractCachedObject cachedObject = includeObjectCache.get(resourceInfo);
  231. if (cachedObject != null) {
  232. if (areaInfo != null) {
  233. cachedObject.dataObjectInfo.setObjectAreaInfo(areaInfo);
  234. }
  235. cachedObject.includeObject();
  236. return true;
  237. } else {
  238. return false;
  239. }
  240. }
  241. private void updateResourceInfoUri(AFPResourceInfo resourceInfo) {
  242. String uri = resourceInfo.getUri();
  243. if (uri == null) {
  244. uri = "/";
  245. }
  246. // if this is an instream data object adjust the uri to ensure that its unique
  247. if (uri.endsWith("/")) {
  248. uri += "#" + (++instreamObjectCount);
  249. resourceInfo.setUri(uri);
  250. }
  251. }
  252. private void includeObject(AFPDataObjectInfo dataObjectInfo,
  253. String objectName) {
  254. IncludeObject includeObject = dataObjectFactory.createInclude(objectName, dataObjectInfo);
  255. dataStream.getCurrentPage().addObject(includeObject);
  256. }
  257. /**
  258. * Handles font embedding. If a font is embeddable and has not already been embedded it will be.
  259. * @param afpFont the AFP font to be checked for embedding
  260. * @param charSet the associated character set
  261. * @throws IOException if there's a problem while embedding the external resources
  262. */
  263. public void embedFont(AFPFont afpFont, CharacterSet charSet)
  264. throws IOException {
  265. if (afpFont.isEmbeddable()) {
  266. //Embed fonts (char sets and code pages)
  267. if (charSet.getResourceAccessor() != null) {
  268. AFPResourceAccessor accessor = charSet.getResourceAccessor();
  269. createIncludedResource(
  270. charSet.getName(), accessor,
  271. ResourceObject.TYPE_FONT_CHARACTER_SET);
  272. createIncludedResource(
  273. charSet.getCodePage(), accessor,
  274. ResourceObject.TYPE_CODE_PAGE);
  275. }
  276. }
  277. }
  278. private void includePageSegment(AFPDataObjectInfo dataObjectInfo,
  279. String pageSegmentName) {
  280. int x = dataObjectInfo.getObjectAreaInfo().getX();
  281. int y = dataObjectInfo.getObjectAreaInfo().getY();
  282. AbstractPageObject currentPage = dataStream.getCurrentPage();
  283. boolean createHardPageSegments = true;
  284. currentPage.createIncludePageSegment(pageSegmentName, x, y, createHardPageSegments);
  285. }
  286. /**
  287. * Creates an included resource object by loading the contained object from a file.
  288. * @param resourceName the name of the resource
  289. * @param accessor resource accessor to access the resource with
  290. * @param resourceObjectType the resource object type ({@link ResourceObject}.*)
  291. * @throws IOException if an I/O error occurs while loading the resource
  292. */
  293. public void createIncludedResource(String resourceName, AFPResourceAccessor accessor,
  294. byte resourceObjectType) throws IOException {
  295. URI uri;
  296. try {
  297. uri = new URI(resourceName.trim());
  298. } catch (URISyntaxException e) {
  299. throw new IOException("Could not create URI from resource name: " + resourceName
  300. + " (" + e.getMessage() + ")");
  301. }
  302. createIncludedResource(resourceName, uri, accessor, resourceObjectType);
  303. }
  304. /**
  305. * Creates an included resource object by loading the contained object from a file.
  306. * @param resourceName the name of the resource
  307. * @param uri the URI for the resource
  308. * @param accessor resource accessor to access the resource with
  309. * @param resourceObjectType the resource object type ({@link ResourceObject}.*)
  310. * @throws IOException if an I/O error occurs while loading the resource
  311. */
  312. public void createIncludedResource(String resourceName, URI uri, AFPResourceAccessor accessor,
  313. byte resourceObjectType) throws IOException {
  314. AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
  315. AFPResourceInfo resourceInfo = new AFPResourceInfo();
  316. resourceInfo.setLevel(resourceLevel);
  317. resourceInfo.setName(resourceName);
  318. resourceInfo.setUri(uri.toASCIIString());
  319. AbstractCachedObject cachedObject = includeObjectCache.get(resourceInfo);
  320. if (cachedObject == null) {
  321. if (log.isDebugEnabled()) {
  322. log.debug("Adding included resource: " + resourceName);
  323. }
  324. IncludedResourceObject resourceContent = new IncludedResourceObject(
  325. resourceName, accessor, uri);
  326. ResourceObject resourceObject = factory.createResource(resourceName);
  327. resourceObject.setDataObject(resourceContent);
  328. resourceObject.setType(resourceObjectType);
  329. ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
  330. resourceGroup.addObject(resourceObject);
  331. //TODO what is the data object?
  332. cachedObject = new CachedObject(resourceName, null);
  333. // record mapping of resource info to data object resource name
  334. includeObjectCache.put(resourceInfo, cachedObject);
  335. } else {
  336. //skip, already created
  337. }
  338. }
  339. /**
  340. * Creates an included resource extracting the named resource from an external source.
  341. * @param resourceName the name of the resource
  342. * @param uri the URI for the resource
  343. * @param accessor resource accessor to access the resource with
  344. * @throws IOException if an I/O error occurs while loading the resource
  345. */
  346. public void createIncludedResourceFromExternal(final String resourceName,
  347. final URI uri, final AFPResourceAccessor accessor) throws IOException {
  348. AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
  349. AFPResourceInfo resourceInfo = new AFPResourceInfo();
  350. resourceInfo.setLevel(resourceLevel);
  351. resourceInfo.setName(resourceName);
  352. resourceInfo.setUri(uri.toASCIIString());
  353. AbstractCachedObject cachedObject = includeObjectCache.get(resourceInfo);
  354. if (cachedObject == null) {
  355. ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
  356. //resourceObject delegates write commands to copyNamedResource()
  357. //The included resource may already be wrapped in a resource object
  358. AbstractNamedAFPObject resourceObject = new AbstractNamedAFPObject(null) {
  359. @Override
  360. protected void writeContent(OutputStream os) throws IOException {
  361. InputStream inputStream = null;
  362. try {
  363. inputStream = accessor.createInputStream(uri);
  364. BufferedInputStream bin = new BufferedInputStream(inputStream);
  365. AFPResourceUtil.copyNamedResource(resourceName, bin, os);
  366. } finally {
  367. IOUtils.closeQuietly(inputStream);
  368. }
  369. }
  370. //bypass super.writeStart
  371. @Override
  372. protected void writeStart(OutputStream os) throws IOException { }
  373. //bypass super.writeEnd
  374. @Override
  375. protected void writeEnd(OutputStream os) throws IOException { }
  376. };
  377. resourceGroup.addObject(resourceObject);
  378. cachedObject = new CachedObject(resourceName, null);
  379. includeObjectCache.put(resourceInfo, cachedObject);
  380. }
  381. }
  382. /**
  383. * Sets resource level defaults. The existing defaults over merged with the ones passed in
  384. * as parameter.
  385. * @param defaults the new defaults
  386. */
  387. public void setResourceLevelDefaults(AFPResourceLevelDefaults defaults) {
  388. this.resourceLevelDefaults.mergeFrom(defaults);
  389. }
  390. /**
  391. * Returns the resource level defaults in use with this resource manager.
  392. * @return the resource level defaults
  393. */
  394. public AFPResourceLevelDefaults getResourceLevelDefaults() {
  395. return this.resourceLevelDefaults;
  396. }
  397. }