/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.cluster.server; import java.io.ByteArrayOutputStream; import java.io.File; import java.util.Properties; import javax.jms.JMSException; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogException; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.event.CatalogAddEvent; import org.geoserver.catalog.event.CatalogListener; import org.geoserver.catalog.event.CatalogModifyEvent; import org.geoserver.catalog.event.CatalogPostModifyEvent; import org.geoserver.catalog.event.CatalogRemoveEvent; import org.geoserver.catalog.impl.ModificationProxy; import org.geoserver.cluster.JMSApplicationListener; import org.geoserver.cluster.JMSPublisher; import org.geoserver.cluster.impl.handlers.DocumentFile; import org.geoserver.cluster.impl.utils.BeanUtils; import org.geoserver.cluster.server.events.StyleModifyEvent; import org.geoserver.config.GeoServerDataDirectory; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.resource.Resource; import org.geoserver.platform.resource.Resource.Type; import org.geoserver.platform.resource.Resources; import org.geoserver.util.IOUtils; import org.geotools.util.logging.Logging; /** * JMS MASTER (Producer) Listener used to send GeoServer Catalog events over the JMS channel. * * @see {@link JMSApplicationListener} * * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it * */ public class JMSCatalogListener extends JMSAbstractGeoServerProducer implements CatalogListener { private final static java.util.logging.Logger LOGGER = Logging.getLogger(JMSCatalogListener.class); private final JMSPublisher jmsPublisher; private final GeoServerResourceLoader loader; private final GeoServerDataDirectory dataDirectory; /** * Constructor * * @param topicTemplate the getJmsTemplate() object used to send message to the topic queue * */ public JMSCatalogListener(final Catalog catalog, final JMSPublisher jmsPublisher, GeoServerResourceLoader loader, GeoServerDataDirectory dataDirectory) { super(); this.jmsPublisher = jmsPublisher; this.loader = loader; this.dataDirectory = dataDirectory; catalog.addListener(this); } @Override public void handleAddEvent(CatalogAddEvent event) throws CatalogException { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("Incoming event of type " + event.getClass().getSimpleName() + " from Catalog"); } // skip incoming events if producer is not Enabled if (!isEnabled()) { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("skipping incoming event: context is not initted"); } return; } // update properties final Properties options = getProperties(); try { // check if we may publish also the file final CatalogInfo info = event.getSource(); if (info instanceof StyleInfo) { final StyleInfo sInfo=((StyleInfo) info); WorkspaceInfo wInfo =sInfo.getWorkspace(); Resource styleFile=null; // make sure we work fine with workspace specific styles if(wInfo!=null){ styleFile=loader.get( File.separator+ "workspaces"+ File.separator+ wInfo.getName()+ File.separator+ "styles"+ File.separator+ sInfo.getFilename()); }else{ styleFile=loader.get("styles/" + sInfo.getFilename()); } // checks if(!Resources.exists(styleFile)||!Resources.canRead(styleFile)||!(styleFile.getType() == Type.RESOURCE)){ throw new IllegalStateException("Unable to find style for event: "+sInfo.toString()); } // transmit the file jmsPublisher.publish(getTopic(), getJmsTemplate(), options, new DocumentFile(styleFile)); } // propagate the event jmsPublisher.publish(getTopic(), getJmsTemplate(), options, event); } catch (Exception e) { if (LOGGER.isLoggable(java.util.logging.Level.SEVERE)) { LOGGER.severe(e.getLocalizedMessage()); } final CatalogException ex = new CatalogException(e); throw ex; } } @Override public void handleRemoveEvent(CatalogRemoveEvent event) throws CatalogException { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("Incoming message event of type " + event.getClass().getSimpleName() + " from Catalog"); } // skip incoming events until context is loaded if (!isEnabled()) { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("skipping incoming event: context is not initted"); } return; } // update properties Properties options = getProperties(); try { jmsPublisher.publish(getTopic(), getJmsTemplate(), options, event); } catch (JMSException e) { if (LOGGER.isLoggable(java.util.logging.Level.SEVERE)) { LOGGER.severe(e.getLocalizedMessage()); } throw new CatalogException(e); } } @Override public void handleModifyEvent(CatalogModifyEvent event) throws CatalogException { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("Incoming message event of type " + event.getClass().getSimpleName() + " from Catalog"); } // skip incoming events until context is loaded if (!isEnabled()) { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("skipping incoming event: context is not initted"); } return; } // update properties Properties options = getProperties(); // check if we may publish also the file CatalogInfo info = event.getSource(); // if the modified object was a style we need to send the style file too if (info instanceof StyleInfo) { // we need to get the associated resource file, for this we need to look // at the final object we use a proxy to preserver the original object StyleInfo styleInfo = ModificationProxy.create((StyleInfo) info, StyleInfo.class); // updated the proxy object with the new values try { BeanUtils.smartUpdate(styleInfo, event.getPropertyNames(), event.getNewValues()); } catch (Exception exception) { // there is nothing we can do about this throw new RuntimeException(String.format( "Error setting proxy of style '%s' new values.", styleInfo.getName()), exception); } // get style associated resource Resource resource = dataDirectory.get(styleInfo, styleInfo.getFilename()); if (!resource.file().exists()) { // this should not happen we throw an exception throw new RuntimeException(String.format( "Style file '%s' for style '%s' could not be found.", styleInfo.getFilename(), styleInfo.getName())); } try { // read the style file to an array of bytes ByteArrayOutputStream output = new ByteArrayOutputStream(); try { IOUtils.copy(resource.in(), output); } catch (Exception exception) { throw new RuntimeException(String.format( "Error reading style '%s' file '%s'.", styleInfo.getName(), resource.file().getAbsolutePath()), exception); } // publish the style event jmsPublisher.publish(getTopic(), getJmsTemplate(), options, new StyleModifyEvent(event, output.toByteArray())); } catch (Exception exception) { throw new RuntimeException(String.format( "Error publishing file associated with style '%s'.", styleInfo.getName()), exception); } } else { // propagate the catalog modified event try { jmsPublisher.publish(getTopic(), getJmsTemplate(), options, event); } catch (Exception exception) { throw new RuntimeException(String.format( "Error publishing catalog modified event of type '%s'.", info.getClass().getSimpleName()), exception); } } } @Override public void handlePostModifyEvent(CatalogPostModifyEvent event) throws CatalogException { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("Incoming message event of type " + event.getClass().getSimpleName() + " from Catalog"); } // EAT EVENT // this event should be generated locally (to slaves) by the catalog // itself } @Override public void reloaded() { // skip incoming events until context is loaded if (!isEnabled()) { if (LOGGER.isLoggable(java.util.logging.Level.FINE)) { LOGGER.fine("skipping incoming event: context is not initted"); } return; } // EAT EVENT // TODO disable and re-enable the producer!!!!! // this is potentially a problem since this listener should be the first // called by the GeoServer. } }