org.geopublishing.geopublisher.AtlasConfigEditable.java Source code

Java tutorial

Introduction

Here is the source code for org.geopublishing.geopublisher.AtlasConfigEditable.java

Source

/*******************************************************************************
 * Copyright (c) 2010 Stefan A. Tzeggai.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Stefan A. Tzeggai - initial API and implementation
 ******************************************************************************/
package org.geopublishing.geopublisher;

import java.awt.Component;
import java.awt.Window;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.geopublishing.atlasViewer.AtlasConfig;
import org.geopublishing.atlasViewer.AtlasRefInterface;
import org.geopublishing.atlasViewer.dp.DpEntry;
import org.geopublishing.atlasViewer.dp.Group;
import org.geopublishing.atlasViewer.dp.layer.DpLayer;
import org.geopublishing.atlasViewer.dp.layer.DpLayerVectorFeatureSource;
import org.geopublishing.atlasViewer.dp.media.DpMedia;
import org.geopublishing.atlasViewer.exceptions.AtlasException;
import org.geopublishing.atlasViewer.http.FileWebResourceLoader;
import org.geopublishing.atlasViewer.http.Webserver;
import org.geopublishing.atlasViewer.map.Map;
import org.geopublishing.atlasViewer.map.MapPool;
import org.geopublishing.atlasViewer.map.MapPool.EventTypes;
import org.geopublishing.atlasViewer.swing.AtlasViewerGUI;
import org.geotools.styling.Style;

import rachel.http.loader.WebResourceManager;
import rachel.loader.FileResourceLoader;
import de.schmitzm.geotools.styling.StylingUtil;
import de.schmitzm.i18n.Translation;
import de.schmitzm.io.FilterUtil;
import de.schmitzm.io.IOUtil;
import de.schmitzm.jfree.chart.style.ChartStyle;
import de.schmitzm.swing.ExceptionDialog;

/**
 * An extension of the AtlasConfig for use within Geopublisher. One purpose is,
 * to keep {@link File} related references out of {@link AtlasViewerGUI}
 * 
 * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
 * 
 */
public class AtlasConfigEditable extends AtlasConfig {

    /**
     * Resource location of the splashscreen image that will be used for
     * JavaWebStart and <code>start.bat</code>, <code>atlas.exe</code> if the
     * didn't define one or the user-defined one can't be found.
     */
    public static final String SPLASHSCREEN_RESOURCE_NAME_FALLBACK = "/export/default_splashscreen.png";
    public static final String MAPLOGO_RESOURCE_NAME_FALLBACK = "/export/default_maplogo.png";

    private boolean gpHosterAuth = false;
    private final FileResourceLoader fileResLoader;

    private final FileWebResourceLoader fileWebResLoader;

    private boolean isDisposed = false;

    public AtlasConfigEditable(File atlasDir) {
        if (!atlasDir.exists() || !atlasDir.isDirectory())
            throw new IllegalArgumentException(atlasDir
                    + " is not a directory. An editable atlas (ACE) can only be created/openend from a directory.");

        this.atlasDir = atlasDir;
        fileResLoader = new FileResourceLoader(atlasDir);
        getResLoMan().addResourceLoader(fileResLoader);

        fileWebResLoader = new FileWebResourceLoader(atlasDir);
        WebResourceManager.addResourceLoader(fileWebResLoader);
    }

    @Override
    protected void finalize() throws Throwable {
        if (!isDisposed) {
            LOGGER.info("An AtlasConfigEditable instance " + this + " has not beed disposed! We dispose it now...");
            dispose();
        }
    };

    @Override
    public void dispose() {
        isDisposed = true;

        super.dispose();
        getAtlasDir();
        try {
            getResLoMan().removeResourceLoader(fileResLoader);
        } catch (Exception e) {
            LOGGER.error(e);
        }

        try {
            WebResourceManager.removeResourceLoader(fileWebResLoader);
        } catch (Exception e) {
            LOGGER.error(e);
        }

    }

    /**
     * this function completely deletes the atlas directory
     */
    public void deleteAtlas() {
        try {
            FileUtils.deleteDirectory(getAtlasDir());
        } catch (Exception e) {
            LOGGER.error(e);
        }
    }

    /** The name of the marker file for a AtlasWorkingCopy folder **/
    public static final String ATLAS_GPA_FILENAME = "atlas.gpa";

    final static private Logger LOGGER = Logger.getLogger(AtlasConfigEditable.class);

    private final List<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>();

    /**
     * Returns the root/first group of the "groups-tree" which contains
     * references to {@link DpEntry}s or sub groups *
     */
    @Override
    public final Group getRootGroup() {
        Group fg = super.getRootGroup();

        // /**
        // * Fill the description of the first group and tell the user, that it
        // is
        // * never visible in the atlas.
        // */
        // if (fg != null && I18NUtil.isEmpty(fg.getTitle())) {
        // List<String> activeLang = new ArrayList<String>();
        // activeLang.add(Translation.getActiveLang());
        // fg.setTitle(new Translation(activeLang, GpUtil
        // .R("FirstGroup.DefaultTitle")));
        // fg.setDesc(new Translation(activeLang, GpUtil
        // .R("FirstGroup.DefaultDesc")));
        // }

        return fg;

    }

    /**
     * {@link PropertyChangeListener} can be registered to be informed when the
     * {@link MapPool} changes.
     * 
     * @param propertyChangeListener
     */
    public void addChangeListener(PropertyChangeListener propertyChangeListener) {
        listeners.add(propertyChangeListener);
    }

    /**
     * Informs all registered {@link PropertyChangeListener}s about a change in
     * the {@link AtlasConfigEditable}. Also informs all listeners of MapPool
     * and DataPool.
     */
    public void fireChangeEvents() {
        PropertyChangeEvent pce = new PropertyChangeEvent(this, "change", false, true);

        for (PropertyChangeListener pcl : listeners) {
            if (pcl != null)
                pcl.propertyChange(pce);
        }

        getMapPool().fireChangeEvents(this, EventTypes.unknown, null);
        getDataPool().fireChangeEvents(org.geopublishing.atlasViewer.dp.DataPool.EventTypes.unknown);
    }

    /**
     * Directory where the Atlas was created in, or loaded from, or saved to.
     * AtlasDir has <code>ad</code> sub-directory!
     */
    private final File atlasDir;

    /** A cache to remember the sizes of the {@link DpEntry} folders **/
    private final java.util.Map<String, Long> rememberFolderSizes = new HashMap<String, Long>();

    /**
     * Convenience method for file system operations in the AC Returns a File to
     * the 'ad' directory under the atlasDir
     */
    public File getAd() {
        File adDir = new File(atlasDir, ATLASDATA_DIRNAME);
        if (!adDir.exists())
            adDir.mkdirs();
        return adDir;
    }

    /**
     * Convenience method for file system operations in the AC
     */
    public File getDataDir() {

        File dataDir = new File(getAd(), DATA_DIRNAME);
        if (!dataDir.exists())
            dataDir.mkdirs();
        return dataDir;
    }

    /**
     * Convenience method for file system operations in the AC. The returned
     * folder contains subdirectories for every map. @see {@link #getHtmlDirFor}
     */
    public File getHtmlDir() {
        File htmlDir = new File(getAd(), HTML_DIRNAME);
        if (!htmlDir.exists())
            htmlDir.mkdirs();
        return htmlDir;
    }

    /**
     * The returned folder {@link File} object contains optional additional
     * files.
     */
    public File getFontsDir() {
        File fontsDir = new File(getAd(), FONTS_DIRNAME);
        if (!fontsDir.exists()) {
            fontsDir.mkdirs();
        }
        return fontsDir;
    }

    /**
     * Convenience method for file system operations in the
     * {@link AtlasConfigEditable}. The returned folder contains the HTML files
     * for the requested {@link Map}. The directory is automatically created if
     * it doesn't exist.
     */
    public File getHtmlDirFor(Map map) {
        return getHtmlDirFor(map.getId());
    }

    /**
     * Convenience method for file system operations in the
     * {@link AtlasConfigEditable}. The returned folder contains the HTML files
     * for the requested {@link Map} ID. The directory is automatically created
     * if it doesn't exist.
     */
    private File getHtmlDirFor(String mapId) {
        File mapHtmlFolder = new File(getHtmlDir(), mapId);
        mapHtmlFolder.mkdirs();
        return mapHtmlFolder;
    }

    /**
     * Convenience method for file system operations in the AC
     */
    public File getImagesDir() {
        File imagesDir = new File(getAd(), IMAGES_DIRNAME);
        if (!imagesDir.exists()) {
            imagesDir.mkdirs();
        }
        return imagesDir;
    }

    /**
     * Convenience method for file system operations in the AC. The about dir
     * contains the popup and the about HTML info pages.
     */
    public File getAboutDir() {
        File aboutDir = new File(getHtmlDir(), ABOUT_DIRNAME);
        if (!aboutDir.exists())
            aboutDir.mkdirs();
        return aboutDir;
    }

