com.adobe.xfa.service.href.HrefStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
The newest version!
/**
* This service handles all href related actions in an XFA based application.
*
* Copyright 2005 Adobe Systems Incorporated. All Rights Reserved
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in
* accordance with the terms of the Adobe license agreement accompanying it.
* If you have received this file from a source other than Adobe, then your
* use, modification, or distribution of it requires the prior written
* permission of Adobe.
*/
package com.adobe.xfa.service.href;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.adobe.xfa.AppModel;
import com.adobe.xfa.Chars;
import com.adobe.xfa.Element;
import com.adobe.xfa.Node;
import com.adobe.xfa.XFA;
import com.adobe.xfa.protocol.ProtocolUtils;
import com.adobe.xfa.service.storage.PacketHandler;
import com.adobe.xfa.service.storage.XMLStorage;
import com.adobe.xfa.template.TemplateModelFactory;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.StringHolder;
import com.adobe.xfa.ut.StringUtils;
/**
*
* @exclude from published api -- Mike Tardif, May 2006.
*/
public final class HrefStore implements PacketHandler {
// Portions of this code implement AdobePatentID="B624"
@SuppressWarnings("unused")
private final static String patentRef = "AdobePatentID=\"B624\"";
final static class HrefData {
private AppModel moAppModel;
HrefData() {
moAppModel = null;
}
AppModel getAppModel() {
return moAppModel;
}
void setAppModel(AppModel oAppModel) {
moAppModel = oAppModel;
}
}
private static final class CacheItem {
HrefData moHrefData;
int mnCacheIndex;
int mnSize;
boolean mbLocked;
CacheItem() {
// moHrefData = null;
// mnCacheIndex = 0;
// mnSize = 0;
}
}
private final Map moHrefs;
private String msContainerConfigBaseUrl; // Config setting of containing fragment
private String msImplicitBaseUrl; // Base URL of containing document
private String msBaseUrl;
private String msAltUrl;
private boolean mbTrusted;
private int mnCacheIndex;
private int mnCacheSize;
private int mnCurrentCacheSize;
private final HrefService moHrefService;
/**
* Construct this object -- add an href to the store.
* @param oHrefService
* @param sBaseUrl the document base URL.
* @param sAltUrl the alternate document base URL.
* @param nCacheSize
*/
HrefStore(HrefService oHrefService, String sBaseUrl, String sAltUrl, int nCacheSize) {
moHrefService = oHrefService;
moHrefs = new HashMap();
msBaseUrl = sBaseUrl;
msAltUrl = sAltUrl;
mbTrusted = true;
//mnCacheIndex = 0;
mnCacheSize = nCacheSize;
//mnCurrentCacheSize = 0;
}
//
// add, remove and get prepend the supplied URL with the implicit base
// (ie. the base of the containing document, if any) before accessing
// the cache. That keeps entries unique so that we don't confuse two
// identical relative URLs that are referenced from containing documents
// in different directories. Note that the string produced may not be a
// valid URL but will be unique, which is all that really matters.
//
/**
* Add an href to the store.
* @param sUrl an URL.
*/
void add(String sUrl) {
//
// If href isn't already loaded in our cache Then
// add it to our cache.
//
String sBaseUrl = getImplicitBaseUrl();
String sFullUrl = (sBaseUrl != null) ? sBaseUrl + sUrl : sUrl;
CacheItem oItem = moHrefs.get(sFullUrl);
if (oItem == null)
moHrefs.put(sFullUrl, new CacheItem());
}
/**
* Remove an href from the store, base on its unique key.
* @param sUrl a key that will uniquely identify this href.
* @exception HrefStoreInvalidKeyException - thrown when the
* key specified is not valid for this store.
* @exception HrefStoreRelativePathException - thrown when the
* relative URL cannot be resolved.
*/
void remove(String sUrl) {
String sBaseUrl = getImplicitBaseUrl();
String sFullUrl = (sBaseUrl != null) ? sBaseUrl + sUrl : sUrl;
CacheItem oItem = moHrefs.get(sFullUrl);
if (oItem.mnCacheIndex >= 0)
mnCurrentCacheSize -= oItem.mnSize;
moHrefs.remove(sFullUrl);
}
private CacheItem get(String sUrl) {
String sBaseUrl = getImplicitBaseUrl();
String sFullUrl = (sBaseUrl != null) ? sBaseUrl + sUrl : sUrl;
return moHrefs.get(sFullUrl);
}
/**
* Remove all hrefs from the store.
*/
void clearCache() {
mnCurrentCacheSize = 0;
moHrefs.clear();
}
/**
* Get the number of hrefs currently in the Store.
*/
int size() {
return moHrefs.size();
}
/**
* Get the base URL that relative hrefs will be resolved against.
* @return a path to resolve relative hrefs against.
*/
String getBaseUrl() {
return msBaseUrl;
}
/**
* Set the base URL that relative hrefs will be resolved against.
* @param sBaseUrl a path to resolve relative hrefs against.
*/
void setBaseUrl(String sBaseUrl) {
msBaseUrl = ProtocolUtils.normalizeBaseUrl(sBaseUrl);
}
/**
* Get the alternate URL that relative hrefs will be resolved
* against, if a base url is not specified.
* @return a path to resolve relative hrefs against.
*/
String getAlternativeUrl() {
return msAltUrl;
}
/**
* Set the alternate URL that relative hrefs will be resolved
* against, if a base url is not specified.
* @param sAltUrl a path to resolve relative hrefs against.
*/
void setAlternativeUrl(String sAltUrl) {
msAltUrl = ProtocolUtils.normalizeBaseUrl(sAltUrl);
}
/**
* Get the trustiness of absolute urls.
* @return the trustiness. When set absolute hrefs are allowed.
*/
boolean isTrusted() {
return mbTrusted;
}
/**
* Set the trustiness of absolute urls. Trust implies that
* absolute hrefs are allowed.
* @param bTrusted allow absolute urls when true.
*/
void isTrusted(boolean bTrusted) {
mbTrusted = bTrusted;
}
/**
* Get the size of the cache in bytes. See the setCacheSize method.
* @return the size of the cache in bytes.
*/
int getCacheSize() {
return mnCacheSize;
}
/**
* Set the CacheSize in bytes. The store will use this value as an
* upper limit for loaded hrefs. Hrefs will be added and removed from
* the cache automatically based on the current state of the store. There
* will always be one href cached (the currently accessed href) even if
* the cache is set to zero.
* @param nCacheSize The size of the cache in bytes.
*/
void setCacheSize(int nCacheSize) {
mnCacheSize = nCacheSize;
}
/**
* Get the current size of the cache in bytes.
* @return the current size of the cache in bytes.
*/
int getCurrentCacheSize() {
return mnCurrentCacheSize;
}
/**
* Get the HrefData for the href based on the unique href key.
* @param sUrl an URL that will uniquely identify this href.
* @return an HrefData object containing the href info.
* @exception HrefStoreInvalidKeyException - thrown when the
* key specified is not valid for this store.
* @exception HrefStoreRelativePathException - thrown when the
* relative URL cannot be resolved.
* @exception HrefStoreLoadEmbeddedFailure - thrown when a problem
* is encountered decoding the Embedded href.
*/
HrefData getHrefData(String sUrl) {
CacheItem oItem = get(sUrl);
//
// The add method must be called first!
//
assert (oItem != null);
//
// Fetch this entry's HrefData. If non present, construct it.
//
HrefData oHrefData = oItem.moHrefData;
if (oHrefData == null) {
//
// Fetch a handle to the resolved URL.
//
StringHolder sRealUrl = new StringHolder();
InputStream oInStream = null;
ByteArrayOutputStream oOutStream = new ByteArrayOutputStream();
int nSize = 0;
try {
oInStream = resolveUrl(sUrl, sRealUrl);
//
// Read its contents into memory, recording the file size.
//
try {
byte[] iBuf = new byte[4096];
for (int nRead = 0; (nRead = oInStream.read(iBuf)) > 0; ) {
oOutStream.write(iBuf, 0, nRead);
nSize += nRead;
}
} catch (IOException e) {
throw new ExFull(e);
}
}
finally {
try {
if (oInStream != null) oInStream.close();
}
catch (IOException ex) {
}
}
//
// Update our cache index so we know what order
// this node was added into the cache. (FIFO)
//
oHrefData = new HrefData();
oItem.moHrefData = oHrefData;
oItem.mnSize = nSize;
oItem.mbLocked = true;
mnCurrentCacheSize += nSize;
oItem.mnCacheIndex = ++mnCacheIndex;
//
// Create an app model & template factory.
//
AppModel oAppModel = new AppModel(null);
oAppModel.addFactory(new TemplateModelFactory());
//
// Set this fragment model's href handler.
//
oAppModel.setHrefHandler(moHrefService);
oAppModel.setIsFragmentDoc(true);
oItem.moHrefData.setAppModel(oAppModel);
//
// Figure out and save the implicit base, which is the directory of
// the previously-fetched URL.
// We'll restore the previous setting in the finally block.
//
String sPrevious = getImplicitBaseUrl();
String sBase = sRealUrl.value;
if (sBase != null) {
int nLastSlash = sBase.lastIndexOf('/');
if (nLastSlash > 0)
setImplicitBaseUrl(sBase.substring(0, nLastSlash + 1));
}
//
// Save the msContainerConfigBaseUrl value. It may be
// updated during loadXDP for this particular XDP.
// We'll restore the previous setting in the finally block.
//
String msPreviousContainerConfigBaseUrl = msContainerConfigBaseUrl;
setContainerConfigBaseUrl(null);
try {
//
// Load the XFA template model.
//
XMLStorage oXMLStorage = new XMLStorage();
ByteArrayInputStream oMemStream
= new ByteArrayInputStream(oOutStream.toByteArray());
oOutStream = null;
oXMLStorage.loadXDP(oAppModel, oMemStream, this, this, false);
oMemStream = null;
//
// Record the current state of our search path so that href's
// within fragments can later be resolved according to the
// same path.
//
List oSearchPath = new ArrayList();
if (msContainerConfigBaseUrl != null)
oSearchPath.add(msContainerConfigBaseUrl);
if (msImplicitBaseUrl != null)
oSearchPath.add(msImplicitBaseUrl);
if (msBaseUrl != null)
oSearchPath.add(msBaseUrl);
if (msAltUrl != null)
oSearchPath.add(msAltUrl);
oAppModel.setFragmentSearchPath(oSearchPath);
//
// If our cache has gone over the limit, then lets remove some
// stuff to bring the cache within its limit.
//
if (mnCurrentCacheSize > mnCacheSize) {
// Create an array of CacheItem ordered by mnCacheIndex
CacheItem[] cacheItems = new CacheItem[moHrefs.size()];
moHrefs.values().toArray(cacheItems);
Arrays.sort(
cacheItems,
new Comparator() {
public int compare(CacheItem cacheItem1, CacheItem cacheItem2) {
if (cacheItem1.mnCacheIndex < cacheItem2.mnCacheIndex)
return -1;
if (cacheItem1.mnCacheIndex == cacheItem2.mnCacheIndex)
return 0;
return 1;
}
});
// Now walk through the list of cached items, and delete
// from the cache until our size is back under our limit.
for (CacheItem cacheItem : cacheItems) {
// If it's locked, then skip it. We always
// keep items in the cache when they're in use
// regardless of whether they fit in the cache.
if (cacheItem.mbLocked)
continue;
if (cacheItem.mnCacheIndex == 0)
continue;
// Cleanup the node
cacheItem.mnCacheIndex = 0;
cacheItem.moHrefData = null;
// update the current size of the cache
mnCurrentCacheSize -= cacheItem.mnSize;
if (mnCurrentCacheSize < mnCacheSize)
break;
}
}
}
finally {
oItem.mbLocked = false;
setImplicitBaseUrl(sPrevious);
setContainerConfigBaseUrl(msPreviousContainerConfigBaseUrl);
}
}
return oHrefData;
}
void setContainerConfigBaseUrl(String sContainerConfigBaseUrl) {
msContainerConfigBaseUrl = ProtocolUtils.normalizeBaseUrl(sContainerConfigBaseUrl);
}
private void setImplicitBaseUrl(String sImplicitBaseUrl) {
msImplicitBaseUrl = ProtocolUtils.normalizeBaseUrl(sImplicitBaseUrl);
}
private String getImplicitBaseUrl() {
return msImplicitBaseUrl;
}
/**
* Resolve a relative href URL to it's an absolute path based
* on the information set within the href service.
* @param sURL the URL to resolve
* @return a String representing the absolute URL.
* @exception XFAHrefStoreException - thrown when the
* relative URL cannot be resolved.
*/
private InputStream resolveUrl(String sURL, StringHolder sRealURL) {
//
// The url may have a "|" instead of ":" for the drive so
// replace it if necessary.
//
int nSlash = sURL.indexOf("|");
if (nSlash > 0) {
StringBuilder sPathBuf = new StringBuilder(sURL);
sPathBuf.setCharAt(nSlash, ':');
sURL = sPathBuf.toString();
}
//
// Try resolving the path according to the implicit base url (i.e.
// the directory of the previously-fetched url, if any), then the
// base url, and failing that, the alternate url.
// If can't then hurl.
//
InputStream resolvedStream = null;
if (! StringUtils.isEmpty(msContainerConfigBaseUrl)) {
resolvedStream = ProtocolUtils.checkUrl(msContainerConfigBaseUrl, sURL, mbTrusted, sRealURL);
}
if (resolvedStream == null && ! StringUtils.isEmpty(msImplicitBaseUrl)) {
resolvedStream = ProtocolUtils.checkUrl(msImplicitBaseUrl, sURL, mbTrusted, sRealURL);
}
if (resolvedStream == null && ! StringUtils.isEmpty(msBaseUrl)) {
resolvedStream = ProtocolUtils.checkUrl(msBaseUrl, sURL, mbTrusted, sRealURL);
}
if (resolvedStream == null && ! StringUtils.isEmpty(msAltUrl)) {
resolvedStream = ProtocolUtils.checkUrl(msAltUrl, sURL, mbTrusted, sRealURL);
}
if (resolvedStream == null) {
resolvedStream = ProtocolUtils.checkUrl("", sURL, mbTrusted, sRealURL);
}
if (resolvedStream == null) { // Cannot resolve url [%1].
MsgFormatPos oMsg = new MsgFormatPos(ResId.XFAHrefStoreException);
oMsg.format(sURL);
throw new ExFull(oMsg);
}
return resolvedStream;
}
// JavaPort: not needed.
// String cleanupRelativeURL(String sSource, Scalar oOk) {
// oOk.set(false);
// //
// // This code should ONLY be executed for URL's, not for local paths.
// //
// try {
// URI oSource = new URI(sSource);
// if (oSource.getScheme() == null)
// return sSource;
// }
// catch (/*URISyntax*/Exception e) {
// return sSource;
// }
// //
// // Get rid of any /./ included in the path in case this is one
// // of our "home-made" protocols (such as LiveCycle's repository
// // protocol) which doesn't seem to be able to handle those kind
// // of things.
// //
// StringBuilder sSrc = new StringBuilder(sSource);
// int nFoundAt = sSrc.indexOf("/./");
// while (nFoundAt >= 0) {
// sSrc.delete(nFoundAt + 1, nFoundAt + 2);
// nFoundAt = sSrc.indexOf("/./");
// }
// nFoundAt = sSrc.indexOf("/../");
// while (nFoundAt >= 0) {
// //
// // Now find the last / or ':' before the dots, because we
// // need to remove that directory. The reason we need to look for ':' as
// // well is that in our "home-made" protocol a notation like dest:mydir is
// // valid.
// //
// int i = nFoundAt - 1;
// while (i >= 0) {
// if (sSrc.charAt(i) == '/' || sSrc.charAt(i) == ':') {
// sSrc.delete(i + 1, nFoundAt + 3);
// break;
// }
// i--;
// }
// // Too many ..'s - we're out of directories to remove
// if (i < 0)
// return sSource;
// nFoundAt = sSrc.indexOf("/../");
// }
// oOk.set(true);
// return sSrc.toString();
// }
/*
* A PacketHandler used in loadXDP to filter user-specified nodes
* in an XDP file.
*
* The data parameter is the HrefStore calling loadXDP.
* The reason for this callback is that we need to get information
* out of the config DOM, but we can't wait until the whole XDP is
* loaded because we need the information WHILE the template is
* loading. If the proper config value is found, then the HrefStore's
* msContainerConfigBaseUrl is set to that value.
* Currently hard-coded to get "$config.present.common.template.base".
* Section 9.1 of the proposal at
* http://xtg.can.adobe.com/twiki/bin/view/XFA/FormFragmentsXFAV23Proposal
* does explicitly say that we'll look in this particular location
* within the fragment document, though the xfahrefservice
* shouldn't really know that it's the presentation agent that's u:f
sing it.
* Chances are, no-one else will use it, so it's probably not worth
* trying to make it more general, at least not now.
*/
public void filterPackets(Node oPacket, Object data) {
if (! (oPacket instanceof Element))
return;
String aName = oPacket.getName();
if (aName == XFA.CONFIG) {
Node oNode = oPacket;
if (oNode != null)
oNode = oNode.locateChildByName(XFA.PRESENT, 0);
if (oNode != null)
oNode = oNode.locateChildByName(XFA.COMMON, 0);
if (oNode != null)
oNode = oNode.locateChildByName(XFA.TEMPLATE, 0);
if (oNode != null)
oNode = oNode.locateChildByName(XFA.BASE, 0);
if (oNode != null) {
Node oChild = oNode.getFirstXMLChild();
while ( oChild != null) {
if (oChild instanceof Chars) {
String sTemplateBase = ((Chars) oChild).getData();
if (sTemplateBase.length() > 0) {
assert(data instanceof HrefStore);
HrefStore oHrefStore = (HrefStore) data;
oHrefStore.setContainerConfigBaseUrl(sTemplateBase);
}
break;
}
oChild = oChild.getNextXMLSibling();
}
}
}
//
// Since we're here anyway, trim out all packets except the template
// to reduce memory usage, since within a fragment all that's needed
// is the template.
//
if (aName != XFA.TEMPLATE) {
Node oNode = oPacket;
if (oNode != null)
oNode.getXFAParent().removeChild(oNode);
}
}
}