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

gov.nasa.worldwind.ogc.kml.KMLNetworkLink Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.ogc.kml;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.event.Message;
import gov.nasa.worldwind.ogc.kml.impl.KMLTraversalContext;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.*;

import javax.swing.*;
import java.beans.*;
import java.util.concurrent.atomic.*;

/**
 * Represents the KML NetworkLink element and provides access to its contents.
 * 

* During rendering, KMLNetworkLink retrieves and loads its network resource whenever necessary. Upon a * successful retrieval, KMLNetworkLink sends an AVKey.RETRIEVAL_STATE_SUCCESSFUL property * change event to this link's property change listeners. Once retrieved and loaded, KMLNetworkLink stores * its network resource by calling setNetworkResource, draws its network resource during preRendering and * rendering, and forwards property change events from the network resource to its property change listeners. *

* During retrieval, KMLNetworkLink attempts to use either the Link or the Url. * The Link is the preferred method for encoding a KML NetworkLink's address since KML version 2.1, * therefore we give it priority over Url. * * @author tag * @version $Id: KMLNetworkLink.java 1171 2013-02-11 21:45:02Z dcollins $ */ public class KMLNetworkLink extends KMLAbstractContainer implements PropertyChangeListener { /** Indicates the network resource referenced by this KMLNetworkLink. Initially null. */ protected AtomicReference networkResource = new AtomicReference(); /** * Time, in milliseconds since the Epoch, at which this KMLNetworkLink's network resource was last * retrieved. Initially -1, indicating that the network resource has not been retrieved. */ protected AtomicLong networkResourceRetrievalTime = new AtomicLong(-1); protected AtomicLong firstRetrievalTime; /** Flag to indicate that the Link has been fetched from the hash map. */ protected boolean linkFetched = false; protected KMLLink link; /** * Flag that is raised if the link retrieves a files that is not a KML document. In this case, do not attempt to * retrieve the resource again until the link changes. If the link changes then target file may also have changed. */ protected boolean invalidTarget; /** * Cache the root of this network link. Accessing the root by climbing up a deep network link tree can be a * performance bottleneck. */ protected KMLRoot root; /** * Construct an instance. * * @param namespaceURI the qualifying namespace URI. May be null to indicate no namespace qualification. */ public KMLNetworkLink(String namespaceURI) { super(namespaceURI); } /** * Forward property change events from the inner {@link KMLRoot} to this object's parent KMLRoot. * * @param evt Event to forward. */ public void propertyChange(PropertyChangeEvent evt) { this.getRoot().firePropertyChange(evt); } @Override public void onMessage(Message msg) { KMLLink link = this.getLinkOrUrl(); if (link != null) { link.onMessage(msg); } KMLRoot networkResource = this.getNetworkResource(); if (networkResource != null) { networkResource.onMessage(msg); } } /** {@inheritDoc} Overridden to cache the root instead of climbing the parse tree each time. */ @Override public KMLRoot getRoot() { if (root == null) this.root = super.getRoot(); return this.root; } public Boolean getRefreshVisibility() { return (Boolean) this.getField("refreshVisibility"); } public Boolean getFlyToView() { return (Boolean) this.getField("flyToView"); } public KMLLink getNetworkLink() { if (!this.linkFetched) { this.linkFetched = true; this.link = (KMLLink) this.getField("Link"); } return this.link; } public KMLLink getUrl() { return (KMLLink) this.getField("Url"); } /** * Returns this NetworkLink's Link element or its Url element, depending on * which is available. Link is the preferred method for encoding a NetworkLink since KML * version 2.1, therefore we give it priority. If a Link is not specified, the deprecated * Url element is returned. * * @return this NetworkLink's Link element, if one is specified. Otherwise, this * NetworkLink's Url element. Returns null if neither * Link or a Url are specified. */ protected KMLLink getLinkOrUrl() { KMLLink link = this.getNetworkLink(); if (link != null) return link; return this.getUrl(); } /** * Indicates the network resource referenced by this KMLNetworkLink. This returns null if * this link has no resource. * * @return this link's network resource, or null to indicate that this link has no resource. * * @see #setNetworkResource(KMLRoot) */ public KMLRoot getNetworkResource() { return networkResource.get(); } /** * {@inheritDoc} Overridden to apply region culling according to the same rules as in KMLAbstractFeature. This * prevents retrieving network links in inactive regions. */ @Override protected boolean isFeatureActive(KMLTraversalContext tc, DrawContext dc) { if (this.getVisibility() != null && !this.getVisibility()) return false; KMLRegion region = this.getRegion(); if (region == null) region = tc.peekRegion(); return region == null || region.isActive(tc, dc); } protected boolean hasNetworkLinkControl() { return this.getRoot().getNetworkLinkControl() != null; } @Override public String getName() { if (this.hasNetworkLinkControl() && !WWUtil.isEmpty(this.getRoot().getNetworkLinkControl().getLinkName())) return this.getRoot().getNetworkLinkControl().getLinkName(); return super.getName(); } @Override public String getDescription() { if (this.hasNetworkLinkControl() && !WWUtil.isEmpty(this.getRoot().getNetworkLinkControl().getLinkDescription())) return this.getRoot().getNetworkLinkControl().getLinkDescription(); return super.getDescription(); } @Override public Object getSnippet() { if (this.hasNetworkLinkControl() && !WWUtil.isEmpty(this.getRoot().getNetworkLinkControl().getLinkSnippet())) return this.getRoot().getNetworkLinkControl().getLinkSnippet(); return super.getSnippet(); } /** * Specifies the network resource referenced by this KMLNetworkLink, or null if this link * has no resource. If the specified kmlRoot is not nullkmlRoot during preRendering and rendering, and forwards property change events from the * kmlRoot to this link's property change listeners. * * @param kmlRoot the network resource referenced by this KMLNetworkLink. May be null. * * @see #getNetworkResource() */ public void setNetworkResource(final KMLRoot kmlRoot) { // Remove any property change listeners previously set on the KMLRoot. This eliminates dangling references from // the KMLNetworkLink to its previous KMLRoot. KMLRoot resource = this.getNetworkResource(); if (resource != null) resource.removePropertyChangeListener(this); this.networkResource.set(kmlRoot); this.networkResourceRetrievalTime.set(System.currentTimeMillis()); if (this.firstRetrievalTime == null) this.firstRetrievalTime = new AtomicLong(this.networkResourceRetrievalTime.get()); // Set up to listen for property change events on the KMLRoot. KMLNetworkLink must forward REPAINT and REFRESH // property change events from its internal KMLRoot to its parent KMLRoot to support BrowserBalloon repaint // events and recursive KMLNetworkLink elements. if (kmlRoot != null) { kmlRoot.addPropertyChangeListener(this); // Apply any updates contained in the new root's optional network link control. SwingUtilities.invokeLater(new Runnable() { public void run() { if (kmlRoot.getNetworkLinkControl() != null && kmlRoot.getNetworkLinkControl().getUpdate() != null && !kmlRoot.getNetworkLinkControl().getUpdate().isUpdatesApplied()) kmlRoot.getNetworkLinkControl().getUpdate().applyOperations(); } }); } } /** * Pre-renders the network resource referenced by this KMLNetworkLink. If this link must retrieve its * network resource, this initiates a retrieval and does nothing until the resource is retrieved and loaded. Once * the network resource is retrieved and loaded, this calls {@link #setNetworkResource(KMLRoot)} to * specify this link's new network resource, and sends an {@link gov.nasa.worldwind.avlist.AVKey#RETRIEVAL_STATE_SUCCESSFUL} * property change event to this link's property change listeners. * * @param tc the current KML traversal context. * @param dc the current draw context. * * @see #getNetworkResource() */ @Override protected void doPreRender(KMLTraversalContext tc, DrawContext dc) { super.doPreRender(tc, dc); if (this.mustRetrieveNetworkResource()) this.requestResource(dc); if (this.getNetworkResource() != null) this.getNetworkResource().preRender(tc, dc); } /** * Renders the network resource referenced by this KMLNetworkLink. This does nothing if this link has * no network resource. * * @param tc the current KML traversal context. * @param dc the current draw context. */ @Override protected void doRender(KMLTraversalContext tc, DrawContext dc) { super.doRender(tc, dc); if (this.getNetworkResource() != null) this.getNetworkResource().render(tc, dc); } /** * Returns whether this KMLNetworkLink must retrieve its network resource. This always returns * false if this KMLNetworkLink has no KMLLink. * * @return true if this KMLNetworkLink must retrieve its network resource, otherwise * false. */ protected boolean mustRetrieveNetworkResource() { KMLLink link = this.getLinkOrUrl(); if (link == null) return false; // If both the Link and the Url are null, then there's nothing to retrieve. // If the resource has already been retrieved, but is not a KML file, don't retrieve the resource again. if (this.invalidTarget) return false; // Make sure a refresh doesn't occur within the minimum refresh period, if one is specified. KMLNetworkLinkControl linkControl = this.getRoot().getNetworkLinkControl(); if (linkControl != null && linkControl.getMinRefreshPeriod() != null) { long now = System.currentTimeMillis(); if (this.firstRetrievalTime != null // be sure it gets retrieved a first time && this.networkResourceRetrievalTime.get() + linkControl.getMinRefreshPeriod() * 1000 > now) return false; } // Make sure a refresh doesn't occur after the max session length is reached, if one is specified. if (linkControl != null && linkControl.getMaxSessionLength() != null && this.firstRetrievalTime != null) { long now = System.currentTimeMillis(); if (this.firstRetrievalTime.get() + linkControl.getMaxSessionLength() * 1000 > now) return false; } // The resource must be retrieved if the link has been updated since the resource was // last retrieved, or if the resource has never been retrieved. return this.getNetworkResource() == null || link.getUpdateTime() > this.networkResourceRetrievalTime.get(); } /** * Thread's off a task to determine whether the resource is local or remote and then retrieves it either from disk * cache or a remote server. * * @param dc the current draw context. */ protected void requestResource(DrawContext dc) { if (WorldWind.getTaskService().isFull()) return; KMLLink link = this.getLinkOrUrl(); if (link == null) return; // If both the Link and the Url are null, then there's nothing to retrieve. String address = link.getAddress(dc); if (address != null) address = address.trim(); if (WWUtil.isEmpty(address)) return; if (this.hasNetworkLinkControl() && this.getRoot().getNetworkLinkControl().getCookie() != null) address = address + this.getRoot().getNetworkLinkControl().getCookie(); WorldWind.getTaskService().addTask(new RequestTask(this, address)); } /** * Initiates a retrieval of the network resource referenced by this KMLNetworkLink. Once the network * resource is retrieved and loaded, this calls {@link #setNetworkResource(KMLRoot)} to specify this * link's new network resource, and sends an {@link gov.nasa.worldwind.avlist.AVKey#RETRIEVAL_STATE_SUCCESSFUL} * property change event to this link's property change listeners. *

* This does nothing if this KMLNetworkLink has no KMLLink. * * @param address the address of the resource to retrieve */ protected void retrieveNetworkResource(String address) { // Treat the address as either a path to a local document, or as an absolute URL to a remote document. If the // address references a remote document, this attempts to retrieve it and loads the document once retrieval // succeeds. This does not handle absolute local file paths; absolute local file paths are not supported by the // KML specification. However, a NetworkLink may reference an absolute local file by specifying an absolute // URL with the "file:" protocol. // // Don't cache remote documents if this link's refreshMode is onInterval, or if this link's viewRefreshMode is // onStop. These documents are transient and therefore should be stored in a temporary location. long updateTime = 0L; KMLLink link = this.getLinkOrUrl(); if (link != null) { updateTime = link.getUpdateTime(); } Object o = this.getRoot().resolveNetworkLink(address, this.isLinkCacheable(), updateTime); if (o instanceof KMLRoot) { KMLRoot newRoot = (KMLRoot) o; this.setNetworkResource(newRoot); // Check for an expiration time set through HTTP header or NetworkLinkControl long expiration = this.computeExpiryRefreshTime(newRoot, address); this.getLinkOrUrl().setExpirationTime(expiration); this.getRoot().firePropertyChange(AVKey.RETRIEVAL_STATE_SUCCESSFUL, null, KMLNetworkLink.this); } // Anything other than a KMLRoot is not a valid link target else if (o != null) { String message = Logging.getMessage("KML.InvalidNetworkLinkTarget", address); Logging.logger().warning(message); this.invalidTarget = true; // Stop trying to retrieve this resource } } /** * Indicates the expiration time of a linked resource. The expiration time is specified by (in order of priority): a * NetworkLinkControl/expires element in the target document, a HTTP Cache-Control header, or an HTTP Expires * header. * * @param root Root of target resource. * @param address Address of linked resource. * * @return The expiration time of the resource, in milliseconds since the Epoch. Zero indicates that there is no * expiration time. */ protected long computeExpiryRefreshTime(KMLRoot root, String address) { KMLNetworkLinkControl linkControl = root.getNetworkLinkControl(); if (linkControl != null && linkControl.getExpires() != null) { Long time = WWUtil.parseTimeString(linkControl.getExpires()); return time != null ? time : 0; } // Check for expiration in HTTP headers return this.getRoot().getExpiration(address); } /** * Indicates whether the network resource references by this KMLNetworkLink should be retrieved to the * World Wind cache or to a temporary location. This returns true if all of the following conditions * are met, and false otherwise: *

*

  • This network link has either a Link or a Url element.
  • The Link or Url * element's refreshMode is not onInterval or onExpire.
  • The Link or * Url element's viewRefreshMode is not onStop.
* * @return true if this link's network resource can should be stored in a cache, or false * if it should be stored in a temporary location. */ public boolean isLinkCacheable() { KMLLink link = this.getLinkOrUrl(); return link != null && !KMLConstants.ON_INTERVAL.equalsIgnoreCase(link.getRefreshMode()) && !KMLConstants.ON_EXPIRE.equalsIgnoreCase(link.getRefreshMode()) && !KMLConstants.ON_STOP.equalsIgnoreCase(link.getViewRefreshMode()); } @Override public void applyChange(KMLAbstractObject sourceValues) { if (!(sourceValues instanceof KMLNetworkLink)) { String message = Logging.getMessage("nullValue.SourceIsNull"); Logging.logger().warning(message); throw new IllegalArgumentException(message); } KMLNetworkLink sourceLink = (KMLNetworkLink) sourceValues; // Reset this network link only if the change contains a new link if (sourceLink.getLinkOrUrl() != null) this.reset(); super.applyChange(sourceValues); } @Override public void onChange(Message msg) { if (KMLAbstractObject.MSG_LINK_CHANGED.equals(msg.getName())) this.reset(); super.onChange(msg); } protected void reset() { this.networkResource.set(null); this.networkResourceRetrievalTime.set(-1); this.firstRetrievalTime = null; this.linkFetched = false; this.link = null; this.invalidTarget = false; this.getRoot().requestRedraw(); // cause doPreRender to be called to initiate new link retrieval } /** Attempts to find this network link resource file locally, and if that fails attempts to find it remotely. */ protected static class RequestTask implements Runnable { /** The link associated with this request. */ protected final KMLNetworkLink link; /** The resource's address. */ protected final String address; /** * Construct a request task for a specified network link resource. * * @param link the network link for which to construct the request task. * @param address the address of the resource to request. */ protected RequestTask(KMLNetworkLink link, String address) { if (link == null) { String message = Logging.getMessage("nullValue.ObjectIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (address == null) { String message = Logging.getMessage("nullValue.PathIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.link = link; this.address = address; } public void run() { if (Thread.currentThread().isInterrupted()) return; // the task was cancelled because it's a duplicate or for some other reason this.link.retrieveNetworkResource(this.address); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RequestTask that = (RequestTask) o; if (!this.address.equals(that.address)) return false; //noinspection RedundantIfStatement if (!this.link.equals(that.link)) return false; return true; } @Override public int hashCode() { int result = link.hashCode(); result = 31 * result + address.hashCode(); return result; } public String toString() { return this.address; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy