001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2006-2013 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.server; 011 012import mondrian.olap.*; 013import mondrian.rolap.RolapSchema; 014import mondrian.tui.XmlaSupport; 015import mondrian.util.Pair; 016import mondrian.xmla.DataSourcesConfig; 017 018import org.apache.log4j.Logger; 019 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Timer; 023import java.util.TimerTask; 024import java.util.concurrent.TimeUnit; 025 026/** 027 * Implementation of 028 * {@link RepositoryContentFinder} that 029 * periodically reloads the content of the repository. 030 * 031 * <p>The updates are performed by a background thread. 032 * It is important to call {@link DynamicContentFinder#shutdown()} 033 * once this object can be disposed of. 034 * 035 * @author Thiyagu Ajit 036 * @author Luc Boudreau 037 * @author Julian Hyde 038 */ 039public class DynamicContentFinder 040 extends UrlRepositoryContentFinder 041{ 042 protected String lastDataSourcesConfigString; 043 044 protected DataSourcesConfig.DataSources dataSources; 045 046 private static final Logger LOGGER = 047 Logger.getLogger(MondrianServer.class); 048 049 private final Timer timer; 050 051 /** 052 * Creates a DynamicContentFinder. 053 * @param dataSourcesConfigUrl URL of repository 054 */ 055 public DynamicContentFinder( 056 String dataSourcesConfigUrl) 057 { 058 super(dataSourcesConfigUrl); 059 reloadDataSources(); 060 timer = Util.newTimer( 061 "mondrian.server.DynamicContentFinder$timer", 062 true); 063 final Pair<Long, TimeUnit> interval = 064 Util.parseInterval( 065 String.valueOf( 066 MondrianProperties.instance() 067 .XmlaSchemaRefreshInterval.get()), 068 TimeUnit.MILLISECONDS); 069 final long period = interval.right.toMillis(interval.left); 070 timer.schedule( 071 new TimerTask() { 072 public void run() { 073 reloadDataSources(); 074 } 075 }, 076 period, 077 period); 078 } 079 080 /** 081 * Cleans up all background updating jobs. 082 */ 083 public void shutdown() { 084 super.shutdown(); 085 timer.cancel(); 086 } 087 088 /** 089 * Checks for updates to datasources content, flushes obsolete catalogs. 090 */ 091 public synchronized void reloadDataSources() { 092 try { 093 String dataSourcesConfigString = getContent(); 094 if (!hasDataSourcesContentChanged(dataSourcesConfigString)) { 095 return; 096 } 097 DataSourcesConfig.DataSources newDataSources = 098 XmlaSupport.parseDataSources( 099 dataSourcesConfigString, LOGGER); 100 if (newDataSources == null) { 101 return; 102 } 103 flushObsoleteCatalogs(newDataSources); 104 this.dataSources = newDataSources; 105 this.lastDataSourcesConfigString = dataSourcesConfigString; 106 } catch (Exception e) { 107 throw Util.newError( 108 e, 109 "Failed to parse data sources config '" + url + "'"); 110 } 111 } 112 113 protected boolean hasDataSourcesContentChanged( 114 String dataSourcesConfigString) 115 { 116 return dataSourcesConfigString != null 117 && !dataSourcesConfigString.equals( 118 this.lastDataSourcesConfigString); 119 } 120 121 private Map< 122 String, 123 Pair<DataSourcesConfig.DataSource, DataSourcesConfig.Catalog>> 124 createCatalogMap( 125 DataSourcesConfig.DataSources newDataSources) 126 { 127 Map< 128 String, 129 Pair<DataSourcesConfig.DataSource, DataSourcesConfig.Catalog>> 130 newDatasourceCatalogNames = 131 new HashMap<String, 132 Pair<DataSourcesConfig.DataSource, 133 DataSourcesConfig.Catalog>>(); 134 for (DataSourcesConfig.DataSource dataSource 135 : newDataSources.dataSources) 136 { 137 for (DataSourcesConfig.Catalog catalog 138 : dataSource.catalogs.catalogs) 139 { 140 if (catalog.dataSourceInfo == null) { 141 catalog.dataSourceInfo = dataSource.dataSourceInfo; 142 } 143 newDatasourceCatalogNames.put( 144 catalog.name, Pair.of(dataSource, catalog)); 145 } 146 } 147 return newDatasourceCatalogNames; 148 } 149 150 public synchronized void flushObsoleteCatalogs( 151 DataSourcesConfig.DataSources newDataSources) 152 { 153 if (dataSources == null) { 154 return; 155 } 156 157 Map<String, 158 Pair<DataSourcesConfig.DataSource, 159 DataSourcesConfig.Catalog>> newDatasourceCatalogs = 160 createCatalogMap(newDataSources); 161 162 for (DataSourcesConfig.DataSource oldDataSource 163 : dataSources.dataSources) 164 { 165 for (DataSourcesConfig.Catalog oldCatalog 166 : oldDataSource.catalogs.catalogs) 167 { 168 Pair<DataSourcesConfig.DataSource, DataSourcesConfig.Catalog> 169 pair = 170 newDatasourceCatalogs.get(oldCatalog.name); 171 if (pair == null 172 || !areCatalogsEqual( 173 oldDataSource, oldCatalog, pair.left, pair.right)) 174 { 175 flushCatalog(oldCatalog.name); 176 } 177 } 178 } 179 } 180 181 protected void flushCatalog(String catalogName) { 182 for (RolapSchema schema : RolapSchema.getRolapSchemas()) { 183 if (schema.getName().equals(catalogName)) { 184 schema.getInternalConnection().getCacheControl(null) 185 .flushSchema(schema); 186 } 187 } 188 } 189 190 public static boolean areCatalogsEqual( 191 DataSourcesConfig.DataSource dataSource1, 192 DataSourcesConfig.Catalog catalog1, 193 DataSourcesConfig.DataSource dataSource2, 194 DataSourcesConfig.Catalog catalog2) 195 { 196 return 197 Util.equals(dsi(dataSource1, catalog1), dsi(dataSource2, catalog2)) 198 && catalog1.name.equals(catalog2.name) 199 && catalog1.definition.equals(catalog2.definition); 200 } 201 202 public static String dsi( 203 DataSourcesConfig.DataSource dataSource, 204 DataSourcesConfig.Catalog catalog) 205 { 206 return catalog.dataSourceInfo == null && dataSource != null 207 ? dataSource.dataSourceInfo 208 : catalog.dataSourceInfo; 209 } 210} 211 212// End DynamicContentFinder.java