/*******************************************************************************
* Copyright 2013 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.discovery;
import gov.nasa.worldwind.util.WWXML;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Singleton;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import au.gov.ga.earthsci.common.persistence.Adapter;
import au.gov.ga.earthsci.common.persistence.ElementPersistentAdapter;
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;
/**
* Manages the {@link IDiscoveryService}s in the application.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
@Singleton
public class DiscoveryServiceManager
{
private static Persister persister;
static
{
persister = new Persister();
persister.setIgnoreMissing(true);
persister.setIgnoreNulls(true);
persister.registerNamedExportable(PersistentDiscoveryService.class, "Service"); //$NON-NLS-1$
}
private static final String servicesFilename = "discoveryServices.xml"; //$NON-NLS-1$
private static final File servicesFile = ConfigurationUtil.getWorkspaceFile(servicesFilename);
private static final Set<IDiscoveryService> services = new HashSet<IDiscoveryService>();
private static final Logger logger = LoggerFactory.getLogger(DiscoveryServiceManager.class);
private static final Listeners listeners = new Listeners();
/**
* Add a listener for change events on this manager.
*
* @param listener
* Listener to add
*/
public static void addListener(IDiscoveryServiceManagerListener listener)
{
listeners.add(listener);
}
/**
* Remove a listener from this manager.
*
* @param listener
* Listener to remove
*/
public static void removeListener(IDiscoveryServiceManagerListener listener)
{
listeners.remove(listener);
}
/**
* Add a new {@link IDiscoveryService}.
*
* @param service
* Service to add
*/
public static void addService(IDiscoveryService service)
{
services.add(service);
listeners.serviceAdded(service);
}
/**
* Remove the given {@link IDiscoveryService}.
*
* @param service
* Service to remove
*/
public static void removeService(IDiscoveryService service)
{
services.remove(service);
listeners.serviceRemoved(service);
}
/**
* @return List of discovery services
*/
public static Set<IDiscoveryService> getServices()
{
return Collections.unmodifiableSet(services);
}
@PostConstruct
protected void loadServices()
{
try
{
List<IDiscoveryService> services = loadServices(servicesFile);
if (services != null)
{
for (IDiscoveryService service : services)
{
DiscoveryServiceManager.addService(service);
}
}
}
catch (Exception e)
{
logger.error("Error unpersisting discovery services", e); //$NON-NLS-1$
}
}
@PreDestroy
protected void saveServices()
{
try
{
saveServices(services, servicesFile);
}
catch (Exception e)
{
logger.error("Error persisting discovery services", e); //$NON-NLS-1$
}
}
/**
* Load a list of discovery services from an XML file. Returns
* <code>null</code> if the file doesn't exist.
*
* @param inputFile
* XML file to load from
* @return List of loaded discovery services
* @throws IOException
* @throws SAXException
* @throws PersistenceException
*/
public static List<IDiscoveryService> loadServices(File inputFile) throws IOException, SAXException,
PersistenceException
{
if (!inputFile.exists())
{
return null;
}
FileInputStream is = null;
try
{
is = new FileInputStream(inputFile);
Document document = WWXML.createDocumentBuilder(false).parse(is);
Element parent = document.getDocumentElement();
return loadServices(parent);
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (IOException e)
{
}
}
}
}
public static List<IDiscoveryService> loadServices(Element servicesElement) throws PersistenceException
{
Element[] elements = XmlUtil.getElements(servicesElement);
List<IDiscoveryService> services = new ArrayList<IDiscoveryService>(elements.length);
for (Element element : elements)
{
PersistentDiscoveryService persistent = (PersistentDiscoveryService) persister.load(element, null);
services.add(persistent.createService());
}
return services;
}
/**
* Save the given collection of discovery services to a file as formatted
* XML.
*
* @param services
* Services to save
* @param outputFile
* Output file to save to
* @throws TransformerException
* @throws IOException
* @throws PersistenceException
*/
public static void saveServices(Collection<IDiscoveryService> services, File outputFile)
throws TransformerException, IOException, PersistenceException
{
FileOutputStream os = null;
try
{
os = new FileOutputStream(outputFile);
DocumentBuilder documentBuilder = WWXML.createDocumentBuilder(false);
Document document = documentBuilder.newDocument();
Element element = document.createElement("Services"); //$NON-NLS-1$
document.appendChild(element);
for (IDiscoveryService service : services)
{
PersistentDiscoveryService persistent = new PersistentDiscoveryService(service);
persister.save(persistent, element, null);
}
XmlUtil.saveDocumentToFormattedStream(document, os);
}
finally
{
if (os != null)
{
try
{
os.close();
}
catch (IOException e)
{
}
}
}
}
@Exportable
protected static class PersistentDiscoveryService
{
private static final String PROPERTY_ELEMENT_NAME = "property"; //$NON-NLS-1$
private static final String ID_ATTRIBUTE_NAME = "id"; //$NON-NLS-1$
private String providerId;
private String name;
private URL serviceURL;
private boolean enabled;
private Element propertiesElement;
@SuppressWarnings("unused")
private PersistentDiscoveryService()
{
//for unpersistence
}
public PersistentDiscoveryService(IDiscoveryService service)
{
setProviderId(service.getProvider().getId());
setServiceURL(service.getServiceURL());
setName(service.getName());
if (service instanceof MissingPluginPlaceholderDiscoveryService)
{
MissingPluginPlaceholderDiscoveryService missing = (MissingPluginPlaceholderDiscoveryService) service;
setEnabled(missing.wasEnabled());
setProperties(missing.getPropertiesElement());
}
else
{
setEnabled(service.isEnabled());
setupPropertiesElement(service);
}
}
public IDiscoveryService createService()
{
IDiscoveryProvider provider = DiscoveryProviderRegistry.getProviderForId(getProviderId());
if (provider == null)
{
logger.warn("Missing Discovery provider with id '" + getProviderId() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
return new MissingPluginPlaceholderDiscoveryService(getProviderId(), getName(), getServiceURL(),
isEnabled(), propertiesElement);
}
Map<IDiscoveryServiceProperty<?>, Object> propertyValues = getPropertyValues(provider);
IDiscoveryService service = provider.createService(getName(), getServiceURL(), propertyValues);
service.setEnabled(isEnabled());
return service;
}
private void setupPropertiesElement(IDiscoveryService service)
{
propertiesElement = null;
if (service == null || service.getProvider() == null || service.getProvider().getProperties() == null)
{
return;
}
try
{
@SuppressWarnings("unchecked")
IDiscoveryServiceProperty<Object>[] objectProperties =
(IDiscoveryServiceProperty<Object>[]) service.getProvider().getProperties();
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
propertiesElement = document.createElement("root"); //$NON-NLS-1$
for (IDiscoveryServiceProperty<Object> property : objectProperties)
{
Object value = property.getValue(service);
Element propertyElement = document.createElement(PROPERTY_ELEMENT_NAME);
propertyElement.setAttribute(ID_ATTRIBUTE_NAME, property.getId());
property.persist(propertyElement, value);
propertiesElement.appendChild(propertyElement);
}
}
catch (ParserConfigurationException e)
{
logger.error("Error creating discovery properties XML element", e); //$NON-NLS-1$
}
}
private Map<IDiscoveryServiceProperty<?>, Object> getPropertyValues(IDiscoveryProvider provider)
{
Map<IDiscoveryServiceProperty<?>, Object> propertyValues =
new HashMap<IDiscoveryServiceProperty<?>, Object>();
@SuppressWarnings("unchecked")
IDiscoveryServiceProperty<Object>[] properties =
(IDiscoveryServiceProperty<Object>[]) provider.getProperties();
if (properties != null)
{
Map<String, Element> propertyElements = new HashMap<String, Element>();
if (propertiesElement != null)
{
NodeList children = propertiesElement.getElementsByTagName(PROPERTY_ELEMENT_NAME);
for (int i = 0; i < children.getLength(); i++)
{
Element child = (Element) children.item(i);
String id = child.getAttribute(ID_ATTRIBUTE_NAME);
if (id != null)
{
propertyElements.put(id, child);
}
}
}
for (IDiscoveryServiceProperty<Object> property : properties)
{
Element propertyElement = propertyElements.get(property.getId());
Object value = propertyElement == null ? null : property.unpersist(propertyElement);
propertyValues.put(property, value);
}
}
return propertyValues;
}
@Persistent(attribute = true)
public String getProviderId()
{
return providerId;
}
public void setProviderId(String providerId)
{
this.providerId = providerId;
}
@Persistent(attribute = true)
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Persistent(attribute = true)
public URL getServiceURL()
{
return serviceURL;
}
public void setServiceURL(URL serviceURL)
{
this.serviceURL = serviceURL;
}
@Persistent(attribute = true)
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
@Persistent
@Adapter(ElementPersistentAdapter.class)
public Element getProperties()
{
return propertiesElement;
}
public void setProperties(Element propertiesElement)
{
this.propertiesElement = propertiesElement;
}
}
private static class Listeners extends ArrayList<IDiscoveryServiceManagerListener> implements
IDiscoveryServiceManagerListener
{
@Override
public void serviceAdded(IDiscoveryService service)
{
for (int i = size() - 1; i >= 0; i--)
{
get(i).serviceAdded(service);
}
}
@Override
public void serviceRemoved(IDiscoveryService service)
{
for (int i = size() - 1; i >= 0; i--)
{
get(i).serviceRemoved(service);
}
}
}
}