All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.service.href.HrefStore Maven / Gradle / Ivy

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);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy