001    package railo.runtime.tag;
002    
003    import java.io.IOException;
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    
007    import railo.commons.io.res.Resource;
008    import railo.commons.io.res.util.ResourceUtil;
009    import railo.commons.lang.StringUtil;
010    import railo.runtime.exp.ApplicationException;
011    import railo.runtime.exp.ExpressionException;
012    import railo.runtime.exp.PageException;
013    import railo.runtime.ext.tag.TagImpl;
014    import railo.runtime.op.Caster;
015    import railo.runtime.search.IndexResult;
016    import railo.runtime.search.SearchCollection;
017    import railo.runtime.search.SearchCollectionSupport;
018    import railo.runtime.search.SearchException;
019    import railo.runtime.search.SearchIndex;
020    import railo.runtime.search.lucene2.LuceneSearchCollection;
021    import railo.runtime.tag.util.DeprecatedUtil;
022    import railo.runtime.type.Struct;
023    import railo.runtime.type.StructImpl;
024    import railo.runtime.type.util.ListUtil;
025    
026    /**
027    * Populates collections with indexed data.
028    **/
029    public final class Index extends TagImpl {
030        
031    
032        private static final String[] EMPTY = new String[0];
033    
034    
035            public static String[] EXTENSIONS=new String[]{"htm","html","cfm","cfml","dbm","dbml"};
036        
037    
038            /** Specifies the index action. */
039            private String action;
040    
041            /** Specifies the URL path for files if type = "file" and type = "path". When the collection is 
042            **      searched with cfsearch, the pathname is automatically be prepended to filenames and returned as 
043            **      the url attribute. */
044            private String urlpath;
045    
046            /** Specifies the type of entity being indexed. Default is CUSTOM. */
047            private short type=-1;
048    
049            /** Title for collection;
050            ** Query column name for type and a valid query name;
051            ** Permits searching collections by title or displaying a separate title from the key */
052            private String title;
053    
054            private String language;
055    
056    
057            /** Specifies the comma-separated list of file extensions that CFML uses to index files if 
058            **      type = "Path". Default is HTM, HTML, CFM, CFML, DBM, DBML.
059            **      An entry of "*." returns files with no extension */
060            private String[] extensions=EXTENSIONS;
061    
062            /**  */
063            private String key;
064    
065        /** A custom field you can use to store data during an indexing operation. Specify a query column 
066        **  name for type and a query name. */
067            private String custom1;
068            private long timeout=10000;
069    
070        /** A custom field you can use to store data during an indexing operation. Usage is the same as 
071        **  for custom1. */
072        private String custom2;
073    
074        /** A custom field you can use to store data during an indexing operation. Usage is the same as 
075        **  for custom1. */
076        private String custom3;
077    
078        /** A custom field you can use to store data during an indexing operation. Usage is the same as 
079        **  for custom1. */
080        private String custom4;
081    
082            /** Specifies the name of the query against which the collection is generated. */
083            private String query;
084    
085            /** Specifies a collection name. If you are indexing an external collection external = "Yes", 
086            **      specify the collection name, including fully qualified path. */
087            private SearchCollection collection;
088    
089            /** Yes or No. Yes specifies, if type = "Path", that directories below the path specified in 
090            **      key are included in the indexing operation. */
091            private boolean recurse;
092    
093            /**  */
094            private String body;
095            private String name;
096            
097    
098            private String[] category=EMPTY;
099            private String categoryTree="";
100            private String status;
101            private String prefix;
102    
103    
104            private boolean throwontimeout=false;
105    
106            @Override
107            public void release()   {
108                    super.release();
109                    action=null;
110                    urlpath=null;
111                    type=-1;
112                    title=null;
113                    language=null;
114                    extensions=EXTENSIONS;
115                    key=null;
116                    
117                    custom1=null;
118                    custom2=null;
119                    custom3=null;
120                    custom4=null;
121                    
122                    query=null;
123                    collection=null;
124                    recurse=false;
125                    body=null;
126                    name=null;
127                    
128                    category=EMPTY;
129                    categoryTree="";
130                    status=null;
131                    prefix=null;
132                    timeout=10000;
133                    throwontimeout=false;
134            }
135    
136    
137            /** set the value action
138            *  Specifies the index action.
139            * @param action value to set
140            **/
141            public void setAction(String action)    {
142                    this.action=action.toLowerCase().trim();
143            }
144    
145            /** set the value urlpath
146            *  Specifies the URL path for files if type = "file" and type = "path". When the collection is 
147            *       searched with cfsearch, the pathname is automatically be prepended to filenames and returned as 
148            *       the url attribute.
149            * @param urlpath value to set
150            **/
151            public void setUrlpath(String urlpath)  {
152                    if(StringUtil.isEmpty(urlpath))return;
153                    this.urlpath=urlpath.toLowerCase().trim();
154            }
155    
156            /** set the value type
157            *  Specifies the type of entity being indexed. Default is CUSTOM.
158            * @param type value to set
159             * @throws PageException
160            **/
161            public void setType(String type) throws PageException   {
162                    if(type==null)return;
163                try {
164                this.type=SearchIndex.toType(type);
165            } 
166                catch (SearchException e) {
167                throw Caster.toPageException(e);
168            }
169            }
170            
171            /**
172             * @param timeout the timeout in seconds
173             * @throws ApplicationException 
174             */
175            public void setTimeout(double timeout) throws ApplicationException {
176                    
177                    this.timeout = (long)(timeout*1000D);
178                    if(this.timeout<0)
179                            throw new ApplicationException("attribute timeout must contain a positive number");
180                    if(timeout==0)timeout=1;
181            }
182            
183            /** set the value throwontimeout
184            *  Yes or No. Specifies how   timeout conditions are handled. If the value is Yes, an exception is 
185            *       generated to provide notification of the timeout. If the value is No, execution continues. Default is Yes.
186            * @param throwontimeout value to set
187            **/
188            public void setThrowontimeout(boolean throwontimeout) {
189                    this.throwontimeout = throwontimeout;
190            }
191    
192            
193            
194            public void setName(String name){
195                    this.name=name;
196            }
197    
198            /** set the value title
199            *  Title for collection;
200            * Query column name for type and a valid query name;
201            * Permits searching collections by title or displaying a separate title from the key
202            * @param title value to set
203            **/
204            public void setTitle(String title)      {
205                    this.title=title;
206            }
207    
208            /** set the value custom1
209            *  A custom field you can use to store data during an indexing operation. Specify a query column 
210            *       name for type and a query name.
211            * @param custom1 value to set
212            **/
213            public void setCustom1(String custom1)  {
214                    this.custom1=custom1;
215            }
216    
217            /** set the value language
218            * @param language value to set
219            **/
220            public void setLanguage(String language)        {
221                    if(StringUtil.isEmpty(language)) return;
222                    this.language=Collection.validateLanguage(language);
223            }
224    
225            /** set the value external
226            * @param external value to set
227             * @throws ApplicationException
228            **/
229            public void setExternal(boolean external) {
230                    DeprecatedUtil.tagAttribute(pageContext,"Index", "external");
231            }
232    
233            /** set the value extensions
234            * @param extensions value to set
235             * @throws PageException
236            **/
237            public void setExtensions(String extensions) throws PageException       {
238                    if(extensions==null) return;
239                    this.extensions=ListUtil.toStringArrayTrim(ListUtil.listToArray(extensions,','));
240            }
241    
242            /** set the value key
243            *  
244            * @param key value to set
245            **/
246            public void setKey(String key)  {
247                    this.key=key;
248            }
249    
250            /** set the value custom2
251            *  A custom field you can use to store data during an indexing operation. Usage is the same as 
252            *       for custom1.
253            * @param custom2 value to set
254            **/
255            public void setCustom2(String custom2)  {
256                    this.custom2=custom2;
257            }
258        
259        /**
260         * @param custom3 The custom3 to set.
261         */
262        public void setCustom3(String custom3) {
263            this.custom3 = custom3;
264        }
265    
266    
267        /**
268         * @param custom4 The custom4 to set.
269         */
270        public void setCustom4(String custom4) {
271            this.custom4 = custom4;
272        }
273    
274            /** set the value query
275            *  Specifies the name of the query against which the collection is generated.
276            * @param query value to set
277            **/
278            public void setQuery(String query)      {
279                    this.query=query;
280            }
281    
282            /** set the value collection
283            *  Specifies a collection name. If you are indexing an external collection external = "Yes", 
284            *       specify the collection name, including fully qualified path.
285            * @param collection value to set
286             * @throws PageException
287            **/
288            public void setCollection(String collection) throws PageException       {
289                    try {
290                        this.collection=pageContext.getConfig().getSearchEngine().getCollectionByName(collection.toLowerCase().trim());
291                }
292                    catch (SearchException e) {
293                throw Caster.toPageException(e);
294            }
295                      
296            }
297    
298            /** set the value recurse
299            *  Yes or No. Yes specifies, if type = "Path", that directories below the path specified in 
300            *       key are included in the indexing operation.
301            * @param recurse value to set
302            **/
303            public void setRecurse(boolean recurse) {
304                    this.recurse=recurse;
305            }
306    
307            /** set the value body
308            *  
309            * @param body value to set
310            **/
311            public void setBody(String body)        {
312                    this.body=body;
313            }
314            
315            /**
316             * @param category the category to set
317             * @throws ApplicationException 
318             */
319            public void setCategory(String listCategories)  {
320                    if(listCategories==null) return;
321                    this.category = ListUtil.trimItems(ListUtil.listToStringArray(listCategories, ','));
322            }
323    
324    
325            /**
326             * @param categoryTree the categoryTree to set
327             * @throws ApplicationException 
328             */
329            public void setCategorytree(String categoryTree) {
330                    if(categoryTree==null) return;
331                    categoryTree=categoryTree.replace('\\', '/').trim();
332                    if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1);
333                    if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/";
334                    this.categoryTree = categoryTree;
335            }
336    
337    
338            /**
339             * @param prefix the prefix to set
340             */
341            public void setPrefix(String prefix) {
342                    this.prefix = prefix;
343            }
344    
345    
346            /**
347             * @param status the status to set
348             * @throws ApplicationException 
349             */
350            public void setStatus(String status) {
351                    this.status = status;
352            }
353    
354    
355            @Override
356            public int doStartTag() throws PageException    {
357                // SerialNumber sn = pageContext.getConfig().getSerialNumber();
358                //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY)
359                //    throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo");
360                
361                try {
362                        if(action.equals("purge")) doPurge();
363                        else if(action.equals("update")) doUpdate();
364                            else if(action.equals("delete")) doDelete();
365                            else if(action.equals("refresh")) doRefresh();
366                            else if(action.equals("list")) doList();
367    
368                            else throw new ApplicationException("invalid action name [" + action + "]","valid action names are [list,update, delete, purge, refresh]");
369                    } catch (Exception e) {
370                            throw Caster.toPageException(e);
371                    } 
372                    return SKIP_BODY;
373            }
374    
375            
376            /**
377             * @throws PageException
378             * @throws SearchException
379             * @throws IOException
380         * 
381         */
382        private void doRefresh() throws PageException, SearchException, IOException {
383            doPurge();
384            doUpdate();
385        }
386        
387        private void doList() throws ApplicationException, PageException {
388                    required("index",action,"name",name);
389                    pageContext.setVariable(name,((SearchCollectionSupport)collection).getIndexesAsQuery());
390            }
391    
392    
393        /**
394         * delete a collection
395             * @throws PageException
396             * @throws SearchException
397         */
398        private void doDelete() throws PageException, SearchException {
399            required("index",action,"collection",collection);
400            if(type!=SearchIndex.TYPE_CUSTOM)required("index",action,"key",key);
401            
402            // no type defined
403            if(type==-1) {
404                if(query!=null) {
405                    type=SearchIndex.TYPE_CUSTOM;
406                }
407                else {
408                    Resource file=null;
409                    try {
410                        file=ResourceUtil.toResourceExisting(pageContext,key);
411                        pageContext.getConfig().getSecurityManager().checkFileLocation(file);
412                    } 
413                    catch (ExpressionException e) {}
414    
415                    
416                    if(file!=null && file.exists() && file.isFile()) type=SearchIndex.TYPE_FILE;
417                    else if(file!=null && file.exists() && file.isDirectory()) type=SearchIndex.TYPE_PATH;
418                    else {
419                        try {
420                            new URL(key);
421                            type=SearchIndex.TYPE_URL;
422                        } catch (MalformedURLException e) {}
423                    }
424                }
425            }
426            
427            collection.deleteIndex(pageContext,key,type,query);   
428        }
429    
430        /**
431         * purge a collection
432         * @throws PageException
433         * @throws SearchException
434         */
435        private void doPurge() throws PageException, SearchException {
436            required("index",action,"collection",collection);
437            collection.purge();
438        }
439    
440        /**
441         * update a collection
442         * @throws PageException
443         * @throws SearchException
444         * @throws IOException
445         */
446        private void doUpdate() throws PageException, SearchException, IOException {
447            // check attributes
448            required("index",action,"collection",collection);
449            required("index",action,"key",key);
450            
451            if(type==-1) type=(query==null)?SearchIndex.TYPE_FILE:SearchIndex.TYPE_CUSTOM;
452            
453            if(type==SearchIndex.TYPE_CUSTOM) {
454                required("index",action,"body",body);
455                //required("index",action,"query",query);
456            }
457            IndexResult result;
458            
459            // FUTURE remove this condition
460            if(collection instanceof LuceneSearchCollection)
461                    result = ((LuceneSearchCollection)collection).index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,timeout,custom1,custom2,custom3,custom4);
462            else
463                    result = collection.index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,custom1,custom2,custom3,custom4);
464             if(!StringUtil.isEmpty(status))pageContext.setVariable(status,toStruct(result));
465        }
466    
467    
468            @Override
469            public int doEndTag()   {
470                    return EVAL_PAGE;
471            }
472    
473            private Struct toStruct(IndexResult result) {
474                    Struct sct=new StructImpl();
475                    sct.setEL("deleted",new Double(result.getCountDeleted()));
476                    sct.setEL("inserted",new Double(result.getCountInserted()));
477                    sct.setEL("updated",new Double(result.getCountUpdated()));
478                    return sct;
479            }
480    
481    }