    /**
     * @return workDir directory where the atlas was loaded from or created in.
     *         This method also creates all sub-folders automatically .
     */
    public File getAtlasDir() {
        getAd();
        getHtmlDir();
        getImagesDir();
        getDataDir();
        getAboutDir();
        getFontsDir();
        return this.atlasDir;
    }

    /**
     * @return A {@link List} of <b>human-readable</b> strings representing
     *         {@link DpEntry}s and/or {@link Map}s which are not referenced
     *         from the grouptree.
     */
    public List<String> listNotReferencedInGroupTree() {
        List<String> unrefed = new LinkedList<String>();

        for (Map map : getMapPool().values()) {
            LinkedList<AtlasRefInterface<?>> refs = new LinkedList<AtlasRefInterface<?>>();
            Group.findReferencesTo(this, map, refs, false);
            if (refs.size() == 0)
                unrefed.add("Map: " + map.getTitle()); // i8n
        }

        for (DpEntry<? extends ChartStyle> dpe : getDataPool().values()) {
            LinkedList<AtlasRefInterface<?>> refs = new LinkedList<AtlasRefInterface<?>>();
            Group.findReferencesTo(this, dpe, refs, false);
            if (refs.size() == 0)
                unrefed.add("DatapoolEntry: " + dpe.getTitle() + ", (" + dpe.getFilename() + ")"); // i8n
        }

        return unrefed;
    }

    /**
     * @return A {@link List} of <b>ID</b> strings representing {@link DpEntry}s
     *         and/or {@link Map}s which are not referenced from the grouptree.
     */
    public List<String> lisIdsNotReferencedInGroupTree() {
        List<String> unrefed = new LinkedList<String>();

        for (Map map : getMapPool().values()) {
            LinkedList<AtlasRefInterface<?>> refs = new LinkedList<AtlasRefInterface<?>>();
            Group.findReferencesTo(this, map, refs, false);
            if (refs.size() == 0)
                unrefed.add(map.getId());
        }

        for (DpEntry<? extends ChartStyle> dpe : getDataPool().values()) {
            LinkedList<AtlasRefInterface<?>> refs = new LinkedList<AtlasRefInterface<?>>();
            Group.findReferencesTo(this, dpe, refs, false);
            if (refs.size() == 0)
                unrefed.add(dpe.getId());
        }

        return unrefed;
    }

    /**
     * @return a subset of {@link #values()}, containing only the
     *         {@link DpEntry}s that are actually referenced in the atlas
     */
    public List<DpEntry<? extends ChartStyle>> getUsedDpes() {
        List<DpEntry<? extends ChartStyle>> notUsed = getUnusedDpes();
        List<DpEntry<? extends ChartStyle>> used = new ArrayList<DpEntry<? extends ChartStyle>>();

        // TODO faster or cache
        for (DpEntry dpe : getDataPool().values()) {
            if (notUsed.contains(dpe))
                continue;
            used.add(dpe);
        }

        return used;
    }

    /**
     * @return a {@link List} of all {@link Map}s that will be exported. A map
     *         is exported if it is referenced from the {@link Group} tree OR if
     *         the map is selected as the default start map.
     */
    public Set<Map> getUsedMaps() {

        Set<Map> usedMaps = new HashSet<Map>();

        for (Map m : getMapPool().values()) {
            boolean thisMapIsInteresting = false;

            // Check, if this map is directly reachable from the group tree.
            LinkedList<AtlasRefInterface<?>> refs2map = new LinkedList<AtlasRefInterface<?>>();
            Group.findReferencesTo(this, m, refs2map, false);

            if (refs2map.size() == 0) {
                // This map is not interesting, EXCEPT it is the default map

                if (m.getId().equals(getMapPool().getStartMapID())) {
                    // Startupmap has been defined and this is it.
                    thisMapIsInteresting = true;
                }
                if ((getMapPool().getStartMapID() == null && getMapPool().get(0).equals(m))) {
                    // NO startup map has been defined, but this is map
                    // number zero.
                    thisMapIsInteresting = true;
                }
            } else {
                thisMapIsInteresting = true;
            }

            if (thisMapIsInteresting)
                usedMaps.add(m);

        }

        return usedMaps;
    }

