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.runtime.search;
020
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Map.Entry;
026
027import lucee.commons.collection.MapFactory;
028import lucee.commons.io.FileUtil;
029import lucee.commons.io.log.Log;
030import lucee.commons.io.res.Resource;
031import lucee.commons.io.res.util.ResourceUtil;
032import lucee.commons.lang.StringUtil;
033import lucee.commons.lock.KeyLock;
034import lucee.commons.lock.Lock;
035import lucee.commons.net.HTTPUtil;
036import lucee.runtime.PageContext;
037import lucee.runtime.config.ConfigImpl;
038import lucee.runtime.exp.DatabaseException;
039import lucee.runtime.exp.PageException;
040import lucee.runtime.op.Caster;
041import lucee.runtime.type.ArrayImpl;
042import lucee.runtime.type.Collection.Key;
043import lucee.runtime.type.KeyImpl;
044import lucee.runtime.type.Query;
045import lucee.runtime.type.QueryColumn;
046import lucee.runtime.type.QueryImpl;
047import lucee.runtime.type.dt.DateTime;
048import lucee.runtime.type.dt.DateTimeImpl;
049import lucee.runtime.type.util.ArrayUtil;
050import lucee.runtime.type.util.KeyConstants;
051import lucee.runtime.type.util.ListUtil;
052
053import org.w3c.dom.Element;
054
055/**
056 * represent a single Collection
057 */
058public abstract class SearchCollectionSupport implements SearchCollectionPlus {
059
060        private static final long serialVersionUID = 8089312879341384114L;
061        
062        private static final int LOCK_TIMEOUT = 10*60*1000; // ten minutes
063    private final String name;
064        private final Resource path;
065        private String language;
066        private DateTime lastUpdate;
067    private SearchEngineSupport searchEngine;
068        //TODO change visibility to private
069    protected Map<String,SearchIndex> indexes=MapFactory.<String,SearchIndex>getConcurrentMap();
070
071    private DateTime created;
072
073    private Log log;
074        //private static LockManager manager=Lock Manager Impl.getInstance();
075    private KeyLock<String> lock=new KeyLock<String>();
076    
077    
078        /**
079         * constructor of the class
080         * @param searchEngine
081         * @param name name of the Collection
082         * @param path
083         * @param language
084         * @param count total count of documents in the collection
085         * @param lastUpdate
086         * @param created 
087         */
088        public SearchCollectionSupport(SearchEngineSupport searchEngine, String name,Resource path, String language, DateTime lastUpdate, DateTime created) {
089                this.searchEngine=searchEngine;
090            this.name=name;
091                this.path=path;
092                this.language=SearchUtil.translateLanguage(language);
093        this.lastUpdate=lastUpdate;
094        this.created=created;
095
096        log=((ConfigImpl)searchEngine.getConfig()).getLog("search");
097        }
098
099        @Override
100        public final void create() throws SearchException {
101                Lock l = lock();
102                try {
103            _create();
104        }
105                finally {
106                        unlock(l);
107                }
108        }
109
110        /**
111         * create a collection
112         * @throws SearchException
113         */
114        protected abstract void _create() throws SearchException;
115
116    @Override
117    public final void optimize() throws SearchException  {
118        Lock l = lock();
119         try {
120         _optimize();
121        changeLastUpdate();
122    }
123                finally {
124                        unlock(l);
125                }
126    }
127
128    /**
129     * optimize a Collection
130     * @throws SearchException
131     */
132    protected abstract void _optimize() throws SearchException ;
133
134    @Override
135    public final void map(Resource path) throws SearchException  {
136        Lock l = lock();
137        try {
138        _map(path);
139        changeLastUpdate();
140    }
141                finally {
142                        unlock(l);
143                }
144    }
145
146    /**
147     * map a Collection
148     * @param path
149     * @throws SearchException
150     */ 
151    protected abstract void _map(Resource path) throws SearchException ;
152
153    @Override
154    public final void repair() throws SearchException  {
155        Lock l = lock();
156        try {
157        _repair();
158        changeLastUpdate();
159    }
160                finally {
161                        unlock(l);
162                }
163    }
164
165    /**
166     * repair a Collection
167     * @throws SearchException
168     */
169    protected abstract void _repair() throws SearchException ;
170    
171    @Override
172    public IndexResult index(PageContext pc, String key, short type, String urlpath, String title, String body, String language, 
173            String[] extensions, String query, boolean recurse,String categoryTree, String[] categories,
174            String custom1, String custom2, String custom3, String custom4) throws PageException, MalformedURLException, SearchException {
175        return index(pc, key, type, urlpath, title, body, language, extensions, query, recurse, categoryTree, categories, 10000, custom1, custom2, custom3, custom4);
176    }
177                
178    // FUTURE add this to interface
179    public IndexResult index(PageContext pc, String key, short type, String urlpath, String title, String body, String language, 
180            String[] extensions, String query, boolean recurse,String categoryTree, String[] categories, long timeout,
181            String custom1, String custom2, String custom3, String custom4) throws PageException, MalformedURLException, SearchException {
182        language=SearchUtil.translateLanguage(language);
183        Lock l = lock();
184        try {
185        SearchIndex si = new SearchIndex(title,key,type,query,extensions,language,urlpath,categoryTree,categories,custom1,custom2,custom3,custom4);
186        String id=si.getId();
187        IndexResult ir=IndexResultImpl.EMPTY;
188        if(type==SearchIndex.TYPE_FILE){
189                Resource file=ResourceUtil.toResourceNotExisting(pc,key);
190            if(!file.isFile())throw new SearchException("value of attribute key must specify a existing file, ["+key+"] is invalid");
191             ir=indexFile(id,title,file,language);
192        }
193        else if(type==SearchIndex.TYPE_PATH){
194                Resource dir=ResourceUtil.toResourceNotExisting(pc,key);
195            if(!dir.isDirectory())throw new SearchException("value of attribute key must specify a existing directory, ["+key+"] is invalid");
196            ir=indexPath(id,title,dir,extensions,recurse,language);
197        }
198        else if(type==SearchIndex.TYPE_URL) {
199                ir=indexURL(id,title,new URL(key),extensions,recurse,language,timeout);
200        }
201        else if(type==SearchIndex.TYPE_CUSTOM) {
202                Query qv;
203                if(StringUtil.isEmpty(query)){
204            
205                // set columns
206                        lucee.runtime.type.Array columns=new ArrayImpl();
207                columns.append("key");
208                columns.append("body");
209                if(!StringUtil.isEmpty(title))columns.append("title");
210                if(!StringUtil.isEmpty(urlpath))columns.append("urlpath");
211                if(!StringUtil.isEmpty(custom1))columns.append("custom1");
212                if(!StringUtil.isEmpty(custom2))columns.append("custom2");
213                if(!StringUtil.isEmpty(custom3))columns.append("custom3");
214                if(!StringUtil.isEmpty(custom4))columns.append("custom4");
215                
216            // populate query with a single row
217                qv=new QueryImpl(columns,1,"query");
218                // body
219                qv.setAt(KeyConstants._key, 1, key);
220                key="key";
221
222                // body
223                qv.setAt(KeyConstants._body, 1, body);
224                body="body";
225
226                // title
227                if(!StringUtil.isEmpty(title)){
228                        qv.setAt(KeyConstants._title, 1, title);
229                        title="title";
230                }
231
232                // urlpath
233                if(!StringUtil.isEmpty(urlpath)){
234                        qv.setAt("urlpath", 1, urlpath);
235                        urlpath="urlpath";
236                }
237
238                // custom1
239                if(!StringUtil.isEmpty(custom1)){
240                        qv.setAt("custom1", 1, custom1);
241                        custom1="custom1";
242                }
243                // custom2
244                if(!StringUtil.isEmpty(custom2)){
245                        qv.setAt("custom2", 1, custom2);
246                        custom2="custom2";
247                }
248                // custom3
249                if(!StringUtil.isEmpty(custom3)){
250                        qv.setAt("custom3", 1, custom3);
251                        custom3="custom3";
252                }
253                // custom4
254                if(!StringUtil.isEmpty(custom4)){
255                        qv.setAt("custom4", 1, custom4);
256                        custom4="custom4";
257                }
258            }
259                else qv = Caster.toQuery(pc.getVariable(query));
260            
261                QueryColumn keyColumn=qv.getColumn(key);
262            
263            String[] strBodies=ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(body,','));
264            QueryColumn[] bodyColumns=new QueryColumn[strBodies.length];
265            for(int i=0;i<bodyColumns.length;i++) {
266                bodyColumns[i]=qv.getColumn(strBodies[i]);
267            }
268            
269            ir= indexCustom(id,
270                        getValue(qv,title),
271                    keyColumn,
272                    bodyColumns,
273                    language,
274                    getValue(qv,urlpath),
275                    getValue(qv,custom1),
276                    getValue(qv,custom2),
277                    getValue(qv,custom3),
278                    getValue(qv,custom4));
279        }
280        createIndex(si);
281        return ir;
282    }
283                finally {
284                        unlock(l);
285                }
286    }
287 
288    private QueryColumn getColumnEL(Query query, String column) {
289        if(StringUtil.isEmpty(column)) return null;
290        QueryColumn c = query.getColumn(column,null);
291        
292        return c;
293    }
294 
295    private Object getValue(Query query, String column) {
296        if(StringUtil.isEmpty(column)) return null;
297        QueryColumn c = query.getColumn(column,null);
298        if(c==null) return column;
299        return c;
300    }
301
302    @Override
303    public final IndexResult indexFile(String id,String title, Resource res, String language) throws SearchException {
304        IndexResult ir=_indexFile(id,title,res,language);
305        changeLastUpdate();
306        return ir;
307    }
308
309    /**
310     * updates a collection with a file
311     * @param id
312     * @param title
313     * @param file
314     * @param language
315     * @throws SearchException
316     */
317    protected abstract IndexResult _indexFile(String id, String title, Resource file, String language)  throws SearchException;
318
319    @Override
320    public final IndexResult indexPath(String id, String title, Resource dir, String[] extensions, boolean recurse, String language) throws SearchException {
321        IndexResult ir=_indexPath(id,title,dir,extensions,recurse,language);
322        changeLastUpdate();
323        return ir;
324    }
325
326
327    /**
328     * updates a collection with a path
329     * @param id
330     * @param title
331     * @param dir
332     * @param recurse
333     * @param extensions
334     * @param language
335     * @throws SearchException
336     */
337    protected abstract IndexResult _indexPath(String id,String title, Resource dir, String[] extensions, boolean recurse, String language) throws SearchException;
338
339    @Override
340    public final IndexResult indexURL(String id,String title, URL url, String[] extensions, boolean recurse, String language) throws SearchException {
341        return indexURL(id, title, url, extensions, recurse, language, 10000);
342    }
343        // FUTURE replace this in interface with method above
344    public final IndexResult indexURL(String id,String title, URL url, String[] extensions, boolean recurse, String language,long timeout) throws SearchException {
345        IndexResult ir=_indexURL(id,title,url,extensions,recurse,language,timeout);
346        changeLastUpdate();
347        return ir;
348    } 
349
350    /**
351     * updates a collection with a url
352     * @param id
353     * @param title
354     * @param recurse
355     * @param extensions
356     * @param url
357     * @param language
358     * @throws SearchException
359     */
360    protected abstract IndexResult _indexURL(String id,String title, URL url, String[] extensions, boolean recurse, String language,long timeout) throws SearchException ;
361    
362    protected abstract IndexResult _indexURL(String id,String title, URL url, String[] extensions, boolean recurse, String language) throws SearchException ;
363
364    @Override
365    public final IndexResult indexCustom(String id, QueryColumn title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, 
366                QueryColumn custom1, QueryColumn custom2, QueryColumn custom3, QueryColumn custom4) throws SearchException {
367        IndexResult ir=_indexCustom(id,title,keyColumn,bodyColumns,language,null,custom1,custom2,custom3,custom4);
368        changeLastUpdate();
369        return ir;
370    }
371    /*public final IndexResult indexCustom(String id, QueryColumn title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, 
372                QueryColumn urlpath,QueryColumn custom1, QueryColumn custom2, QueryColumn custom3, QueryColumn custom4) throws SearchException {
373        IndexResult ir=_indexCustom(id,title,keyColumn,bodyColumns,language,urlpath,custom1,custom2,custom3,custom4);
374        changeLastUpdate();
375        return ir;
376    }*/
377    public final IndexResult indexCustom(String id, Object title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, 
378                Object urlpath,Object custom1, Object custom2, Object custom3, Object custom4) throws SearchException {
379        IndexResult ir=_indexCustom(id,title,keyColumn,bodyColumns,language,urlpath,custom1,custom2,custom3,custom4);
380        changeLastUpdate();
381        return ir;
382    }
383    
384    
385    public final IndexResult deleteCustom(String id, QueryColumn keyColumn) throws SearchException {
386        IndexResult ir = _deleteCustom(id,keyColumn);
387        changeLastUpdate();
388        return ir;
389    }
390    
391    protected abstract IndexResult _deleteCustom(String id, QueryColumn keyColumn) throws SearchException;
392
393        /**
394     * updates a collection with a custom
395     * @param id
396     * @param title Title for the Index
397     * @param keyColumn Key Column
398     * @param bodyColumns Body Column Array
399     * @param language Language for index
400     * @param custom1 
401     * @param custom2 
402     * @param custom3 
403     * @param custom4 
404     * @throws SearchException
405     */
406    //protected abstract IndexResult _indexCustom(String id,QueryColumn title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, QueryColumn custom1, QueryColumn custom2, QueryColumn custom3, QueryColumn custom4) throws SearchException;
407    protected abstract IndexResult _indexCustom(
408                String id,Object title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, Object urlpath
409                , Object custom1, Object custom2, Object custom3, Object custom4) throws SearchException;
410
411    /**
412     * @param index
413     * @throws SearchException
414     */
415    private void createIndex(SearchIndex index) throws SearchException {
416        Iterator<String> it = indexes.keySet().iterator();
417        SearchIndex otherIndex=null;
418        
419        while(it.hasNext()) {
420            Object key=it.next();
421            if(key.equals(index.getId())) {
422                otherIndex=indexes.get(key);
423                break;
424            }
425        }
426        
427        Element collElement=searchEngine.getCollectionElement(name);
428        
429        // Insert
430        if(otherIndex==null) {
431            addIndex(index);
432            collElement.appendChild(searchEngine.toElement(index));
433        }
434        // Update
435        else {
436            addIndex(index);
437            Element el=searchEngine.getIndexElement(collElement,index.getId());
438            searchEngine.setAttributes(el,index);
439        }
440        changeLastUpdate();
441    }
442
443    /**
444     * @param index
445     */
446    public void addIndex(SearchIndex index) {
447        indexes.put(index.getId(),index);
448    }
449
450        @Override
451        public final String getLanguage() {
452                return language;
453        }
454    
455        @Override
456        public final IndexResult purge() throws SearchException {
457                Lock l = lock();
458        try {
459        indexes.clear();
460        IndexResult ir=_purge();
461        searchEngine.purgeCollection(this);
462        changeLastUpdate();
463        return ir;
464        }
465                finally {
466                        unlock(l);
467                }
468        }
469
470        /**
471         * purge a collection
472         * @throws SearchException
473         */
474        protected abstract IndexResult _purge() throws SearchException;
475
476    @Override
477    public final IndexResult delete() throws SearchException {
478        Lock l = lock();
479        try {
480        IndexResult ir=_delete();
481            searchEngine.removeCollection(this);
482            return ir;
483    }
484                finally {
485                        unlock(l);
486                }
487    }
488
489    /**
490     * delete the collection from a file
491         * @throws SearchException
492     */
493    protected abstract IndexResult _delete() throws SearchException;
494
495    @Override
496    public final IndexResult deleteIndex(PageContext pc,String key,short type,String queryName) throws SearchException {
497        //if(queryName==null) queryName="";
498        Key k;
499        
500        if(type==SearchIndex.TYPE_CUSTOM) {
501                // delete all when no key is defined
502                if(StringUtil.isEmpty(key,true))
503                        return deleteIndexNotCustom(pc, key, type, queryName);
504                
505                try{
506                        Query qv;
507                if(!StringUtil.isEmpty(queryName)) {
508                        k=KeyImpl.init(key);
509                        qv = Caster.toQuery(pc.getVariable(queryName));
510                }
511                else {
512                        k=KeyConstants._id;
513                    Key[] cols = new Key[]{k};
514                    String[] types = new String[]{"VARCHAR"};
515                        qv=new QueryImpl(cols,types, 1,"query");
516                        qv.setAtEL(k, 1, key);
517                }
518                        
519                        
520                        
521                    QueryColumn keyColumn=qv.getColumn(k);
522                        return deleteCustom("custom", keyColumn);
523                }
524                catch(PageException pe){
525                        throw new SearchException(pe);
526                }
527        }
528        return deleteIndexNotCustom(pc, key, type, queryName);
529        
530        
531    }
532    
533
534    public final IndexResult deleteIndexNotCustom(PageContext pc,String key,short type,String queryName) throws SearchException {
535        Iterator<String> it = indexes.keySet().iterator();
536        while(it.hasNext()) {
537            String id = it.next();
538            if(id.equals(SearchIndex.toId(type,key,queryName))) {
539                SearchIndex index=indexes.get(id);
540                
541                IndexResult ir=_deleteIndex(index.getId());
542                Element indexEl=searchEngine.getIndexElement(searchEngine.getCollectionElement(name),index.getId());
543                if(indexEl!=null)indexEl.getParentNode().removeChild(indexEl);
544                changeLastUpdate();
545                    return ir; 
546            }
547        }
548        return new IndexResultImpl(0,0,0);
549    }
550
551    /**
552     * delete a Index from collection
553         * @param id id ofthe Index to delete
554     * @throws SearchException
555     */ 
556    protected abstract IndexResult _deleteIndex(String id) throws SearchException;
557    
558        @Override
559        public final Resource getPath() {
560                return path;
561        }
562
563    @Override
564    public DateTime getCreated() {
565        return created;
566    }
567    
568        @Override
569        public final DateTime getLastUpdate() {
570                return lastUpdate;
571        }
572
573        @Override
574        public final String getName() {
575                return name;
576        } 
577        
578    @Override
579    public final Log getLogger() {
580        return log;
581    }
582    
583    @Override
584    public final SearchEngine getSearchEngine() {
585        return searchEngine;
586    }
587
588    /**
589     * change the last update attribute and store it
590     * @throws SearchException
591     */
592    private void changeLastUpdate() throws SearchException {
593        lastUpdate=new DateTimeImpl();
594        searchEngine.store();
595    }
596    
597    @Override
598    public Object created() {
599        return created;
600    }
601
602    @Override
603    public final int search(SearchData data, Query qry,String criteria, String language, short type,int startrow,int maxrow,String categoryTree, String[] categories) throws SearchException, PageException {
604        int len=qry.getRecordcount();
605        SearchResulItem[] records;
606        
607        AddionalAttrs aa = AddionalAttrs.getAddionlAttrs();
608        boolean hasRowHandling=false;
609        aa.setStartrow(startrow);
610        if(maxrow!=-1)aa.setMaxrows(maxrow-len);
611        
612        Lock l = lock();
613        try {
614                records = _search(data, criteria,language,type,categoryTree,categories);
615        }
616        finally {
617                unlock(l);
618                if(hasRowHandling=aa.hasRowHandling())
619                        startrow = aa.getStartrow();
620                
621        }
622        
623        
624        
625        // Startrow
626        if(!hasRowHandling && startrow>1) {
627            
628                if(startrow>records.length) {
629                return startrow-records.length;
630            }
631            int start=startrow-1;
632            
633            SearchResulItem[] tmpRecords=new SearchResulItem[records.length-start];
634            for(int i=start;i<records.length;i++) {
635                tmpRecords[i-start]=records[i];
636            }
637            records=tmpRecords;
638            startrow=1;
639        }
640        
641        
642        if(!ArrayUtil.isEmpty(records)) {
643            
644            int to=(!hasRowHandling && maxrow>-1 && len+records.length>maxrow)?maxrow-len:records.length;
645            qry.addRow(to);
646            
647            String title;
648            String custom1;
649            String custom2;
650            String custom3;
651            String custom4;
652            String url;
653            SearchResulItem record;
654            SearchIndex si;
655            for(int y=0;y<to;y++) {
656                        
657                int row=len+y+1;
658                record = records[y];
659                si=indexes.get(record.getId());
660
661                title=record.getTitle();
662                custom1=record.getCustom1();
663                custom2=record.getCustom2();
664                custom3=record.getCustom3();
665                custom4=record.getCustom4();
666                url=record.getUrl();
667                
668                qry.setAt(KeyConstants._title,row,title);
669                qry.setAt(KeyConstants._custom1,row,custom1);
670                qry.setAt(KeyConstants._custom2,row,custom2);
671                qry.setAt(KeyConstants._custom3,row,custom3);
672                qry.setAt(KeyConstants._custom4,row,custom4);
673                qry.setAt("categoryTree",row,record.getCategoryTree());
674                qry.setAt(KeyConstants._category,row,record.getCategory());
675                qry.setAt(KeyConstants._type,row,record.getMimeType());
676                qry.setAt(KeyConstants._author,row,record.getAuthor());
677                qry.setAt(KeyConstants._size,row,record.getSize());
678
679                qry.setAt(KeyConstants._summary,row,record.getSummary());
680                qry.setAt(KeyConstants._context,row,record.getContextSummary());
681                qry.setAt(KeyConstants._score,row,new Float(record.getScore()));
682                qry.setAt(KeyConstants._key,row,record.getKey());
683                qry.setAt(KeyConstants._url,row,url);
684                qry.setAt(KeyConstants._collection,row,getName());
685                qry.setAt(KeyConstants._rank,row,new Double(row));
686                String rootPath,file;
687                String urlPath;
688                if(si!=null) {
689                        switch(si.getType()){
690                        case SearchIndex.TYPE_PATH:
691                                rootPath = si.getKey();
692                                rootPath=rootPath.replace(FileUtil.FILE_ANTI_SEPERATOR,FileUtil.FILE_SEPERATOR);
693                                file=record.getKey();
694                                file=file.replace(FileUtil.FILE_ANTI_SEPERATOR,FileUtil.FILE_SEPERATOR);
695                                qry.setAt(KeyConstants._url,row,toURL(si.getUrlpath(),StringUtil.replace(file, rootPath, "", true)));
696                                
697                                
698                        break;
699                        case SearchIndex.TYPE_URL:
700                                rootPath = si.getKey();
701                                urlPath = si.getUrlpath();
702                                try {
703                                        rootPath = getDirectory(si.getKey());
704                                                } 
705                                catch (MalformedURLException e) {}
706                                if(StringUtil.isEmpty(urlPath))urlPath=rootPath;
707                                file=record.getKey();
708                                qry.setAt(KeyConstants._url,row,toURL(urlPath,StringUtil.replace(file, rootPath, "", true)));
709                                
710                                
711                        break;
712                        case SearchIndex.TYPE_CUSTOM:
713                                qry.setAt(KeyConstants._url,row,url);
714                        break;
715                        default:
716                                qry.setAt(KeyConstants._url,row,toURL(si.getUrlpath(),url));
717                        break;
718                        }
719                        
720                        
721                    if(StringUtil.isEmpty(title))      qry.setAt(KeyConstants._title,row,si.getTitle());
722                    if(StringUtil.isEmpty(custom1))    qry.setAt(KeyConstants._custom1,row,si.getCustom1());
723                    if(StringUtil.isEmpty(custom2))    qry.setAt(KeyConstants._custom2,row,si.getCustom2());
724                    if(StringUtil.isEmpty(custom3))    qry.setAt(KeyConstants._custom3,row,si.getCustom3());
725                    if(StringUtil.isEmpty(custom4))    qry.setAt(KeyConstants._custom4,row,si.getCustom4());
726                    
727                }
728            }
729        }
730        return startrow;
731    }
732
733    public static String getDirectory(String strUrl) throws MalformedURLException {
734        URL url = new URL(strUrl);
735        String path=url.getPath(); 
736        int slashIndex = path.lastIndexOf('/');
737        int dotIndex = path.lastIndexOf('.');
738        // no dot
739        if(dotIndex==-1){
740                if(path.endsWith("/"))return HTTPUtil.removeRef(url).toExternalForm();
741                return HTTPUtil.removeRef(new URL(
742                                url.getProtocol(),
743                                url.getHost(),
744                                url.getPort(),path+"/")).toExternalForm();
745        }
746        if(slashIndex>dotIndex){
747                path=path.substring(0,dotIndex);
748                slashIndex = path.lastIndexOf('/');
749        }
750        
751        return HTTPUtil.removeRef(new URL(
752                                url.getProtocol(),
753                                url.getHost(),
754                                url.getPort(),path.substring(0,slashIndex+1))).toExternalForm();
755        }
756
757        private static String toURL(String url, String path) {
758        if(StringUtil.isEmpty(url)) return path;
759        if(StringUtil.isEmpty(path)) return url;
760        
761        url=url.replace('\\','/');
762        path=path.replace('\\','/');
763        if(StringUtil.startsWith(path, '/'))path=path.substring(1);
764        if(StringUtil.endsWith(url, '/'))url=url.substring(0,url.length()-1);
765        
766        if(StringUtil.startsWithIgnoreCase(path, url))
767                return path;
768        return url+"/"+path;
769    }
770    
771        
772        
773        
774
775    protected SearchIndex[] getIndexes() {
776        Iterator<Entry<String, SearchIndex>> it = indexes.entrySet().iterator();
777                int len=indexes.size();
778                SearchIndex[] rtn=new SearchIndex[len];
779                int count=0;
780                while(it.hasNext()) {
781                        rtn[count++]=it.next().getValue();
782                }
783                return rtn;
784        }
785
786
787        private Lock lock() throws SearchException {
788                try {
789                        return lock.lock(getId(),LOCK_TIMEOUT);
790                        //manager.lock(LockManager.TYPE_EXCLUSIVE,getId(),LOCK_TIMEOUT,ThreadLocalPageContext.get().getId());
791                } 
792                catch (Exception e) {
793                        throw new SearchException(e);
794                }
795                
796        }
797
798        private void unlock(Lock l) {
799                lock.unlock(l);
800                //manager.unlock(ThreadLocalPageContext.get().getId());
801        }
802
803
804        private String getId() {
805                return path.getRealResource(name).getAbsolutePath();
806        }
807
808        // FUTURE
809        public Object getIndexesAsQuery() {
810                Iterator<Entry<String, SearchIndex>> it = indexes.entrySet().iterator();
811                
812                final String v="VARCHAR";
813        Query query=null;
814        String[] cols = new String[]{
815                        "categories","categoryTree","custom1","custom2","custom3","custom4","extensions",
816                        "key","language","query","title","urlpath","type"};
817        String[] types = new String[]{
818                        v,v,v,v,v,v,v,
819                        v,v,v,v,v,v};
820        try {
821            query=new QueryImpl(cols,types, 0,"query");
822        } catch (DatabaseException e) {
823            query=new QueryImpl(cols, 0,"query");
824        }
825        
826        Entry<String, SearchIndex> entry;
827        SearchIndex index;
828        int row=0;
829                while(it.hasNext()) {
830                        query.addRow();
831                        row++;
832                entry = it.next();
833                index= entry.getValue();
834                if(index==null)continue;
835                try {
836                        
837                query.setAt("categories",row,ListUtil.arrayToList(index.getCategories(),""));
838                query.setAt("categoryTree",row,index.getCategoryTree());
839                
840                query.setAt(KeyConstants._custom1,row,index.getCustom1());
841                query.setAt(KeyConstants._custom2,row,index.getCustom2());
842                query.setAt(KeyConstants._custom3,row,index.getCustom3());
843                query.setAt(KeyConstants._custom4,row,index.getCustom4());
844                
845                query.setAt(KeyConstants._extensions,row,ListUtil.arrayToList(index.getExtensions(),","));
846                query.setAt(KeyConstants._key,row,index.getKey());
847                query.setAt(KeyConstants._language,row,index.getLanguage());
848                query.setAt(KeyConstants._query,row,index.getQuery());
849                query.setAt(KeyConstants._title,row,index.getTitle());
850                query.setAt(KeyConstants._urlpath,row,index.getUrlpath());
851                query.setAt(KeyConstants._type,row,SearchIndex.toStringTypeEL(index.getType()));
852                
853                }
854                    catch(PageException pe) {}
855            }
856                return query;
857        }
858
859}