package org.geoserver.hibernate;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.Info;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerInfo;
import org.geoserver.config.LoggingInfo;
import org.geoserver.config.ServiceInfo;
import org.geoserver.platform.GeoServerExtensions;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Hibernate interceptor which forwards hibernate events to the catalog.
*
* @author Justin Deoliveira, The Open Planning Project
*
*/
public class HibPropertyChangeInterceptor extends EmptyInterceptor implements ApplicationContextAware {
/**
* A thread local used to prevent the propagation of the same event over and over which will
* happen if an event listener makes another query against hibernate
*/
static ThreadLocal<List> events = new ThreadLocal();
//TODO: a post request hook to clean up this thread local
private final static Logger LOGGER = Logging.getLogger(HibPropertyChangeInterceptor.class);
/**
* spring app context
*/
ApplicationContext appContext;
/**
* catalog
*/
Catalog catalog;
/**
* config
*/
GeoServer geoServer;
public HibPropertyChangeInterceptor() {
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.appContext = applicationContext;
}
protected Catalog catalog() {
if (catalog == null) {
synchronized (this) {
if (catalog == null) {
catalog = (Catalog) GeoServerExtensions.bean("catalog", appContext);
}
}
}
return catalog;
}
protected GeoServer geoServer() {
if (geoServer == null) {
synchronized (this) {
if (geoServer == null) {
geoServer = GeoServerExtensions.bean(GeoServer.class, appContext);
}
}
}
return geoServer;
}
public void afterTransactionCompletion(Transaction tx) {
if (!tx.wasRolledBack()) {
//TODO: fire post modified... maybe via thread local?
}
}
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
Object[] previousState, String[] propertyNames, Type[] types) {
List<String> propertyNamesChanged = new ArrayList<String>();
List<Object> oldValues = new ArrayList<Object>();
List<Object> newValues = new ArrayList<Object>();
if (previousState != null) {
for (int i = 0; i < propertyNames.length; i++) {
Object oldValue = previousState[i];
Object newValue = currentState[i];
if (!Utilities.equals(oldValue, newValue)) {
propertyNamesChanged.add(propertyNames[i]);
oldValues.add(oldValue);
newValues.add(newValue);
}
}
} else {
for (int i = 0; i < propertyNames.length; i++) {
propertyNamesChanged.add(propertyNames[i]);
}
}
Info info = (Info) entity;
Event e = new Event(info, propertyNamesChanged);
if (events.get() == null) {
events.set(new ArrayList());
}
if (events.get().contains(e)) {
return false;
}
events.get().add(e);
try {
if (!filterEvent(info, propertyNamesChanged, oldValues, newValues)) {
if (info instanceof CatalogInfo) {
catalog().fireModified((CatalogInfo)info, propertyNamesChanged, oldValues, newValues);
}
else {
if (info instanceof GeoServerInfo) {
geoServer().fireGlobalModified((GeoServerInfo)info, propertyNamesChanged, oldValues, newValues);
}
else if (info instanceof LoggingInfo) {
geoServer().fireLoggingModified((LoggingInfo)info, propertyNamesChanged, oldValues, newValues);
}
else if (info instanceof ServiceInfo) {
geoServer().fireServiceModified((ServiceInfo)info, propertyNamesChanged, oldValues, newValues);
}
}
}
}
finally {
events.get().remove(e);
}
return false;
}
/*
* method to filter out situations in which we don;t want to throw an event
*/
protected boolean filterEvent(
Info entity, List<String> propertyNamesChanged, List oldValues, List newValues) {
//handle default namespace/workspace changing or default datastore
if ((entity instanceof WorkspaceInfo || entity instanceof NamespaceInfo || entity instanceof StoreInfo) &&
propertyNamesChanged.size() == 1 && propertyNamesChanged.contains("default")) {
return true;
}
return false;
}
static class Event {
Info info;
List<String> changed;
Event(Info info, List<String> changed) {
this.info = info;
this.changed = changed;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((changed == null) ? 0 : changed.hashCode());
result = prime * result + ((info == null) ? 0 : info.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Event other = (Event) obj;
if (changed == null) {
if (other.changed != null)
return false;
} else if (!changed.equals(other.changed))
return false;
if (info == null) {
if (other.info != null)
return false;
} else if (!info.equals(other.info))
return false;
return true;
}
}
}