    /**
     * @return A {@link List} of {@link DpEntry}s which are not used in the
     *         atlas. If no {@link Map} is linked from the {@link Group}-Tree
     *         and no default map is selected, the first map (if any exists) of
     *         the atlas is use as the startup map.
     */
    public List<DpEntry<? extends ChartStyle>> getUnusedDpes() {

        List<DpEntry<? extends ChartStyle>> unrefed = new LinkedList<DpEntry<? extends ChartStyle>>();

        Set<Map> usedMaps = getUsedMaps();

        for (DpEntry<? extends ChartStyle> dpe : getDataPool().values()) {
            LinkedList<AtlasRefInterface<?>> refs = new LinkedList<AtlasRefInterface<?>>();
            Group.findReferencesTo(this, dpe, refs, false);

            if (refs.size() > 0) {
                // We have a direct reference, so no chance to add this DPE to
                // the list.
                continue;
            }

            /**
             * Now look at all the maps that are actually part of the atlas.
             */
            boolean referencedFromAMap = false;

            for (Map m : usedMaps) {
                /**
                 * If we find a reference to our DPE in this map, the DpEntry
                 * can be reached from the atlas
                 */
                if (m.containsDpe(dpe.getId())) {
                    referencedFromAMap = true;
                    break;
                }

                /**
                 * If we find a reference to our DPE in the HTML-files of this
                 * map, the DpEntry can be reached from the atlas
                 */
                if (dpe instanceof DpMedia && m.referencesInHtml((DpMedia) dpe)) {
                    referencedFromAMap = true;
                    break;
                }
            }

            /**
             * If there are no direct references from the group tree, AND there
             * are no references from maps that are in the group tree (or the
             * startup map), then add it to the list
             */
            if (!referencedFromAMap) {
                // LOGGER.info(dpe.getId() + "/" + dpe.getTitle()
                // + " are unreachable!");
                unrefed.add(dpe);
            }
        }
        return unrefed;
    }

    /**
     * @return {@link List} of {@link File}s to the HTML About Documents for all
     *         languages. The order of the {@link File}s equals the order of the
     *         languages in getLanguages(). If the files do not exist they will
     *         be created with defaults.
     * @throws IOException
     *             If the default files can not be created.
     */
    public List<File> getAboutHtMLFiles(Component owner) {
        ArrayList<File> urls = new ArrayList<File>();

        for (String lang : getLanguages()) {
            File aboutHTMLfile = new File(getAboutDir(), "about_" + lang + ".html");
            try {

                if (!aboutHTMLfile.exists()) {
                    /**
                     * Create a default HTML About window
                     */

                    FileWriter fw = new FileWriter(aboutHTMLfile);
                    fw.write("<html> <body> <p> "
                            + GpUtil.R("EditAboutWindow.TabName",
                                    (getTitle().get(lang) != null && getTitle().get(lang).equals("")) ? "..."
                                            : getTitle().get(lang),
                                    new Locale(lang).getDisplayLanguage(new Locale(Translation.getActiveLang())))
                            + " </p> </body> </html>");
                    fw.flush();
                    fw.close();
                }
                urls.add(aboutHTMLfile);
            } catch (Exception e) {
                ExceptionDialog.show(owner, new AtlasException("Couldn't create the default HTML about file.", e));
            }

        }
        return urls;
    }

    /**
     * @return {@link List} of {@link File}s to the HTML Popup documents for all
     *         languages. The order of the {@link File}s equals the order of the
     *         languages in getLanguages(). If the files do not exist they will
     *         be created with defaults.
     * @throws IOException
     *             If the default files can not be created.
     */
    public List<File> getPopupHtMLFiles(Window owner) {
        ArrayList<File> urls = new ArrayList<File>();

        for (String lang : getLanguages()) {
            File popupHTMLfile = new File(getAboutDir(), "popup_" + lang + ".html");
            try {

                if (!popupHTMLfile.exists()) {
                    /**
                     * Create a default HTML About window
                     */

                    FileWriter fw = new FileWriter(popupHTMLfile);
                    fw.write("<html> <body> <p> "
                            + GpUtil.R("EditPopupWindow.TabName",
                                    (getTitle().get(lang) != null && getTitle().get(lang).equals("")) ? "..."
                                            : getTitle().get(lang),
                                    new Locale(lang).getDisplayLanguage(new Locale(Translation.getActiveLang())))
                            + " </p> </body> </html>");
                    fw.flush();
                    fw.close();
                }
                urls.add(popupHTMLfile);
            } catch (Exception e) {
                ExceptionDialog.show(owner, new AtlasException("Couldn't create the default HTML popup file.", e));
            }

        }
        return urls;
    }

