/* (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.jdbcconfig.config; import static org.geoserver.catalog.CatalogFacade.ANY_WORKSPACE; import static org.geoserver.catalog.Predicates.and; import static org.geoserver.catalog.Predicates.equal; import static org.geoserver.catalog.Predicates.isNull; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.rmi.server.UID; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import org.geoserver.catalog.Info; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.impl.ModificationProxy; import org.geoserver.catalog.util.CloseableIterator; import org.geoserver.catalog.util.CloseableIteratorAdapter; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerFacade; import org.geoserver.config.GeoServerInfo; import org.geoserver.config.LoggingInfo; import org.geoserver.config.ServiceInfo; import org.geoserver.config.SettingsInfo; import org.geoserver.jdbcconfig.internal.ConfigDatabase; import org.geoserver.logging.LoggingStartupContextListener; import org.geoserver.logging.LoggingUtils; import org.geoserver.ows.util.OwsUtils; import org.geoserver.ows.util.ClassProperties; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.resource.ResourceStore; import org.geotools.util.logging.Logging; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; @ParametersAreNonnullByDefault public class JDBCGeoServerFacade implements GeoServerFacade { static final Logger LOGGER = Logging.getLogger(JDBCGeoServerFacade.class); private static final String GLOBAL_ID = "GeoServerInfo.global"; private static final String GLOBAL_LOGGING_ID = "LoggingInfo.global"; private GeoServer geoServer; private final ConfigDatabase db; private ResourceStore ddResourceStore; private GeoServerResourceLoader resourceLoader; public void setResourceLoader(GeoServerResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public void setDdResourceStore(ResourceStore ddResourceStore) { this.ddResourceStore = ddResourceStore; } public JDBCGeoServerFacade(final ConfigDatabase db) { this.db = db; } @SuppressWarnings("deprecation") private void reinitializeLogging() { try { LoggingInfo realLogInfo = this.getLogging(); if (realLogInfo == null) { return; } LoggingInfo startLogInfo = LoggingStartupContextListener.getLogging( ddResourceStore == null ? resourceLoader : ddResourceStore); // Doing this reflectively so that if LoggingInfo gets new properties, this should still // work. KS ClassProperties properties = OwsUtils.getClassProperties(LoggingInfo.class); List<String> propertyNames = new ArrayList<String>(properties.properties().size()); List<Object> newValues = new ArrayList<Object>(properties.properties().size()); List<Object> oldValues = new ArrayList<Object>(properties.properties().size()); final Level propertyTableLevel = Level.FINE; LOGGER.log(propertyTableLevel, "Checking Logging configuration in case it neeeds to be reinitialized"); for (String propName : properties.properties()) { // Don't care about the return type Method read = properties.getter(propName, null); Object newVal = read.invoke(realLogInfo); Object oldVal = read.invoke(startLogInfo); if((newVal==null && oldVal==null) || (newVal!=null && newVal.equals(oldVal))) { // Values the same LOGGER.log(propertyTableLevel, "=== {0} (logging.xml: {1}, JDBCConfig: {2})", new Object[] {read.getName(), oldVal, newVal}); } else { // Values different propertyNames.add(propName); newValues.add(newVal); oldValues.add(oldVal); LOGGER.log(propertyTableLevel, "=/= {0} (logging.xml: {1}, JDBCConfig: {2})", new Object[] {read.getName(), oldVal, newVal}); } } // If there's a difference other than the ID if(!(propertyNames.isEmpty() || (propertyNames.size()==1 && propertyNames.get(0).equals("Id")))) { LOGGER.log(Level.WARNING, "Start up logging config does not match that in JDBCConfig. Reconfiguring now. Logs preceding this message may reflect a different configuration."); LoggingUtils.initLogging(resourceLoader, realLogInfo.getLevel(), !realLogInfo.isStdOutLogging(), realLogInfo.getLocation()); } } catch (Exception ex) { // If something bad happens, log it and keep going with the wrong logging config LOGGER.log(Level.SEVERE, "Problem while reinitializing Logging from JDBC Config. Log configuration may not be correct.", ex); } } @Override public GeoServer getGeoServer() { return geoServer; } @Override public void setGeoServer(GeoServer geoServer) { this.geoServer = geoServer; this.db.setGeoServer(geoServer); reinitializeLogging(); } @Override public GeoServerInfo getGlobal() { GeoServerInfo global = db.getById(GLOBAL_ID, GeoServerInfo.class); return global; } @Override public void setGlobal(GeoServerInfo global) { OwsUtils.set(global, "id", GLOBAL_ID); if (global.getSettings() == null) { SettingsInfo defaultSettings = geoServer.getFactory().createSettings(); add(defaultSettings); global.setSettings(defaultSettings); //JD: disabling this check, global settings should have an id //}else if(null == global.getSettings().getId()){ }else { add(global.getSettings()); } if (null == getGlobal()) { db.add(global); } else { db.save(ModificationProxy.create(global, GeoServerInfo.class)); } GeoServerInfo saved = getGlobal(); Preconditions.checkNotNull(saved); } @Override public void save(GeoServerInfo global) { // this object is a proxy ModificationProxy h = (ModificationProxy) Proxy.getInvocationHandler(global); // fire out what changed List<String> propertyNames = h.getPropertyNames(); List<Object> newValues = h.getNewValues(); List<Object> oldValues = h.getOldValues(); geoServer.fireGlobalModified(global, propertyNames, oldValues, newValues); db.save(global); } @Override public LoggingInfo getLogging() { LoggingInfo loggingInfo = db.getById(GLOBAL_LOGGING_ID, LoggingInfo.class); return loggingInfo; } @Override public void setLogging(LoggingInfo logging) { OwsUtils.set(logging, "id", GLOBAL_LOGGING_ID); if (null == getLogging()) { db.add(logging); } else { db.save(ModificationProxy.create(logging, LoggingInfo.class)); } LoggingInfo saved = getLogging(); Preconditions.checkNotNull(saved); } @Override public void save(LoggingInfo logging) { // this object is a proxy ModificationProxy h = (ModificationProxy) Proxy.getInvocationHandler(logging); // fire out what changed List<String> propertyNames = h.getPropertyNames(); List<Object> newValues = h.getNewValues(); List<Object> oldValues = h.getOldValues(); geoServer.fireLoggingModified(logging, propertyNames, oldValues, newValues); db.save(logging); } @Override public void add(ServiceInfo service) { setId(service, ServiceInfo.class); service.setGeoServer(geoServer); db.add(service); } @Override public void remove(ServiceInfo service) { db.remove(service); } @Override public void save(ServiceInfo service) { // this object is a proxy ModificationProxy h = (ModificationProxy) Proxy.getInvocationHandler(service); // fire out what changed List<String> propertyNames = h.getPropertyNames(); List<Object> newValues = h.getNewValues(); List<Object> oldValues = h.getOldValues(); geoServer.fireServiceModified(service, propertyNames, oldValues, newValues); db.save(service); } @Override public SettingsInfo getSettings(WorkspaceInfo workspace) { Filter filter = equal("workspace.id", workspace.getId()); return get(SettingsInfo.class, filter); } @Override public void add(SettingsInfo settings) { setId(settings, SettingsInfo.class); db.add(settings); } @Override public void save(SettingsInfo settings) { // this object is a proxy ModificationProxy h = (ModificationProxy) Proxy.getInvocationHandler(settings); // fire out what changed List<String> propertyNames = h.getPropertyNames(); List<Object> newValues = h.getNewValues(); List<Object> oldValues = h.getOldValues(); geoServer.fireSettingsModified(settings, propertyNames, oldValues, newValues); db.save(settings); } @Override public void remove(SettingsInfo settings) { db.remove(settings); } @Override public Collection<? extends ServiceInfo> getServices() { return getServices((WorkspaceInfo) null); } private Filter filterForWorkspace(WorkspaceInfo workspace) { if (workspace != null && workspace != ANY_WORKSPACE) { return equal("workspace.id", workspace.getId()); } else { return filterForGlobal(); } } private Filter filterForGlobal() { return isNull("workspace.id"); } @SuppressWarnings("unchecked") private <T extends ServiceInfo> CloseableIterator<T> filterServices(final Class<T> clazz, CloseableIterator<ServiceInfo> it) { return (CloseableIterator<T>) CloseableIteratorAdapter.filter(it, new Predicate<ServiceInfo>(){ @Override public boolean apply(@Nullable ServiceInfo input) { return clazz.isAssignableFrom(input.getClass()); } }); } @Override public Collection<? extends ServiceInfo> getServices(WorkspaceInfo workspace) { Filter filter = filterForWorkspace(workspace); return db.queryAsList(ServiceInfo.class, filter, null, null, null); } @Override public <T extends ServiceInfo> T getService(final Class<T> clazz) { Filter filter = filterForGlobal(); List<ServiceInfo> all = db.queryAsList(ServiceInfo.class, filter, null, null, null); for (ServiceInfo si : all) { if (clazz.isAssignableFrom(si.getClass())) { return clazz.cast(si); } } return null; } @Override public <T extends ServiceInfo> T getService(final WorkspaceInfo workspace, final Class<T> clazz) { Filter filter = filterForWorkspace(workspace); // In order to handle new service types, get all services, deserialize them, and then filter // by checking if the implement the given interface. Since there shouldn't be too many per // workspace, this shouldn't be a significant performance problem. CloseableIterator<T> it = filterServices(clazz, db.query(ServiceInfo.class, filter, null, null, (SortBy)null)); T service; if (it.hasNext()){ service = it.next(); } else { if(LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Could not find service of type "+clazz+" in "+workspace); return null; } if(it.hasNext()) { LOGGER.log(Level.WARNING, "Found multiple services of type "+clazz+" in "+ workspace); return null; } return service; } @Override public <T extends ServiceInfo> T getService(final String id, final Class<T> clazz) { return db.getById(id, clazz); } @Override public <T extends ServiceInfo> T getServiceByName(final String name, final Class<T> clazz) { return findByName(name, null, clazz); } @Override public <T extends ServiceInfo> T getServiceByName(final String name, final WorkspaceInfo workspace, final Class<T> clazz) { return findByName(name, workspace, clazz); } private <T extends Info> T findByName(@Nonnull final String name, @Nullable final WorkspaceInfo workspace, @Nonnull final Class<T> clazz) throws AssertionError { Filter filter = equal("name", name); if (null != workspace && ANY_WORKSPACE != workspace) { final String wsId = workspace.getId(); Filter wsFilter = equal("workspace.id", wsId); filter = and(filter, wsFilter); } try { return get(clazz, filter); } catch (IllegalArgumentException multipleResults) { return null; } } public <T extends Info> T get(Class<T> type, Filter filter) throws IllegalArgumentException { CloseableIterator<T> it = db.query(type, filter, null, 2, (org.opengis.filter.sort.SortBy)null); T result = null; try { if (it.hasNext()) { result = it.next(); if (it.hasNext()) { throw new IllegalArgumentException( "Specified query predicate resulted in more than one object"); } } } finally { it.close(); } return result; } @Override public void dispose() { db.dispose(); } private void setId(Info info, Class<? extends Info> type) { final String curId = info.getId(); if (null == curId) { final String uid = new UID().toString(); final String id = type.getSimpleName() + "." + uid; OwsUtils.set(info, "id", id); } } }