/* * Copyright (c) 2010, SQL Power Group Inc. * * This file is part of SQL Power Library. * * SQL Power Library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * SQL Power Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.enterprise; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import ca.sqlpower.enterprise.client.ProjectLocation; import ca.sqlpower.sql.DataSourceCollection; import ca.sqlpower.sql.DatabaseListChangeEvent; import ca.sqlpower.sql.DatabaseListChangeListener; import ca.sqlpower.sql.JDBCDataSource; import ca.sqlpower.sql.JDBCDataSourceType; import ca.sqlpower.sql.Olap4jDataSource; import ca.sqlpower.sql.PlDotIni; import ca.sqlpower.sql.SPDataSource; public abstract class DataSourceCollectionUpdater implements DatabaseListChangeListener, PropertyChangeListener, UndoableEditListener { protected final ProjectLocation projectLocation; /** * If true this updater is currently posting properties to the server. If * properties are being posted to the server and an event comes in because * of a change during posting the updater should not try to repost the message * it is currently trying to post. */ protected boolean postingProperties = false; protected final ResponseHandler<Void> responseHandler = new ResponseHandler<Void>() { public Void handleResponse(HttpResponse response) throws ClientProtocolException, IOException { if (response.getStatusLine().getStatusCode() != 200) { throw new ClientProtocolException( "Failed to create/update data source on server. Reason:\n" + EntityUtils.toString(response.getEntity())); } else { return null; } } }; public DataSourceCollectionUpdater(ProjectLocation projectLocation) { this.projectLocation = projectLocation; } public abstract HttpClient getHttpClient(); public void attach(DataSourceCollection<JDBCDataSource> dsCollection) { dsCollection.addDatabaseListChangeListener(this); dsCollection.addUndoableEditListener(this); for (JDBCDataSourceType jdst : dsCollection.getDataSourceTypes()) { jdst.addPropertyChangeListener(this); } for (SPDataSource ds : dsCollection.getConnections()) { ds.addPropertyChangeListener(this); } } public void detach(DataSourceCollection<JDBCDataSource> dsCollection) { dsCollection.removeDatabaseListChangeListener(this); dsCollection.removeUndoableEditListener(this); for (JDBCDataSourceType jdst : dsCollection.getDataSourceTypes()) { jdst.removePropertyChangeListener(this); } for (SPDataSource ds : dsCollection.getConnections()) { ds.removePropertyChangeListener(this); } } /** * Handles the addition of a new database entry, relaying its current * state to the server. Also begins listening to the new data source as * would have happened if the new data source existed before * {@link #attach(DataSourceCollection)} was invoked. */ public void databaseAdded(DatabaseListChangeEvent e) { SPDataSource source = e.getDataSource(); source.addPropertyChangeListener(this); List<NameValuePair> properties = new ArrayList<NameValuePair>(); for (Map.Entry<String, String> ent : source.getPropertiesMap().entrySet()) { properties.add(new BasicNameValuePair(ent.getKey(), ent.getValue())); } databaseAdded(e, source, properties); } public void databaseAdded(DatabaseListChangeEvent e, SPDataSource source, List<NameValuePair> properties) { if (source instanceof JDBCDataSource) { postJDBCDataSourceProperties((JDBCDataSource) source, properties); } } /** * Handles deleting of a database entry by requesting that the server * deletes it. Also unlistens to the data source to prevent memory * leaks. */ @Override public void databaseRemoved(DatabaseListChangeEvent e) { HttpClient httpClient = getHttpClient(); try { SPDataSource removedDS = e.getDataSource(); HttpDelete request = new HttpDelete(jdbcDataSourceURI(removedDS)); httpClient.execute(request, responseHandler); } catch (Exception ex) { throw new RuntimeException(ex); } finally { httpClient.getConnectionManager().shutdown(); } } protected URI jdbcDataSourceTypeURI(JDBCDataSourceType jdst) throws URISyntaxException { return ClientSideSessionUtils.getServerURI(projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG + "/data-sources/type/" + jdst.getName()); } protected URI jdbcDataSourceURI(SPDataSource jds) throws URISyntaxException { if (!(jds instanceof JDBCDataSource)) throw new IllegalStateException("DataSource must be an instance of JDBCDataSource"); return ClientSideSessionUtils.getServerURI(projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG + "/data-sources/JDBCDataSource/" + jds.getName()); } protected void postJDBCDataSourceProperties(JDBCDataSource ds, List<NameValuePair> properties) { if (postingProperties) return; HttpClient httpClient = getHttpClient(); try { URI jdbcDataSourceURI = jdbcDataSourceURI(ds); try { HttpPost request = new HttpPost(jdbcDataSourceURI); request.setEntity(new UrlEncodedFormEntity(properties)); httpClient.execute(request, responseHandler); } catch (IOException ex) { throw new RuntimeException("Server request failed at " + jdbcDataSourceURI, ex); } } catch (URISyntaxException ex) { throw new RuntimeException(ex); } finally { httpClient.getConnectionManager().shutdown(); } } protected void postJDBCDataSourceTypeProperties(JDBCDataSourceType jdst, List<NameValuePair> properties) { if (postingProperties) return; HttpClient httpClient = getHttpClient(); try { HttpPost request = new HttpPost(jdbcDataSourceTypeURI(jdst)); request.setEntity(new UrlEncodedFormEntity(properties)); httpClient.execute(request, responseHandler); } catch (Exception ex) { throw new RuntimeException(ex); } finally { httpClient.getConnectionManager().shutdown(); } } public void undoableEditHappened(UndoableEditEvent e) { if (e.getEdit() instanceof PlDotIni.AddDSTypeUndoableEdit) { JDBCDataSourceType jdst = ((PlDotIni.AddDSTypeUndoableEdit) e.getEdit()).getType(); jdst.addPropertyChangeListener(this); List<NameValuePair> properties = new ArrayList<NameValuePair>(); for (String name : jdst.getPropertyNames()) { properties.add(new BasicNameValuePair(name, jdst.getProperty(name))); } postJDBCDataSourceTypeProperties(jdst, properties); } if (e.getEdit() instanceof PlDotIni.RemoveDSTypeUndoableEdit) { JDBCDataSourceType jdst = ((PlDotIni.RemoveDSTypeUndoableEdit) e.getEdit()).getType(); jdst.removePropertyChangeListener(this); removeJDBCDataSourceType(jdst); } } public void removeJDBCDataSourceType(JDBCDataSourceType jdst) { HttpClient httpClient = getHttpClient(); try { HttpDelete request = new HttpDelete(jdbcDataSourceTypeURI(jdst)); httpClient.execute(request, responseHandler); } catch (Exception ex) { throw new RuntimeException(ex); } finally { httpClient.getConnectionManager().shutdown(); } } /** * Handles changes to individual data sources by relaying their new * state to the server. * <p> * <b>Implementation note:</b> Presently, all properties for the data * source are sent back to the server every time one of them changes. * This is not the desired behaviour, but without rethinking the * SPDataSource event system, there is little else we can do: the * property change events tell us JavaBeans property names, but in order * to send incremental updates, we's need to know the pl.ini property * key names. * * @param evt * The event describing the change. Its source must be the * data source object which was modified. */ public void propertyChange(PropertyChangeEvent evt) { // Updating all properties is less than ideal, but a property change event does // not tell us what the "pl.ini" key for the property is. Object source = evt.getSource(); if (source instanceof SPDataSource) { SPDataSource ds = (SPDataSource) source; ds.addPropertyChangeListener(this); List<NameValuePair> properties = new ArrayList<NameValuePair>(); for (Map.Entry<String, String> ent : ds.getPropertiesMap().entrySet()) { properties.add(new BasicNameValuePair(ent.getKey(), ent.getValue())); } propertyChange(evt, ds, properties); } if (source instanceof JDBCDataSourceType) { JDBCDataSourceType jdst = (JDBCDataSourceType) source; jdst.addPropertyChangeListener(this); List<NameValuePair> properties = new ArrayList<NameValuePair>(); for (String name : jdst.getPropertyNames()) { properties.add(new BasicNameValuePair(name, jdst.getProperty(name))); } postJDBCDataSourceTypeProperties(jdst, properties); } } public void propertyChange(PropertyChangeEvent evt, SPDataSource ds, List<NameValuePair> properties) { if (ds instanceof JDBCDataSource) { postJDBCDataSourceProperties((JDBCDataSource) ds, properties); } } }