    /**
     * A helper method to determine the on disk size of a {@link DpEntry}. Size
     * is the recursive folder size in bytes... so this method might may take
     * some while to finish. Once determined, the size is cached.<br/>
     * The calculation omits all files, that are blacklisted for the exports,
     * that means, that Thumbs.db, .svn etc are not counted.
     * 
     * @param dpe
     *            The {@link DpEntry} to have a look at.
     * 
     * @see #uncacheFolderSize(DpEntry)
     * 
     * @return Number of Bytes in the corresponding folder.
     */
    public Long getFolderSize(DpEntry<? extends ChartStyle> dpe) {
        final String id = dpe.getId();
        if (!(rememberFolderSizes.containsKey(id))) {

            File directory = new File(getDataDir(), dpe.getDataDirname());
            // LOGGER.debug("\nDetermining the size of " + directory);
            try {
                rememberFolderSizes.put(id, sizeOfDirectoryWithBlacklist(directory));
            } catch (Exception e) {
                LOGGER.warn("Failed to calculate the size of folder " + directory, e);
                return -1l;
            }
        }
        return rememberFolderSizes.get(id);
    }

    /**
     * Counts the size of a directory recursively (sum of the length of all
     * files). It omits the blacklisted filed via
     * {@link GpUtil#BlacklistedFoldersFilter} and
     * {@link GpUtil#BlacklistedFilesFilter}.
     * 
     * @param directory
     *            directory to inspect, must not be <code>null</code>
     * @return size of directory in bytes, 0 if directory is security restricted
     * @throws NullPointerException
     *             if the directory is <code>null</code>
     */
    private Long sizeOfDirectoryWithBlacklist(File directory) {

        if (!directory.exists()) {
            String message = directory + " does not exist";
            throw new IllegalArgumentException(message);
        }

        if (!directory.isDirectory()) {
            String message = directory + " is not a directory";
            throw new IllegalArgumentException(message);
        }

        long size = 0;

        Collection<File> files = FileUtils.listFiles(directory, FilterUtil.BlacklistedFilesFilter,
                FilterUtil.BlacklistedFoldersFilter);
        if (files == null) { // null if security restricted
            return 0L;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                size += sizeOfDirectoryWithBlacklist(file);
            } else {
                size += file.length();
            }
        }

