/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.catalog;
import gov.nasa.worldwind.util.WWXML;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.TransformerException;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import au.gov.ga.earthsci.common.persistence.Exportable;
import au.gov.ga.earthsci.common.persistence.PersistenceException;
import au.gov.ga.earthsci.common.persistence.Persistent;
import au.gov.ga.earthsci.common.persistence.Persister;
import au.gov.ga.earthsci.common.util.ConfigurationUtil;
import au.gov.ga.earthsci.common.util.XmlUtil;
import au.gov.ga.earthsci.worldwind.common.util.Validate;
/**
* Helper class used to persist the catalog model to an XML file.
*
* @author James Navin (james.navin@ga.gov.au)
*/
public class CatalogPersister
{
private CatalogPersister()
{
}
private static final Logger logger = LoggerFactory.getLogger(CatalogPersister.class);
private static final String ROOT_NODE_NAME = "catalogModel"; //$NON-NLS-1$
private static final String MODEL_ELEMENT_NAME = "model"; //$NON-NLS-1$
private static final String CATALOG_NODE_ELEMENT_NAME = "catalog"; //$NON-NLS-1$
private static final String DEFAULT_WORKSPACE_CATALOG_FILENAME = "catalogs.xml"; //$NON-NLS-1$
private static final Persister persister;
static
{
persister = new Persister();
persister.setIgnoreMissing(true);
persister.setIgnoreNulls(true);
persister.registerNamedExportable(CatalogModelDTO.class, MODEL_ELEMENT_NAME);
persister.registerNamedExportable(CatalogNodeDTO.class, CATALOG_NODE_ELEMENT_NAME);
}
/**
* Save the provided catalog model to the current workspace using the
* default name
*
* @param model
* The model to save
*
* @throws IOException
* If there is a problem writing to the output file
* @throws TransformerException
* If there is a problem formatting the XML output
* @throws PersistenceException
* If there is a problem persisting the model tree
*/
public static void saveToWorkspace(ICatalogModel model)
{
if (model == null)
{
return;
}
try
{
saveCatalogModel(model, ConfigurationUtil.getWorkspaceFile(DEFAULT_WORKSPACE_CATALOG_FILENAME));
}
catch (Exception e)
{
logger.error("Unable to save catalog model to workspace", e); //$NON-NLS-1$
}
}
/**
* Save the provided catalog model to the provided file
*
* @param model
* The catalog model to save. If <code>null</code> this method
* will have no effect.
* @param file
* The file to save the model to. Cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* If the output file is <code>null</code>
* @throws IOException
* If there is a problem writing to the output file
* @throws TransformerException
* If there is a problem formatting the XML output
* @throws PersistenceException
* If there is a problem persisting the model tree
*/
public static void saveCatalogModel(ICatalogModel model, File file) throws IOException, TransformerException,
PersistenceException
{
if (model == null)
{
return;
}
Validate.notNull(file, "An output file is required"); //$NON-NLS-1$
FileOutputStream os = null;
try
{
os = new FileOutputStream(file);
saveCatalogModel(model, os);
}
finally
{
if (os != null)
{
os.close();
}
}
}
/**
* Save the provided catalog model to the provided output stream
*
* @param model
* The catalog model to save. If <code>null</code> this method
* will have no effect.
* @param os
* The output stream to save to. Must be non-<code>null</code>
* and writable.
*
* @throws IllegalArgumentException
* If the output stream is <code>null</code>
* @throws IOException
* If there is a problem writing to the output stream
* @throws TransformerException
* If there is a problem formatting the document tree
* @throws PersistenceException
* If there is a problem persisting the model tree
*/
public static void saveCatalogModel(ICatalogModel model, OutputStream os) throws IOException, TransformerException,
PersistenceException
{
if (model == null)
{
return;
}
Validate.notNull(os, "An output stream is required"); //$NON-NLS-1$
DocumentBuilder documentBuilder = WWXML.createDocumentBuilder(false);
Document document = documentBuilder.newDocument();
Element element = document.createElement(ROOT_NODE_NAME);
document.appendChild(element);
saveCatalogModel(model, element);
XmlUtil.saveDocumentToFormattedStream(document, os);
}
/**
* Save the provided catalog model as XML children of the provided parent
* element
*
* @param model
* The model to save. If <code>null</code>, this method will have
* no effect.
* @param parentElement
* The parent XML element to save the model into
*
* @throws IllegalArgumentException
* If the parent element is <code>null</code>
* @throws PersistenceException
* If there is a problem persisting the model tree
*/
public static void saveCatalogModel(ICatalogModel model, Element parentElement) throws PersistenceException
{
if (model == null)
{
return;
}
Validate.notNull(parentElement, "A parent element is required"); //$NON-NLS-1$
persister.save(new CatalogModelDTO(model), parentElement, null);
}
/**
* Load the catalog model from the current workspace, if it is available, or
* return a new empty model.
*
* @param result
* The model to add the loaded catalog nodes to and return. If
* null, a new model is created.
* @param context
* An Eclipse context
*
* @return The loaded catalog model
*
* @throws IllegalArgumentException
* If the provided source file is <code>null</code>
* @throws SAXException
* If there is a problem parsing the XML document
* @throws IOException
* If there is a problem reading from the source file
* @throws PersistenceException
* If there is a problem recreating the model tree from the
* persistence mechanism
*/
public static ICatalogModel loadFromWorkspace(ICatalogModel result, IEclipseContext context)
{
File workspaceFile = ConfigurationUtil.getWorkspaceFile(DEFAULT_WORKSPACE_CATALOG_FILENAME);
if (!workspaceFile.exists())
{
logger.debug("No catalog model file found in workspace. Creating new model."); //$NON-NLS-1$
return new CatalogModel();
}
try
{
return loadCatalogModel(workspaceFile, result, context, false);
}
catch (Exception e)
{
logger.debug("Unable to load catalog model from workspace", e); //$NON-NLS-1$
return new CatalogModel();
}
}
/**
* Load a catalog model from the provided source file
*
* @param source
* The file to load the catalog model from. Must be non-
* <code>null</code>.
* @param result
* The model to add the loaded catalog nodes to and return. If
* null, a new model is created.
* @param context
* An Eclipse context
*
* @return The loaded catalog model
*
* @throws IllegalArgumentException
* If the provided source file is <code>null</code>
* @throws SAXException
* If there is a problem parsing the XML document
* @throws IOException
* If there is a problem reading from the source file
* @throws PersistenceException
* If there is a problem recreating the model tree from the
* persistence mechanism
*/
public static ICatalogModel loadCatalogModel(File source, ICatalogModel result, IEclipseContext context,
boolean onlyAddUniqueUris)
throws SAXException, IOException, PersistenceException
{
Validate.notNull(source, "An input file is required"); //$NON-NLS-1$
FileInputStream is = null;
try
{
is = new FileInputStream(source);
return loadCatalogModel(is, source.toURI(), result, context, onlyAddUniqueUris);
}
finally
{
if (is != null)
{
is.close();
}
}
}
/**
* Load a previously saved catalog model from the provided input stream.
*
* @param is
* The input stream to load from. Must be non-<code>null</code>.
* @param uriContext
* Context for relative URIs in the catalog model
* @param result
* The model to add the loaded catalog nodes to and return. If
* null, a new model is created.
* @param context
* An Eclipse context
*
* @return The loaded catalog model
*
* @throws IllegalArgumentException
* If the input stream is <code>null</code>
* @throws SAXException
* If there is a problem parsing the XML
* @throws IOException
* If there is a problem reading from the input stream
* @throws PersistenceException
* If there is a problem recreating the model from the
* persistence mechanism
*/
public static ICatalogModel loadCatalogModel(InputStream is, URI uriContext, ICatalogModel result,
IEclipseContext context, boolean onlyAddUniqueUris) throws SAXException, IOException, PersistenceException
{
Validate.notNull(is, "An input stream is required"); //$NON-NLS-1$
Document document = WWXML.createDocumentBuilder(false).parse(is);
Element parent = document.getDocumentElement();
if (!ROOT_NODE_NAME.equals(parent.getNodeName()))
{
throw new PersistenceException(
"Provided document is not a valid catalog model document. Expected root node " + ROOT_NODE_NAME + " but found " + parent.getNodeName()); //$NON-NLS-1$//$NON-NLS-2$
}
Element element = XmlUtil.getFirstChildElement(parent);
return loadCatalogModel(element, uriContext, result, context, onlyAddUniqueUris);
}
/**
* Load a previously saved catalog model from the provided parent XML
* element.
*
* @param parentElement
* The parent element to load the catalog model from. Must be
* non-<code>null</code>.
* @param uriContext
* Context for relative URIs in the catalog model
* @param result
* The model to add the loaded catalog nodes to and return. If
* null, a new model is created.
* @param context
* An Eclipse context
* @param onlyAddUniqueUris
* Only add catalogs if their URIs don't already exist in the
* catalog
*
* @return The loaded catalog model
*
* @throws IllegalArgumentException
* If the parent element is <code>null</code>
* @throws PersistenceException
* If there is a problem recreating the model from the
* persistence mechanism
*/
public static ICatalogModel loadCatalogModel(Element parentElement, URI uriContext, ICatalogModel result,
IEclipseContext context, boolean onlyAddUniqueUris) throws PersistenceException
{
Validate.notNull(parentElement, "A parent XML element is required"); //$NON-NLS-1$
CatalogModelDTO dto = (CatalogModelDTO) persister.load(parentElement, uriContext);
if (result == null)
{
result = new CatalogModel();
}
for (int i = 0; i < dto.catalogs.length; i++)
{
URI uri = dto.catalogs[i].nodeURI;
if (uri != null)
{
if (onlyAddUniqueUris && result.containsTopLevelCatalogURI(uri))
{
continue;
}
LoadingCatalogTreeNode loadingNode = new LoadingCatalogTreeNode(uri);
loadingNode.setLabel(dto.catalogs[i].label);
result.addTopLevelCatalog(loadingNode);
IntentCatalogLoader.load(uri, loadingNode, context);
}
}
return result;
}
/**
* A simple DTO that captures the state of a {@link ICatalogModel} required
* for persistence.
* <p/>
* Used to simplify the persisting / restoring of catalogs
*/
@Exportable
private static class CatalogModelDTO
{
@Persistent
private CatalogNodeDTO[] catalogs;
public CatalogModelDTO(final ICatalogModel model)
{
List<ICatalogTreeNode> topLevelCatalogs = model.getTopLevelCatalogs();
catalogs = new CatalogNodeDTO[topLevelCatalogs.size()];
for (int i = 0; i < topLevelCatalogs.size(); i++)
{
catalogs[i] = new CatalogNodeDTO(topLevelCatalogs.get(i));
}
}
@SuppressWarnings("unused")
public CatalogModelDTO()
{
// For persistence mechanism only
}
}
/**
* A simple DTO that captures the state of a {@link ICatalogTreeNode}
* required for persistence.
* <p/>
* Used to simplify the persisting / restoring of catalogs
*/
@Exportable
private static class CatalogNodeDTO
{
@Persistent(attribute = true)
private String label;
@Persistent(attribute = true, name = "uri")
private URI nodeURI;
public CatalogNodeDTO(final ICatalogTreeNode node)
{
this.label = node.getLabel();
this.nodeURI = node.getURI();
}
@SuppressWarnings("unused")
public CatalogNodeDTO()
{
// For persistence mechanism only
}
}
}