001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.datasource; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.FileNotFoundException; 021import java.io.InputStream; 022import java.util.HashSet; 023import java.util.Set; 024 025import javax.sql.DataSource; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.configuration.Configurable; 029import org.apache.avalon.framework.configuration.Configuration; 030import org.apache.avalon.framework.configuration.ConfigurationException; 031import org.apache.avalon.framework.context.ContextException; 032import org.apache.avalon.framework.context.Contextualizable; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.cocoon.Constants; 037import org.apache.cocoon.environment.Context; 038import org.apache.commons.io.IOUtils; 039import org.apache.commons.lang.StringUtils; 040import org.apache.ibatis.builder.xml.XMLMapperBuilder; 041import org.apache.ibatis.mapping.Environment; 042import org.apache.ibatis.session.SqlSession; 043import org.apache.ibatis.session.SqlSessionFactory; 044import org.apache.ibatis.session.SqlSessionFactoryBuilder; 045import org.apache.ibatis.transaction.TransactionFactory; 046import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; 047 048import org.ametys.runtime.config.Config; 049import org.ametys.runtime.plugin.PluginsManager; 050import org.ametys.runtime.plugin.component.AbstractLogEnabled; 051import org.ametys.runtime.plugin.component.PluginAware; 052 053/** 054 * Interface to be implemented by any object that wishes to have 055 * access to one or multiple SqlMapClient. 056 */ 057public abstract class AbstractMyBatisDAO extends AbstractLogEnabled implements Contextualizable, Serviceable, PluginAware, Configurable, Component 058{ 059 /** The service manager */ 060 protected ServiceManager _manager; 061 062 private SqlSessionFactory _sessionFactory; 063 private SQLDataSourceManager _sqlDataSourceManager; 064 private String _contextPath; 065 private String _pluginName; 066 067 private String _dataSourceId; 068 069 private String _dataSourceParameter; 070 private boolean _dataSourceConfigurationParameter; 071 private Set<SqlMap> _sqlMaps; 072 073 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 074 { 075 Context ctx = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 076 _contextPath = ctx.getRealPath("/"); 077 } 078 079 @Override 080 public void service(ServiceManager manager) throws ServiceException 081 { 082 _manager = manager; 083 } 084 085 private SQLDataSourceManager getSQLDataSourceManager() 086 { 087 if (_sqlDataSourceManager == null) 088 { 089 try 090 { 091 _sqlDataSourceManager = (SQLDataSourceManager) _manager.lookup(SQLDataSourceManager.ROLE); 092 } 093 catch (ServiceException e) 094 { 095 throw new RuntimeException(e); 096 } 097 } 098 return _sqlDataSourceManager; 099 } 100 101 public void setPluginInfo(String pluginName, String featureName, String id) 102 { 103 _pluginName = pluginName; 104 } 105 106 public void configure(Configuration configuration) throws ConfigurationException 107 { 108 _configureDatasource(configuration); 109 110 _sqlMaps = new HashSet<>(); 111 Configuration[] sqlMaps = configuration.getChildren("sqlMap"); 112 for (Configuration sqlMapConf : sqlMaps) 113 { 114 String resourceSrc = sqlMapConf.getAttribute("resource", null); 115 String configSrc = sqlMapConf.getAttribute("config", null); 116 117 if (StringUtils.isBlank(resourceSrc) && StringUtils.isBlank(configSrc)) 118 { 119 throw new ConfigurationException("The sqlmap configuration must have a 'resource' or 'config' attribute.", sqlMapConf); 120 } 121 122 if (StringUtils.isNotBlank(resourceSrc) && StringUtils.isNotBlank(configSrc)) 123 { 124 // If both 'resource' and 'config' attributes are set, try to find if the 'config' one exists, if so take it, if not, take the 'resource' one. 125 // This will enable to potentially override the kernel sqlMap ('resource') with the application sqlMap ('config') 126 File file = configSrc.startsWith("/") ? new File(_contextPath, configSrc) /* Absolute path */ 127 : new File(PluginsManager.getInstance().getPluginLocation(_pluginName), configSrc) /* Relative path */; 128 if (!file.isFile()) 129 { 130 configSrc = null; 131 } 132 } 133 134 SqlMap sqlMap = new SqlMap(); 135 136 if (StringUtils.isNotBlank(configSrc)) 137 { 138 sqlMap.setSource(configSrc); 139 sqlMap.setSourceType("config"); 140 } 141 else 142 { 143 sqlMap.setSource(resourceSrc); 144 sqlMap.setSourceType("resource"); 145 } 146 147 _sqlMaps.add(sqlMap); 148 } 149 } 150 151 /** 152 * Configure datasource 153 * @param configuration the configuration 154 * @throws ConfigurationException if an error occurred 155 */ 156 protected void _configureDatasource(Configuration configuration) throws ConfigurationException 157 { 158 Configuration dataSourceConf = configuration.getChild("datasource", false); 159 if (dataSourceConf == null) 160 { 161 throw new ConfigurationException("The 'datasource' configuration node must be defined.", dataSourceConf); 162 } 163 164 String dataSourceConfParam = dataSourceConf.getValue(); 165 String dataSourceConfType = dataSourceConf.getAttribute("type", "config"); 166 167 _dataSourceConfigurationParameter = StringUtils.equals(dataSourceConfType, "config"); 168 _dataSourceParameter = dataSourceConfParam; 169 } 170 171 /** 172 * Reload configuration and object for mybatis 173 */ 174 protected synchronized void reload() 175 { 176 String newDatasourceId = _getDataSourceId(); 177 if (StringUtils.equals(newDatasourceId, _dataSourceId)) 178 { 179 return; 180 } 181 182 // No it's not ok. Let's reload 183 _dataSourceId = newDatasourceId; 184 185 DataSource dataSource = getSQLDataSourceManager().getSQLDataSource(_dataSourceId); 186 if (dataSource == null) 187 { 188 throw new RuntimeException("Cannot (re)load MyBatis: Invalid datasource id: " + _dataSourceId); 189 } 190 191 SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); 192 193 TransactionFactory transactionFactory = new JdbcTransactionFactory(); 194 Environment env = new Environment(_dataSourceId, transactionFactory, dataSource); 195 196 org.apache.ibatis.session.Configuration config = _getMyBatisConfiguration(env); 197 198 for (SqlMap sqlMap : _sqlMaps) 199 { 200 String sourceType = sqlMap.getSourceType(); 201 String source = sqlMap.getSource(); 202 203 @SuppressWarnings("resource") 204 InputStream mapperStream = null; 205 String mapperLocation = null; 206 207 try 208 { 209 if ("config".equals(sourceType)) 210 { 211 File file = null; 212 if (source.startsWith("/")) 213 { 214 // Absolute path (from the root context path). 215 file = new File(_contextPath, source); 216 } 217 else 218 { 219 // Relative path 220 File pluginDir = PluginsManager.getInstance().getPluginLocation(_pluginName); 221 file = new File(pluginDir, source); 222 } 223 224 mapperLocation = file.toURI().toASCIIString(); 225 try 226 { 227 mapperStream = new FileInputStream(file); 228 } 229 catch (FileNotFoundException e) 230 { 231 throw new RuntimeException("Cannot (re)load MyBatis: Cannot find configuration file: " + file, e); 232 } 233 } 234 else 235 { 236 mapperLocation = source; 237 mapperStream = getClass().getResourceAsStream(source); 238 } 239 240 if (getLogger().isInfoEnabled()) 241 { 242 getLogger().info("Initialized mybatis mapper at location '{}' for datasource id '{}'", mapperLocation, _dataSourceId); 243 } 244 245 XMLMapperBuilder mapperParser = new XMLMapperBuilder(mapperStream, config, mapperLocation, config.getSqlFragments()); 246 mapperParser.parse(); 247 } 248 catch (Exception e) 249 { 250 // Consider it needs a reload next time the method is called 251 _dataSourceId = null; 252 throw e; 253 } 254 finally 255 { 256 IOUtils.closeQuietly(mapperStream); 257 } 258 } 259 260 _sessionFactory = sessionFactoryBuilder.build(config); 261 } 262 263 /** 264 * Get the mybatis configuration 265 * @param env the mybatis environnement 266 * @return the mybatis configuration 267 */ 268 protected org.apache.ibatis.session.Configuration _getMyBatisConfiguration(Environment env) 269 { 270 org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration(env); 271 config.setCacheEnabled(true); 272 config.setLazyLoadingEnabled(true); 273 274 return config; 275 } 276 277 /** 278 * Get datasource id 279 * @return the datasource id 280 */ 281 protected String _getDataSourceId() 282 { 283 // Let's check if MyBatis current configuration is ok 284 String newDatasourceId; 285 if (_dataSourceConfigurationParameter) 286 { 287 newDatasourceId = Config.getInstance().getValueAsString(_dataSourceParameter); 288 } 289 else 290 { 291 newDatasourceId = _dataSourceParameter; 292 } 293 294 if (getSQLDataSourceManager().getDefaultDataSourceId().equals(newDatasourceId)) 295 { 296 // resolve "default", as default may change 297 newDatasourceId = getSQLDataSourceManager().getDefaultDataSourceDefinition().getId(); 298 } 299 300 return newDatasourceId; 301 } 302 303 /** 304 * Returns the myBatis {@link SqlSession}. 305 * @return the myBatis {@link SqlSession}. 306 */ 307 protected SqlSession getSession() 308 { 309 return getSession(false); 310 } 311 312 /** 313 * Returns the myBatis {@link SqlSession}. 314 * @param autoCommit if the underlying Connection should auto commit statements. 315 * @return the myBatis {@link SqlSession}. 316 */ 317 protected SqlSession getSession(boolean autoCommit) 318 { 319 reload(); 320 return _sessionFactory.openSession(autoCommit); 321 } 322 323 class SqlMap 324 { 325 private String _source; 326 private String _sourceType; 327 328 public String getSource() 329 { 330 return _source; 331 } 332 333 public void setSource(String source) 334 { 335 _source = source; 336 } 337 338 public String getSourceType() 339 { 340 return _sourceType; 341 } 342 343 public void setSourceType(String sourceType) 344 { 345 _sourceType = sourceType; 346 } 347 } 348}