        return size;
    };

    /**
     * Removes the cached size of {@link DpEntry}'s folder. Next time the folder
     * size is queried, it will have to be recalculated.
     * 
     * @see #getFolderSize(DpEntry)
     */
    public void uncacheFolderSize(DpEntry<? extends ChartStyle> dpe) {
        if (rememberFolderSizes.containsKey(dpe.getId())) {
            rememberFolderSizes.remove(dpe.getId());
        }
    }

    // /**
    // * Resets the {@link ResourceLoader} to contain. This reset is needed
    // * whenever the Geopublisher loads a new atlas in the same JVM instance..
    // */
    // public void resetResLoMan() {
    //
    // LOGGER.info("Resetting the ResLoMan to only contain defaults");
    //
    // resLoMan = new ResourceLoaderManager();
    //
    // // setupResLoMan();
    //
    // }

    /**
     * Define the list of languages supported by this atlas. This may trigger a
     * Translation.LocaleChangeEvent even though the locale didn't really
     * change.
     */
    public final void setLanguages(List<String> languages) {
        setLanguages(languages.toArray(new String[] {}));
    }

    /**
     * Define the list of languages supported by this atlas. This may trigger a
     * Translation.LocaleChangeEvent even though the locale didn't really
     * change.
     */
    @Override
    public void setLanguages(String... langs) {
        //
        // /*
        // * Check if there is any real change.
        // */
        // if (Arrays.equals(langs, getLanguages().toArray(new String[] {})))
        // return;

        super.setLanguages(langs);
    }

    /**
     * Uncaches all cached information and will result in the GP to continue
     * with some IO stuff. This will NOT reload the <code>atlas.xml</code>.
     */
    public void uncacheAndReread() {

        rememberFolderSizes.clear();

        /**
         * First uncache all Styles
         */
        for (DpEntry<? extends ChartStyle> dpe : getDataPool().values()) {
            uncacheAndReread(dpe);
        }

        for (Map map : getMapPool().values()) {
            map.uncache(null);
            map.getMissingHTMLLanguages();
            map.getLayer0Crs();
        }

    }

    /**
     * Uncaches all cached information for a DPE Entry
     */
    public void uncacheAndReread(DpEntry<? extends ChartStyle> dpe) {

        rememberFolderSizes.remove(dpe.getId());
        dpe.uncache();
        dpe.getQuality();
        if (dpe instanceof DpLayer) {
            DpLayer<?, ? extends ChartStyle> dpl = (DpLayer<?, ? extends ChartStyle>) dpe;
            dpl.getMissingHTMLLanguages();
            dpl.getGeoObject();
            dpl.getCrs();

            // Correcting any wrongly upper/lowercased attribute names
            if (dpe instanceof DpLayer) {
                Style style = ((DpLayer) dpe).getStyle();

                if (dpe instanceof DpLayerVectorFeatureSource) {
                    style = StylingUtil.correctPropertyNames(style, ((DpLayerVectorFeatureSource) dpe).getSchema());
                } else {
                    style = StylingUtil.correctPropertyNames(style, null);
                }

                ((DpLayer) dpe).setStyle(style);
            }
        }
        getFolderSize(dpe);

        // Even though we didn't throw away the Style, we have to check the
        // attributes again

    }

    /**
     * @return a {@link File} object pointing to the main file of the
     *         {@link DpEntry}
     */
    public File getFileFor(DpEntry<? extends ChartStyle> dpe) {
        return new File(new File(getDataDir(), dpe.getDataDirname()), dpe.getFilename());
    }

    /**
     * Uncaches all cached information..
     */
    @Override
    public void uncache() {
        rememberFolderSizes.clear();

        super.uncache();
    }

    /**
     * Returns a {@link URL} where to access a given File (may be a directory)
     * in the local {@link Webserver}.
     * 
     * Returns <code>null</code> if the given file/directory can not be mapped
     * to a URL in the local webserver.
     */
    public String getBrowserURLString(File fileInAtlasData) {
        return getBrowserURLString(IOUtil.fileToURL(fileInAtlasData));
    }

    /**
     * Returns a {@link URL} where to access a given file://-URL (may be a
     * directory) in the local {@link Webserver}.
     * 
     * Returns <code>null</code> if the given file/directory can not be mapped
     * to a URL in the local webserver.
     */
    public String getBrowserURLString(URL fileUrlInAtlasData) {

        try {

            String awcAbsURLStr = IOUtil.fileToURL(getAtlasDir().getAbsoluteFile()).toString();
            String sourceAbsURLStr = fileUrlInAtlasData.toString();
            int relURLStartIdx = sourceAbsURLStr.indexOf(awcAbsURLStr);
            if (relURLStartIdx == -1)
                return null;
            String sourceRelURLStr = sourceAbsURLStr.substring(relURLStartIdx + awcAbsURLStr.length());
            String urlViaLocalServer = "http://localhost:" + Webserver.PORT + "/" + sourceRelURLStr;
            if (!urlViaLocalServer.endsWith("/"))
                urlViaLocalServer += "/";

            return urlViaLocalServer;
        } catch (Exception e) {
            LOGGER.error(fileUrlInAtlasData + " could not be mapped to a URL in the local webserver", e);
            return null;
        }

    }

    /**
     * @return {@link List} of {@link File}s to the HTML Terms of use documents for all
     *         languages. The order of the {@link File}s equals the order of the
     *         languages in getLanguages(). If the files do not exist they will
     *         be created with defaults.
     * @throws IOException
     *             If the default files can not be created.
     */
    public List<File> getTermsOfUseHtMLFiles(Window owner) {
        ArrayList<File> urls = new ArrayList<File>();

        for (String lang : getLanguages()) {
            File termsOfUseHTMLfile = new File(getAboutDir(), "termsofuse_" + lang + ".html");
            try {

                if (!termsOfUseHTMLfile.exists()) {
                    /**
                     * Create a default HTML About window
                     */

                    FileWriter fw = new FileWriter(termsOfUseHTMLfile);
                    fw.write("<html> <body> <p> "
                            + GpUtil.R("EditTermsOfUseWindow.TabName",
                                    (getTitle().get(lang) != null && getTitle().get(lang).equals("")) ? "..."
                                            : getTitle().get(lang),
                                    new Locale(lang).getDisplayLanguage(new Locale(Translation.getActiveLang())))
                            + " </p> </body> </html>");
                    fw.flush();
                    fw.close();
                }
                urls.add(termsOfUseHTMLfile);
            } catch (Exception e) {
                ExceptionDialog.show(owner,
                        new AtlasException("Couldn't create the default HTML terms of use file.", e));
            }

        }
        return urls;
    }

}