001/* 
002 * JKNIV, SQLegance keeping queries maintainable.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.sqlegance.builder;
021
022import java.util.Map.Entry;
023import java.util.Properties;
024
025import javax.naming.Context;
026import javax.naming.InitialContext;
027import javax.naming.NamingException;
028import javax.sql.DataSource;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032import org.w3c.dom.Element;
033import org.w3c.dom.Node;
034import org.w3c.dom.NodeList;
035
036import net.sf.jkniv.asserts.Assertable;
037import net.sf.jkniv.asserts.AssertsFactory;
038import net.sf.jkniv.reflect.beans.ObjectProxy;
039import net.sf.jkniv.reflect.beans.ObjectProxyFactory;
040import net.sf.jkniv.sqlegance.RepositoryProperty;
041import net.sf.jkniv.sqlegance.RepositoryType;
042import net.sf.jkniv.sqlegance.Statistical;
043import net.sf.jkniv.sqlegance.builder.xml.NoSqlStats;
044import net.sf.jkniv.sqlegance.builder.xml.SqlStats;
045import net.sf.jkniv.sqlegance.dialect.SqlDialect;
046import net.sf.jkniv.sqlegance.dialect.SqlFeatureFactory;
047import net.sf.jkniv.sqlegance.dialect.SqlFeatureSupport;
048import net.sf.jkniv.sqlegance.logger.DataMasking;
049import net.sf.jkniv.sqlegance.logger.SimpleDataMasking;
050import net.sf.jkniv.sqlegance.transaction.TransactionType;
051
052/**
053 * 
054 * @author Alisson Gomes
055 * @since 0.6.0
056 */
057public class RepositoryConfig
058{
059    private static final Logger                 LOG                    = LoggerFactory
060            .getLogger(RepositoryConfig.class);
061    private static String                       defaultFileConfig      = "/repository-config.xml";
062    private static final String                 ATTR_NAME              = "name";
063    private static final String                 ATTR_VALUE             = "value";
064    private static final String                 ATTR_TX_TYPE           = "transaction-type";
065    private static final String                 XPATH_REPO_NODE        = "/repository";
066    static final String                         SCHEMA_XSD             = "/net/sf/jkniv/sqlegance/builder/xml/sqlegance-config.xsd";
067    static final String                         XPATH_ROOT_NODE        = "repository-config";
068    private static final String                 XPATH_PROPERTY_NODE    = XPATH_ROOT_NODE + XPATH_REPO_NODE
069            + "[@name='%s']/properties/property";
070    private static final String                 XPATH_DESCRIPTION_NODE = XPATH_ROOT_NODE + XPATH_REPO_NODE
071            + "[@name='%s']/description[1]";
072    private static final String                 XPATH_JNDI_DS_NODE     = XPATH_ROOT_NODE + XPATH_REPO_NODE
073            + "[@name='%s']/jndi-data-source[1]";
074    
075    private static final Assertable             NOT_NULL                = AssertsFactory.getNotNull();
076    //private static final Map<String, SqlLogger> loggers                = new HashMap<String, SqlLogger>();
077    private String                              name;
078    private String                              description;
079    private String                              jndiDataSource;
080    private TransactionType                     transactionType;
081    private Properties                          properties;
082    //private SqlLogger                           sqlLogger;
083    private DataMasking                         masking;
084    private RepositoryType                      repositoryType;
085    private SqlDialect                          sqlDialect;
086    
087    public RepositoryConfig()
088    {
089        this(null, true);
090    }
091    
092    /**
093     * Build new configuration object that access to DataSource and your properties
094     * Default instance is created if parameter is a <code>null</code> name 
095     * @param name from repository configuration
096     * @throws IllegalArgumentException if the name is <code>null</code>
097     */
098    public RepositoryConfig(String name)
099    {
100        this(name, false);
101    }
102    
103    private RepositoryConfig(String name, boolean defaultRepo)
104    {
105        if (!defaultRepo)
106            NOT_NULL.verify(name);
107        
108        this.name = name;
109        this.properties = new Properties();
110        this.load();
111        setDataMasking();
112        //setSqlLogger();
113        //this.sqlDialectName = getProperty(RepositoryProperty.SQL_DIALECT);
114        if (this.transactionType == null)
115            this.transactionType = TransactionType.LOCAL;
116        if (isEmpty(this.jndiDataSource))
117            this.jndiDataSource = name;
118        
119    }
120    
121    private void load()
122    {
123        XmlReader xmlReader = new XmlReader(defaultFileConfig);
124        if (xmlReader.load())
125        {
126            NodeList nodes = xmlReader.evaluateXpath(XPATH_ROOT_NODE + XPATH_REPO_NODE);
127            if (nodes != null)
128            {
129                for (int i = 0; i < nodes.getLength(); i++)
130                {
131                    Node node = nodes.item(i);
132                    if (node.getNodeType() == Node.ELEMENT_NODE)
133                    {
134                        Element element = (Element) node;
135                        String name = element.getAttribute(ATTR_NAME);
136                        if (isEmpty(this.name) || this.name.equals(name))// first repository is default
137                        {
138                            if (LOG.isDebugEnabled())
139                                LOG.debug("Binding Sql Statements context [{}] with Repository config [{}]", this.name,
140                                        name);
141                            this.name = name;
142                            parseAttributes(element);// FIXME parser attributes
143                            parseDescription(xmlReader);
144                            parseJndiDataSource(xmlReader);
145                            parseProperties(xmlReader);
146                            break;
147                        }
148                    }
149                }
150            }
151            else if (!hasProperties())
152            {
153                LOG.info("repository-config.xml does not found. Using [{}] as jndi datasource.", this.name);
154                this.jndiDataSource = this.name;
155            }
156        }
157        defineDialect();
158    }
159    
160    private void parseAttributes(Element element)
161    {
162        this.name = element.getAttribute(ATTR_NAME);
163        String txType = element.getAttribute(ATTR_TX_TYPE);
164        String type = element.getAttribute("type");
165        this.transactionType = TransactionType.get(txType);
166        this.repositoryType = RepositoryType.get(type);
167    }
168    
169    private void parseDescription(XmlReader xmlReader)
170    {
171        NodeList nodes = xmlReader.evaluateXpath(String.format(XPATH_DESCRIPTION_NODE, name));
172        for (int i = 0; i < nodes.getLength(); i++)
173        {
174            Node node = nodes.item(i);
175            if (node.getNodeType() == Node.ELEMENT_NODE)
176            {
177                Element element = (Element) node;
178                this.description = xmlReader.getTextFromElement(element);
179            }
180        }
181    }
182    
183    private void parseJndiDataSource(XmlReader xmlReader)
184    {
185        NodeList nodes = xmlReader.evaluateXpath(String.format(XPATH_JNDI_DS_NODE, name));
186        if (nodes != null)
187        {
188            for (int i = 0; i < nodes.getLength(); i++)
189            {
190                Node node = nodes.item(i);
191                if (node.getNodeType() == Node.ELEMENT_NODE)
192                {
193                    Element element = (Element) node;
194                    this.jndiDataSource = xmlReader.getTextFromElement(element);
195                }
196            }
197        }
198    }
199    
200    private void parseProperties(XmlReader xmlReader)
201    {
202        NodeList nodesProperties = xmlReader.evaluateXpath(String.format(XPATH_PROPERTY_NODE, name));
203        if (nodesProperties != null)
204        {
205            for (int i = 0; i < nodesProperties.getLength(); i++)
206            {
207                Node nodes = nodesProperties.item(i);
208                if (nodes.getNodeType() == Node.ELEMENT_NODE)
209                {
210                    Element element = (Element) nodes;
211                    String properName = element.getAttribute(ATTR_NAME);
212                    String properValue = element.getAttribute(ATTR_VALUE);
213                    this.properties.put(properName, properValue);
214                }
215            }
216        }
217    }
218    
219//    private void setSqlLogger()
220//    {
221//        SqlLogger sqlLogger = loggers.get(name);
222//        if (sqlLogger == null || sqlLogger.getLogLevel() == LogLevel.NONE)
223//        {
224//            LogLevel logLevel = LogLevel.get(getProperty(RepositoryProperty.DEBUG_SQL));
225//            sqlLogger = new SqlLogger(logLevel, this.masking);
226//            loggers.put(this.name, sqlLogger);
227//        }
228//    }
229    
230    private void setDataMasking()
231    {
232        if (masking == null)
233        {
234            masking = new SimpleDataMasking();
235            String className = getProperty(RepositoryProperty.DATA_MASKING);
236            if (className != null)
237            {
238                ObjectProxy<DataMasking> proxy = ObjectProxyFactory.of(className);
239                masking = proxy.newInstance();
240            }
241        }
242    }
243    
244    public String getQueryNameStrategy()
245    {
246        return getProperty(RepositoryProperty.QUERY_NAME_STRATEGY);
247    }
248
249    public String getJdbcAdapterFactory()
250    {
251        String adpterClassName = getProperty(RepositoryProperty.JDBC_ADAPTER_FACTORY);
252        return adpterClassName;
253    }
254
255    public boolean isShotKeyEnable()
256    {
257        return Boolean.valueOf(getProperty(RepositoryProperty.SHORT_NAME_ENABLE));
258    }
259    
260    public boolean isReloadableXmlEnable()
261    {
262        return Boolean.valueOf(getProperty(RepositoryProperty.RELOADABLE_XML_ENABLE));
263    }
264    
265    public void add(String key,  String value)
266    {
267        Properties props = new Properties();
268        props.put(key, value);
269        add(props);
270    }
271    
272    public void add(Properties props)
273    {
274        if (props != null)
275        {
276            for (Entry<Object, Object> entry : props.entrySet())
277            {
278                Object old = this.properties.put(entry.getKey().toString(), entry.getValue());
279                if (old != null)
280                    LOG.info("The value of key [{}] with original value [{}] was replacement to [{}]", entry.getKey(),
281                            old, entry.getValue());
282                if(RepositoryProperty.SQL_DIALECT.key().equals(entry.getKey().toString()))
283                    defineDialect();
284            }
285        }
286    }
287
288    public boolean hasProperties()
289    {
290        return (!this.properties.isEmpty());
291    }
292
293    public DataMasking getDataMasking()
294    {
295        return this.masking;
296    }
297    
298    public String getDescription()
299    {
300        return description;
301    }
302    
303    public String getName()
304    {
305        return name;
306    }
307    
308    public TransactionType getTransactionType()
309    {
310        return transactionType;
311    }
312    
313    public SqlDialect getSqlDialect()
314    {
315        return this.sqlDialect;
316    }
317
318    public void setSqlDialect(SqlDialect sqlDialect)
319    {
320        this.sqlDialect = sqlDialect;
321    }
322    
323    public Statistical getStatistical()
324    {
325        String enable = getProperty(RepositoryProperty.SQL_STATS);
326        if ("true".equalsIgnoreCase(enable))
327            return new SqlStats();
328        
329        return NoSqlStats.getInstance();
330    }
331
332    public String getJndiDataSource()
333    {
334        return jndiDataSource;
335    }
336    
337    public Properties getProperties()
338    {
339        return properties;
340    }
341    
342    public String getProperty(RepositoryProperty proper)
343    {
344        return properties.getProperty(proper.key(), proper.defaultValue());
345    }
346    
347    public String getProperty(String proper)
348    {
349        return properties.getProperty(proper);
350    }
351    
352    public DataSource lookup()
353    {
354        String jndi = getJndiDataSource();
355        DataSource ds = null;
356        Context ctx = null;
357        try
358        {
359            //ctx = (Context) new InitialContext().lookup("java:comp/env");
360            ctx = new InitialContext();
361            if (LOG.isDebugEnabled())
362                LOG.debug("Lookuping JNDI resource with: new InitialContext().lookup(\"{}\") ...", jndi);
363            ds = (DataSource) ctx.lookup(jndi);
364        }
365        catch (NamingException ne)
366        {
367            LOG.error("NamingException, cannot lookup jndi datasource [" + jndi + "]: " + ne.getMessage());
368        }
369        return ds;
370    }
371    
372    private boolean isEmpty(String value)
373    {
374        return (value == null || "".equals(value));
375    }
376    
377    public RepositoryType getRepositoryType()
378    {
379        return repositoryType;
380    }
381
382    private void defineDialect()
383    {
384        String sqlDialectName = getProperty(RepositoryProperty.SQL_DIALECT);
385        ObjectProxy<SqlDialect> proxy = ObjectProxyFactory.of(sqlDialectName);
386        sqlDialect = proxy.newInstance();
387        for (SqlFeatureSupport feature : SqlFeatureSupport.values())
388        {
389            String value = getProperty(RepositoryProperty.PREFIX+".feature."+feature.name().toLowerCase());
390            if (value != null)
391            {
392                boolean supported = Boolean.valueOf(value);
393                sqlDialect.addFeature(SqlFeatureFactory.newInstance(feature, supported));
394            }
395        }
396        String maxParameters = getProperty(RepositoryProperty.PREFIX+".jdbc.max_parameters");
397        if (maxParameters != null)
398            sqlDialect.setMaxOfParameters(Integer.valueOf(maxParameters));
399    }
400
401    @Override
402    public String toString()
403    {
404        return "RepositoryConfig [name=" + name + ", sqlDialectName=" + getSqlDialect() + ", transactionType="
405                + transactionType + "]";
406    }
407    
408}