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