001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.io.res.type.datasource;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.PipedInputStream;
025import java.io.PipedOutputStream;
026import java.sql.Connection;
027import java.sql.SQLException;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031import java.util.WeakHashMap;
032
033import lucee.commons.io.res.Resource;
034import lucee.commons.io.res.ResourceProvider;
035import lucee.commons.io.res.ResourceProviderPro;
036import lucee.commons.io.res.Resources;
037import lucee.commons.io.res.type.datasource.core.Core;
038import lucee.commons.io.res.type.datasource.core.MSSQL;
039import lucee.commons.io.res.type.datasource.core.MySQL;
040import lucee.commons.io.res.util.ResourceLockImpl;
041import lucee.commons.io.res.util.ResourceUtil;
042import lucee.commons.lang.ExceptionUtil;
043import lucee.commons.lang.SizeOf;
044import lucee.commons.lang.StringUtil;
045import lucee.runtime.config.Config;
046import lucee.runtime.config.ConfigImpl;
047import lucee.runtime.db.DatasourceConnection;
048import lucee.runtime.db.DatasourceManagerImpl;
049import lucee.runtime.engine.ThreadLocalPageContext;
050import lucee.runtime.exp.ApplicationException;
051import lucee.runtime.exp.DatabaseException;
052import lucee.runtime.exp.PageException;
053import lucee.runtime.exp.PageRuntimeException;
054import lucee.runtime.op.Caster;
055import lucee.runtime.type.Sizeable;
056
057import org.apache.commons.collections.map.ReferenceMap;
058
059
060/**
061 * Resource Provider for ram resource
062 */
063public final class DatasourceResourceProvider implements ResourceProviderPro,Sizeable {
064
065        public static final int DBTYPE_ANSI92=0;
066        public static final int DBTYPE_MSSQL=1;
067        public static final int DBTYPE_MYSQL=2;
068
069        private static final int MAXAGE = 5000;
070        
071        //private static final int CONNECTION_ID = 0;
072                
073        private String scheme="ds";
074        
075        boolean caseSensitive=true;
076        //private Resources resources;
077        private long lockTimeout=1000;
078        private ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,caseSensitive);
079        private DatasourceManagerImpl _manager;
080        private String defaultPrefix="rdr";
081        //private DataSourceManager manager;
082        //private Core core;
083        private Map cores=new WeakHashMap();
084        private Map attrCache=new ReferenceMap();
085        private Map attrsCache=new ReferenceMap();
086        private Map arguments;
087
088
089        @Override
090        public long sizeOf() {
091                return SizeOf.size(cores)+SizeOf.size(attrCache)+SizeOf.size(attrsCache)+SizeOf.size(lock);
092        }
093
094        
095
096
097        /**
098         * initalize ram resource
099         * @param scheme
100         * @param arguments
101         * @return RamResource
102         */
103        public ResourceProvider init(String scheme,Map arguments) {
104                if(!StringUtil.isEmpty(scheme))this.scheme=scheme;
105                
106                if(arguments!=null) {
107                        this.arguments=arguments;
108                        // case-sensitive
109                        Object oCaseSensitive= arguments.get("case-sensitive");
110                        if(oCaseSensitive!=null) {
111                                caseSensitive=Caster.toBooleanValue(oCaseSensitive,true);
112                        }
113                        
114                        // prefix
115                        Object oPrefix= arguments.get("prefix");
116                        if(oPrefix!=null) {
117                                defaultPrefix=Caster.toString(oPrefix,defaultPrefix);
118                        }
119                        
120                        // lock-timeout
121                        Object oTimeout = arguments.get("lock-timeout");
122                        if(oTimeout!=null) {
123                                lockTimeout=Caster.toLongValue(oTimeout,lockTimeout);
124                        }
125                }
126                lock.setLockTimeout(lockTimeout);
127                lock.setCaseSensitive(caseSensitive);
128                return this;
129        }
130        
131        @Override
132        public Resource getResource(String path) {
133                StringBuilder sb=new StringBuilder();
134                return new DatasourceResource(this,parse(sb,path),sb.toString());
135        }
136        
137        
138        public ConnectionData parse(StringBuilder subPath,String path) {
139                path=ResourceUtil.removeScheme(scheme,path);
140                
141                ConnectionData data=new ConnectionData();
142                int atIndex=path.indexOf('@');
143                int slashIndex=path.indexOf('/');
144                if(slashIndex==-1){
145                        slashIndex=path.length();
146                        path+="/";
147                }
148                int index;
149                
150                // username/password
151                if(atIndex!=-1) {
152                        index=path.indexOf(':');
153                        if(index!=-1 && index<atIndex) {
154                                data.setUsername(path.substring(0,index));
155                                data.setPassword(path.substring(index+1,atIndex));
156                        }
157                        else data.setUsername(path.substring(0,atIndex));
158                }
159                // host port
160                if(slashIndex>atIndex+1) {
161                        data.setDatasourceName(path.substring(atIndex+1,slashIndex));
162                }
163                if(slashIndex>atIndex+1) {
164                        index=path.indexOf(':',atIndex+1);
165                        if(index!=-1 && index>atIndex && index<slashIndex) {
166                                data.setDatasourceName(path.substring(atIndex+1,index));
167                                data.setPrefix(path.substring(index+1,slashIndex));
168                        }
169                        else {
170                                data.setDatasourceName(path.substring(atIndex+1,slashIndex));
171                                data.setPrefix(defaultPrefix);
172                        }
173                }
174                subPath.append(path.substring(slashIndex));
175                return data;
176        }
177        
178
179        @Override
180        public String getScheme() {
181                return scheme;
182        }
183        @Override
184        public void setResources(Resources resources) {
185                //this.resources=resources;
186        }
187
188        @Override
189        public void lock(Resource res) throws IOException {
190                lock.lock(res);
191        }
192
193        @Override
194        public void unlock(Resource res) {
195                lock.unlock(res);
196        }
197
198        @Override
199        public void read(Resource res) throws IOException {
200                lock.read(res);
201        }
202
203        @Override
204        public boolean isAttributesSupported() {
205                return false;
206        }
207
208        @Override
209        public boolean isCaseSensitive() {
210                return caseSensitive;
211        }
212
213        @Override
214        public boolean isModeSupported() {
215                return true;
216        }
217        
218        private DatasourceManagerImpl getManager() {
219                if(_manager==null){
220                        Config config = ThreadLocalPageContext.getConfig();
221                        _manager=new DatasourceManagerImpl((ConfigImpl) config);
222                }
223                return _manager;
224        }
225        
226        private Core getCore(ConnectionData data) throws PageException{
227                Core core = (Core) cores.get(data.datasourceName);
228                if(core==null){
229                        DatasourceConnection dc = getManager().getConnection(ThreadLocalPageContext.get(), data.getDatasourceName(), data.getUsername(), data.getPassword());
230                        try {           
231                                
232                                dc.getConnection().setAutoCommit(false);
233                                dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
234                                
235                                if("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(dc.getDatasource().getClazz().getName()))
236                                        core=new MSSQL(dc,data.getPrefix());
237                                else if("com.microsoft.sqlserver.jdbc.SQLServerDriver".equals(dc.getDatasource().getClazz().getName()))
238                                        core=new MSSQL(dc,data.getPrefix());
239                                else if("net.sourceforge.jtds.jdbc.Driver".equals(dc.getDatasource().getClazz().getName()))
240                                        core=new MSSQL(dc,data.getPrefix());
241                                else if("org.gjt.mm.mysql.Driver".equals(dc.getDatasource().getClazz().getName()))
242                                        core=new MySQL(dc,data.getPrefix());
243                                else 
244                                        throw new ApplicationException("there is no DatasourceResource driver for this database ["+data.getPrefix()+"]");
245                                
246                                cores.put(data.datasourceName, core);
247                        }
248                        catch(SQLException e) {
249                                throw new DatabaseException(e,dc);
250                        }
251                    finally {
252                        release(dc);
253                        //manager.releaseConnection(CONNECTION_ID,dc);
254                    }
255                }
256                return core;
257        }
258        
259        private DatasourceConnection getDatasourceConnection(ConnectionData data, boolean autoCommit) throws PageException {
260                DatasourceConnection dc = getManager().getConnection(ThreadLocalPageContext.get(), data.getDatasourceName(), data.getUsername(), data.getPassword());
261                
262                try {
263                        dc.getConnection().setAutoCommit(autoCommit);
264                        dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
265                } 
266                catch (SQLException e) {
267                        throw new DatabaseException(e,dc);
268                }
269                
270                return dc;
271        }
272        
273        private DatasourceConnection getDatasourceConnection(ConnectionData data) throws PageException {
274                return getDatasourceConnection(data,false);
275        }
276
277        public Attr getAttr(ConnectionData data, int fullPathHash,String path, String name)  {
278                Attr attr=getFromCache(data,path,name);
279                if(attr!=null) return attr;
280                try {
281                        return _getAttr(data, fullPathHash,path, name);
282                } 
283                catch (PageException pe) {
284                        throw new PageRuntimeException(pe);
285                }
286        }
287        
288        private Attr _getAttr(ConnectionData data, int fullPathHash,String path, String name) throws PageException {
289                if(!StringUtil.isEmpty(data.getDatasourceName())) {
290                        DatasourceConnection dc=null;
291                        try {   
292                                dc = getDatasourceConnection(data);     
293                                Attr attr=getCore(data).getAttr(dc,data.getPrefix(),fullPathHash,path,name);
294                                if(attr!=null)return putToCache(data,path,name, attr);
295                        } 
296                        catch (SQLException e) {
297                                throw new DatabaseException(e,dc);
298                        }
299                    finally {
300                        getManager().releaseConnection(ThreadLocalPageContext.get(),dc);
301                    }
302                }
303                return putToCache(data,path,name,Attr.notExists(name,path));
304        }
305
306        public Attr[] getAttrs(ConnectionData data, int pathHash,String path) throws PageException {
307                if(StringUtil.isEmpty(data.getDatasourceName()))
308                        return null;
309                
310                //Attr[] attrs = getFromCache(data, path);
311                //if(attrs!=null) return attrs;
312                
313                DatasourceConnection dc=null;
314                try {           
315                        dc = getDatasourceConnection(data);
316                        List list=getCore(data).getAttrs(dc,data.getPrefix(),pathHash,path);
317                        
318                        if(list!=null){
319                                Iterator it = list.iterator();
320                                Attr[] rtn=new Attr[list.size()];
321                                int index=0;
322                                while(it.hasNext()) {
323                                        rtn[index]=(Attr) it.next();
324                                        putToCache(data,rtn[index].getParent(),rtn[index].getName(),rtn[index]);
325                                        index++;
326                                }
327                                //putToCache(data, path, rtn);
328                                return rtn;
329                        }
330                } 
331                catch (SQLException e) {
332                        throw new DatabaseException(e,dc);
333                }
334            finally {
335                release(dc);
336                //manager.releaseConnection(CONNECTION_ID,dc);
337            }
338                return null;
339        }
340
341        public void create(ConnectionData data, int fullPathHash,int pathHash,String path, String name, int type) throws IOException {
342                if(StringUtil.isEmpty(data.getDatasourceName())) 
343                        throw new IOException("missing datasource definition");
344                
345                removeFromCache(data, path, name);
346                
347
348                DatasourceConnection dc=null;
349                try {           
350                        dc = getDatasourceConnection(data);
351                        getCore(data).create(dc,data.getPrefix(),fullPathHash, pathHash,path,name,type);
352                } 
353                catch (SQLException e) {
354                        throw new IOException(e.getMessage());
355                } 
356                catch (PageException e) {
357                        throw new PageRuntimeException(e);
358                }
359            finally {
360                release(dc);
361            }
362        }
363
364
365        public void delete(ConnectionData data, int fullPathHash,String path, String name) throws IOException {
366                
367                Attr attr = getAttr(data, fullPathHash,path, name);
368                if(attr==null) throw new IOException("can't delete resource "+path+name+", resource does not exist");
369                
370                DatasourceConnection dc=null;
371                try {           
372                        dc = getDatasourceConnection(data);
373                        getCore(data).delete(dc,data.getPrefix(),attr);
374                } 
375                catch (SQLException e) {
376                        throw new IOException(e.getMessage());
377                }
378                catch (PageException e) {
379                        throw new PageRuntimeException(e);
380                }
381            finally {
382                removeFromCache(data, path, name);
383                release(dc);
384                //manager.releaseConnection(CONNECTION_ID,dc);
385            }
386        }
387
388        public InputStream getInputStream(ConnectionData data, int fullPathHash, String path,String name) throws IOException {
389                Attr attr = getAttr(data, fullPathHash,path, name);
390                if(attr==null) throw new IOException("file ["+path+name+"] does not exist");
391                DatasourceConnection dc=null;
392                try {           
393                        dc = getDatasourceConnection(data);
394                        return getCore(data).getInputStream(dc, data.getPrefix(), attr);
395                } 
396                catch (SQLException e) {
397                        throw new IOException(e.getMessage());
398                } 
399                catch (PageException e) {
400                        throw new PageRuntimeException(e);
401                }
402            finally {
403                release(dc);
404                //manager.releaseConnection(CONNECTION_ID,dc);
405            }
406        }
407        
408        public synchronized OutputStream getOutputStream(ConnectionData data, int fullPathHash, int pathHash,String path,String name,boolean append) throws IOException {
409                
410                Attr attr = getAttr(data, fullPathHash,path, name);
411                if(attr.getId()==0){
412                        create(data, fullPathHash, pathHash, path, name, Attr.TYPE_FILE);
413                        attr = getAttr(data, fullPathHash,path, name);
414                }
415                
416                PipedInputStream  pis = new PipedInputStream();
417            PipedOutputStream pos = new PipedOutputStream();
418                pis.connect(pos);
419                DatasourceConnection dc=null;
420                //Connection c=null;
421                try {   
422                        dc = getDatasourceConnection(data);
423                        //Connection c = dc.getConnection();
424                        
425                        DataWriter writer=new DataWriter(getCore(data),dc, data.getPrefix(), attr, pis,this,append);
426                        writer.start();
427                        
428                        return new DatasourceResourceOutputStream(writer,pos);
429                        //core.getOutputStream(dc, name, attr, pis);
430                } 
431                catch (PageException e) {
432                        throw new PageRuntimeException(e);
433                }
434            finally {
435                removeFromCache(data, path, name);
436                //manager.releaseConnection(CONNECTION_ID,dc);
437            }
438        }
439
440
441        public boolean setLastModified(ConnectionData data, int fullPathHash,String path,String name,long time) {
442                 try {
443                        Attr attr = getAttr(data, fullPathHash,path, name); 
444                        DatasourceConnection dc = getDatasourceConnection(data);
445                        try {   
446                                getCore(data).setLastModified(dc,data.getPrefix(),attr,time);
447                        } 
448                        /*catch (SQLException e) {
449                                return false;
450                        } */
451                    finally {
452                        removeFromCache(data, path, name);
453                        release(dc);
454                        //manager.releaseConnection(CONNECTION_ID,dc);
455                    }
456                }
457                catch(Throwable t) {
458                ExceptionUtil.rethrowIfNecessary(t);
459                        return false;
460                }
461                return true;
462        }
463
464        public boolean setMode(ConnectionData data, int fullPathHash,String path,String name,int mode) {
465                try {
466                        Attr attr = getAttr(data, fullPathHash, path, name); 
467                        DatasourceConnection dc = getDatasourceConnection(data);
468                        try {   
469                                getCore(data).setMode(dc,data.getPrefix(),attr,mode);
470                        } 
471                        /*catch (SQLException e) {
472                                return false;
473                        } */
474                    finally {
475                        removeFromCache(data, path, name);
476                        release(dc);
477                        //manager.releaseConnection(CONNECTION_ID,dc);
478                    }
479                }
480                catch(Throwable t) {
481                ExceptionUtil.rethrowIfNecessary(t);
482                        return false;
483                }
484                return true;
485        }
486
487        public boolean concatSupported(ConnectionData data) {
488                try {
489                        return getCore(data).concatSupported();
490                } catch (PageException e) {
491                        return false;
492                }
493        }
494
495        private Attr removeFromCache(ConnectionData data, String path,String name) {
496                attrsCache.remove(data.key()+path);
497                return (Attr) attrCache.remove(data.key()+path+name);
498        }
499
500        private Attr getFromCache(ConnectionData data, String path,String name) {
501                String key=data.key()+path+name;
502                Attr attr=(Attr) attrCache.get(key);
503                
504                if(attr!=null && attr.timestamp()+MAXAGE<System.currentTimeMillis()) {
505                        attrCache.remove(key);
506                        return null;
507                }
508                return attr;
509        }
510        
511        private Attr putToCache(ConnectionData data, String path,String name, Attr attr) {
512                attrCache.put(data.key()+path+name, attr);
513                return attr;
514        }
515
516
517
518        /*private Attr[] getFromCache(ConnectionData data, String path) {
519                String key=data.key()+path;
520                Attr[] attrs= (Attr[]) attrsCache.get(key);
521                
522                / *if(attr!=null && attr.timestamp()+MAXAGE<System.currentTimeMillis()) {
523                        attrCache.remove(key);
524                        return null;
525                }* /
526                return attrs;
527        }
528        
529        private Attr[] putToCache(ConnectionData data, String path, Attr[] attrs) {
530                attrsCache.put(data.key()+path, attrs);
531                return attrs;
532        }*/
533        
534        
535        
536        public class ConnectionData {
537                private String username;
538                private String password;
539                private String datasourceName;
540                private String prefix;
541                /**
542                 * @return the prefix
543                 */
544                public String getPrefix() {
545                        return prefix;
546                }
547
548                /**
549                 * @param prefix the prefix to set
550                 */
551                public void setPrefix(String prefix) {
552                        this.prefix = prefix;
553                }
554
555                /**
556                 * @return the username
557                 */
558                public String getUsername() {
559                        return username;
560                }
561                
562                /**
563                 * @param username the username to set
564                 */
565                public void setUsername(String username) {
566                        this.username = username;
567                }
568                /**
569                 * @return the password
570                 */
571                public String getPassword() {
572                        return password;
573                }
574                /**
575                 * @param password the password to set
576                 */
577                public void setPassword(String password) {
578                        this.password = password;
579                }
580                /**
581                 * @return the datasourceName
582                 */
583                public String getDatasourceName() {
584                        return datasourceName;
585                }
586                /**
587                 * @param datasourceName the datasourceName to set
588                 */
589                public void setDatasourceName(String datasourceName) {
590                        this.datasourceName = datasourceName;
591                }
592                
593                public String key() {
594                        if(StringUtil.isEmpty(username))
595                                        return datasourceName;
596                        return username+":"+password+"@"+datasourceName;
597                }
598                
599        }
600
601
602
603        /**
604         * release datasource connection
605         * @param dc
606         * @param autoCommit 
607         */
608        void release(DatasourceConnection dc) {
609                if(dc!=null) {
610                        
611                                try {
612                                        dc.getConnection().commit();
613                                        dc.getConnection().setAutoCommit(true);
614                                        dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_NONE);
615                                } 
616                                catch (SQLException e) {}
617                        
618                        getManager().releaseConnection(ThreadLocalPageContext.get(),dc);
619                }
620        }
621
622        @Override
623        public Map getArguments() {
624                return arguments;
625        }
626        
627        @Override
628        public char getSeparator() {
629                return '/';
630        }
631
632
633
634
635
636        
637        
638}