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 private SqlSessionFactory _sessionFactory; 060 private SQLDataSourceManager _sqlDataSourceManager; 061 private String _contextPath; 062 private String _pluginName; 063 064 private String _dataSourceId; 065 066 private String _dataSourceParameter; 067 private boolean _dataSourceConfigurationParameter; 068 private Set<SqlMap> _sqlMaps; 069 private ServiceManager _manager; 070 071 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 072 { 073 Context ctx = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 074 _contextPath = ctx.getRealPath("/"); 075 } 076 077 @Override 078 public void service(ServiceManager manager) throws ServiceException 079 { 080 _manager = manager; 081 } 082 083 private SQLDataSourceManager getSQLDataSourceManager() 084 { 085 if (_sqlDataSourceManager == null) 086 { 087 try 088 { 089 _sqlDataSourceManager = (SQLDataSourceManager) _manager.lookup(SQLDataSourceManager.ROLE); 090 } 091 catch (ServiceException e) 092 { 093 throw new RuntimeException(e); 094 } 095 } 096 return _sqlDataSourceManager; 097 } 098 099 public void setPluginInfo(String pluginName, String featureName, String id) 100 { 101 _pluginName = pluginName; 102 } 103 104 public void configure(Configuration configuration) throws ConfigurationException 105 { 106 Configuration dataSourceConf = configuration.getChild("datasource", false); 107 if (dataSourceConf == null) 108 { 109 throw new ConfigurationException("The 'datasource' configuration node must be defined.", dataSourceConf); 110 } 111 112 String dataSourceConfParam = dataSourceConf.getValue(); 113 String dataSourceConfType = dataSourceConf.getAttribute("type", "config"); 114 115 _dataSourceConfigurationParameter = StringUtils.equals(dataSourceConfType, "config"); 116 _dataSourceParameter = dataSourceConfParam; 117 118 _sqlMaps = new HashSet<>(); 119 Configuration[] sqlMaps = configuration.getChildren("sqlMap"); 120 for (Configuration sqlMapConf : sqlMaps) 121 { 122 String resourceSrc = sqlMapConf.getAttribute("resource", null); 123 String configSrc = sqlMapConf.getAttribute("config", null); 124 125 if (StringUtils.isBlank(resourceSrc) && StringUtils.isBlank(configSrc)) 126 { 127 throw new ConfigurationException("The sqlmap configuration must have a 'resource' or 'config' attribute.", sqlMapConf); 128 } 129 if (StringUtils.isNotBlank(resourceSrc) && StringUtils.isNotBlank(configSrc)) 130 { 131 throw new ConfigurationException("The sqlmap configuration can't have both 'resource' and 'config' attributes.", sqlMapConf); 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 * Reload configuration and object for mybatis 153 */ 154 protected synchronized void reload() 155 { 156 // Let's check if MyBatis current configuration is ok 157 String newDatasourceId; 158 if (_dataSourceConfigurationParameter) 159 { 160 newDatasourceId = Config.getInstance().getValueAsString(_dataSourceParameter); 161 } 162 else 163 { 164 newDatasourceId = _dataSourceParameter; 165 } 166 167 if (getSQLDataSourceManager().getDefaultDataSourceId().equals(newDatasourceId)) 168 { 169 // resolve "default", as default may change 170 newDatasourceId = getSQLDataSourceManager().getDefaultDataSourceDefinition().getId(); 171 } 172 173 if (StringUtils.equals(newDatasourceId, _dataSourceId)) 174 { 175 return; 176 } 177 178 // No it's not ok. Let's reload 179 _dataSourceId = newDatasourceId; 180 181 DataSource dataSource = getSQLDataSourceManager().getSQLDataSource(_dataSourceId); 182 if (dataSource == null) 183 { 184 throw new RuntimeException("Cannot (re)load MyBatis: Invalid datasource id: " + _dataSourceId); 185 } 186 187 SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); 188 189 TransactionFactory transactionFactory = new JdbcTransactionFactory(); 190 Environment env = new Environment(_dataSourceId, transactionFactory, dataSource); 191 192 org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration(env); 193 config.setCacheEnabled(true); 194 config.setLazyLoadingEnabled(true); 195 196 for (SqlMap sqlMap : _sqlMaps) 197 { 198 String sourceType = sqlMap.getSourceType(); 199 String source = sqlMap.getSource(); 200 201 @SuppressWarnings("resource") 202 InputStream mapperStream = null; 203 String mapperLocation = null; 204 205 try 206 { 207 if ("config".equals(sourceType)) 208 { 209 File file = null; 210 if (source.startsWith("/")) 211 { 212 // Absolute path (from the root context path). 213 file = new File(_contextPath, source); 214 } 215 else 216 { 217 // Relative path 218 File pluginDir = PluginsManager.getInstance().getPluginLocation(_pluginName); 219 file = new File(pluginDir, source); 220 } 221 222 mapperLocation = file.toURI().toASCIIString(); 223 try 224 { 225 mapperStream = new FileInputStream(file); 226 } 227 catch (FileNotFoundException e) 228 { 229 throw new RuntimeException("Cannot (re)load MyBatis: Cannot find configuration file: " + file, e); 230 } 231 } 232 else 233 { 234 mapperLocation = source; 235 mapperStream = getClass().getResourceAsStream(source); 236 } 237 238 if (getLogger().isInfoEnabled()) 239 { 240 getLogger().info("Initialized mybatis mapper at location '{}' for datasource id '{}'", mapperLocation, _dataSourceId); 241 } 242 243 XMLMapperBuilder mapperParser = new XMLMapperBuilder(mapperStream, config, mapperLocation, config.getSqlFragments()); 244 mapperParser.parse(); 245 } 246 finally 247 { 248 IOUtils.closeQuietly(mapperStream); 249 } 250 } 251 252 _sessionFactory = sessionFactoryBuilder.build(config); 253 } 254 255 /** 256 * Returns the myBatis {@link SqlSession}. 257 * @return the myBatis {@link SqlSession}. 258 */ 259 protected SqlSession getSession() 260 { 261 return getSession(false); 262 } 263 264 /** 265 * Returns the myBatis {@link SqlSession}. 266 * @param autoCommit if the underlying Connection should auto commit statements. 267 * @return the myBatis {@link SqlSession}. 268 */ 269 protected SqlSession getSession(boolean autoCommit) 270 { 271 reload(); 272 return _sessionFactory.openSession(autoCommit); 273 } 274 275 class SqlMap 276 { 277 private String _source; 278 private String _sourceType; 279 280 public String getSource() 281 { 282 return _source; 283 } 284 285 public void setSource(String source) 286 { 287 _source = source; 288 } 289 290 public String getSourceType() 291 { 292 return _sourceType; 293 } 294 295 public void setSourceType(String sourceType) 296 { 297 _sourceType = sourceType; 298 } 299 } 300}