001    package railo.runtime.tag;
002    
003    import java.util.Iterator;
004    import java.util.Map;
005    
006    import railo.commons.lang.StringUtil;
007    import railo.runtime.exp.ApplicationException;
008    import railo.runtime.exp.PageException;
009    import railo.runtime.ext.tag.TagImpl;
010    import railo.runtime.op.Caster;
011    import railo.runtime.op.Decision;
012    import railo.runtime.search.AddionalAttrs;
013    import railo.runtime.search.SearchCollection;
014    import railo.runtime.search.SearchData;
015    import railo.runtime.search.SearchDataImpl;
016    import railo.runtime.search.SearchEngine;
017    import railo.runtime.search.SearchException;
018    import railo.runtime.search.SuggestionItem;
019    import railo.runtime.tag.util.DeprecatedUtil;
020    import railo.runtime.type.KeyImpl;
021    import railo.runtime.type.List;
022    import railo.runtime.type.QueryImpl;
023    import railo.runtime.type.Struct;
024    import railo.runtime.type.StructImpl;
025    
026    public final class Search extends TagImpl {
027    
028            private static final String[] EMPTY = new String[0];
029    
030            private static final int SUGGESTIONS_ALWAYS = Integer.MAX_VALUE;
031            private static final int SUGGESTIONS_NEVER = -1;
032    
033            private static final railo.runtime.type.Collection.Key FOUND = KeyImpl.intern("found");
034            private static final railo.runtime.type.Collection.Key SEARCHED = KeyImpl.intern("searched");
035            private static final railo.runtime.type.Collection.Key KEYWORDS = KeyImpl.intern("keywords");
036            private static final railo.runtime.type.Collection.Key KEYWORD_SCORE = KeyImpl.intern("keywordScore");
037            
038            /** Specifies the criteria type for the search. */
039            private short type=SearchCollection.SEARCH_TYPE_SIMPLE;
040    
041            /** Specifies the maximum number of entries for index queries. If omitted, all rows are returned. */
042            private int maxrows=-1;
043    
044            /** Specifies the criteria for the search following the syntactic rules specified by type. */
045            private String criteria="";
046    
047            /** Specifies the first row number to be retrieved. Default is 1. */
048            private int startrow=1;
049    
050            /** The logical collection name that is the target of the search operation or an external collection 
051            **              with fully qualified path. */
052            private SearchCollection[] collections;
053    
054            /** A name for the search query. */
055            private String name;
056    
057            private String[] category=EMPTY;
058            private String categoryTree="";
059            private String status;
060            private int suggestions=SUGGESTIONS_NEVER;
061            private int contextPassages=0;
062            private int contextBytes=300;
063            private String contextHighlightBegin="<b>";
064            private String contextHighlightEnd="</b>";
065            private String previousCriteria;
066    
067            //private int spellCheckMaxLevel=10;
068            //private String result=null;
069            
070            /**
071            * @see javax.servlet.jsp.tagext.Tag#release()
072            */
073            public void release()   {
074                    super.release();
075                    type=SearchCollection.SEARCH_TYPE_SIMPLE;
076                    maxrows=-1;
077                    criteria="";
078                    startrow=1;
079                    collections=null;
080                    
081                    category=EMPTY;
082                    categoryTree="";
083                    status=null;
084                    suggestions=SUGGESTIONS_NEVER;
085                    contextPassages=0;
086                    contextBytes=300;
087                    contextHighlightBegin="<b>";
088                    contextHighlightEnd="</b>";
089                    previousCriteria=null;
090                    
091    
092                    //spellCheckMaxLevel=10;
093                    //result=null;
094                    
095            }
096    
097            /** set the value type
098            *  Specifies the criteria type for the search.
099            * @param type value to set
100             * @throws ApplicationException
101            **/
102            public void setType(String type) throws ApplicationException    {
103                    if(type==null) return;
104                type=type.toLowerCase().trim();
105                if(type.equals("simple"))this.type=SearchCollection.SEARCH_TYPE_SIMPLE;
106                else if(type.equals("explicit"))this.type=SearchCollection.SEARCH_TYPE_EXPLICIT;
107                else 
108                    throw new ApplicationException("attribute type of tag search has a invalid value, valid values are [simple,explicit] now is ["+type+"]");
109    
110            }
111    
112            /** set the value maxrows
113            *  Specifies the maximum number of entries for index queries. If omitted, all rows are returned.
114            * @param maxrows value to set
115            **/
116            public void setMaxrows(double maxrows)  {
117                    this.maxrows=(int) maxrows;
118            }
119    
120            /** set the value criteria
121            *  Specifies the criteria for the search following the syntactic rules specified by type.
122            * @param criteria value to set
123            **/
124            public void setCriteria(String criteria)        {
125                    this.criteria=criteria;
126            }
127    
128            /** set the value startrow
129            *  Specifies the first row number to be retrieved. Default is 1.
130            * @param startrow value to set
131            **/
132            public void setStartrow(double startrow)        {
133                    this.startrow=(int) startrow;
134            }
135    
136            /** set the value collection
137            *  The logical collection name that is the target of the search operation or an external collection 
138            *               with fully qualified path.
139            * @param collection value to set
140             * @throws PageException
141            **/
142            public void setCollection(String collection) throws PageException       {
143                    String[] collNames=List.toStringArrayTrim(List.listToArrayRemoveEmpty(collection,','));
144                collections=new SearchCollection[collNames.length];
145                SearchEngine se = pageContext.getConfig().getSearchEngine();
146                try { 
147                        for(int i=0;i<collections.length;i++) {
148                            collections[i]=se.getCollectionByName(collNames[i]);
149                        }
150                } catch (SearchException e) {
151                    collections=null;
152                    throw Caster.toPageException(e);
153                }
154            }
155    
156            /** set the value language
157            * @param language value to set
158            **/
159            public void setLanguage(String language)        {
160                    DeprecatedUtil.tagAttribute(pageContext,"Search", "language");
161            }
162    
163            /** set the value external
164            * @param external value to set
165             * @throws ApplicationException
166            **/
167            public void setExternal(boolean external) throws ApplicationException   {
168                    DeprecatedUtil.tagAttribute(pageContext,"Search", "external");
169            }
170    
171            /** set the value name
172            *  A name for the search query.
173            * @param name value to set
174            **/
175            public void setName(String name)        {
176                    this.name=name;
177            }  
178            
179            
180    
181            /**
182             * @param category the category to set
183             * @throws ApplicationException 
184             */
185            public void setCategory(String listCategories)  {
186                    if(StringUtil.isEmpty(listCategories)) return;
187                    this.category = List.trimItems(List.listToStringArray(listCategories, ','));
188            }
189    
190    
191            /**
192             * @param categoryTree the categoryTree to set
193             * @throws ApplicationException 
194             */
195            public void setCategorytree(String categoryTree) {
196                    if(StringUtil.isEmpty(categoryTree)) return;
197                    categoryTree=categoryTree.replace('\\', '/').trim();
198                    if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1);
199                    if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/";
200                    this.categoryTree = categoryTree;
201            }
202    
203            /**
204             * @param contextBytes the contextBytes to set
205             * @throws ApplicationException 
206             */
207            public void setContextbytes(double contextBytes) throws ApplicationException {
208                    this.contextBytes = (int)contextBytes;
209            }
210    
211            /**
212             * @param contextHighlightBegin the contextHighlightBegin to set
213             * @throws ApplicationException 
214             */
215            public void setContexthighlightbegin(String contextHighlightBegin) {
216                    this.contextHighlightBegin = contextHighlightBegin;
217            }
218    
219            /**
220             * @param contextHighlightEnd the contextHighlightEnd to set
221             * @throws ApplicationException 
222             */
223            public void setContexthighlightend(String contextHighlightEnd) {
224                    this.contextHighlightEnd = contextHighlightEnd;
225            }
226    
227            /**
228             * @param contextPassages the contextPassages to set
229             * @throws ApplicationException 
230             */
231            public void setContextpassages(double contextPassages) throws ApplicationException {
232                    this.contextPassages = (int)contextPassages;
233            }
234            
235            
236            
237    
238            /**
239             * @param previousCriteria the previousCriteria to set
240             * @throws ApplicationException 
241             */
242            public void setPreviouscriteria(String previousCriteria) throws ApplicationException {
243                    this.previousCriteria = previousCriteria;
244                    throw new ApplicationException("attribute previousCriteria for tag search is not supported yet");
245                    // TODO impl tag attribute
246            }
247    
248            /**
249             * @param status the status to set
250             * @throws ApplicationException 
251             */
252            public void setStatus(String status) {
253                    if(!StringUtil.isEmpty(status))this.status = status;
254            }
255    
256            /**
257             * @param suggestions the suggestions to set
258             * @throws ApplicationException 
259             */
260            public void setSuggestions(String suggestions) throws PageException {
261                    if(StringUtil.isEmpty(suggestions))return;
262                    suggestions = suggestions.trim().toLowerCase();
263                    if("always".equals(suggestions)) this.suggestions=SUGGESTIONS_ALWAYS;
264                    else if("never".equals(suggestions)) this.suggestions=SUGGESTIONS_NEVER;
265                    else if(Decision.isNumeric(suggestions)) {
266                            this.suggestions=Caster.toIntValue(suggestions);
267                    }
268                    else    
269                            throw new ApplicationException("attribute suggestions has a invalid value ["+suggestions+"], valid values are [always,never,<positive numeric value>]");
270                    
271                    
272            }
273    
274            /**
275             * @see javax.servlet.jsp.tagext.Tag#doStartTag()
276            */
277            public int doStartTag() throws PageException    {
278                //SerialNumber sn = pageContext.getConfig().getSerialNumber();
279                //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY)
280                //    throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo");
281            final String v="VARCHAR",d="DOUBLE";
282            String[] cols = new String[]{"title","url","summary","score","recordssearched","key","custom1","custom2","custom3","custom4",
283                                                                    "categoryTree","category","context","size","rank","author","type","collection"};
284            
285            // TODO support context
286            String[] types = new String[]{v,v,v,d,d,v,v,v,v,v,v,v,v,d,d,v,v,v};
287            SearchData data=new SearchDataImpl(suggestions);
288            SuggestionItem item=null;// this is already here to make sure the classloader load this sinstance 
289            
290            
291            railo.runtime.type.Query qry=new QueryImpl(cols,types,0,"query");
292                    
293                SearchCollection collection;
294                long time=System.currentTimeMillis();
295                AddionalAttrs.setAddionalAttrs(contextBytes,contextPassages,contextHighlightBegin,contextHighlightEnd);
296                try {
297                        for(int i=0;i<collections.length;i++) {
298                            collection=collections[i];
299                            startrow=collection.search(data,qry,criteria,collection.getLanguage(),type,startrow,maxrows,categoryTree,category); 
300                            
301                            if(maxrows>=0 && qry.getRecordcount()>=maxrows) break;
302                        }
303                        pageContext.setVariable(name,qry);
304                }
305                catch(SearchException se) {
306                    throw Caster.toPageException(se);
307                }
308                finally{
309                    AddionalAttrs.removeAddionalAttrs();
310                }
311                
312                time=System.currentTimeMillis()-time;
313                Double recSearched=new Double(data.getRecordsSearched());
314                int len=qry.getRecordcount();
315                for(int i=1;i<=len;i++) {
316                    qry.setAt("recordssearched",i,recSearched);
317                }
318                
319                // status
320                if(status!=null) {
321                    Struct sct=new StructImpl();
322                    pageContext.setVariable(status, sct);
323                    sct.set(FOUND, new Double(qry.getRecordcount()));
324                    sct.set(SEARCHED, recSearched);
325                    sct.set(KeyImpl.TIME, new Double(time));
326                    
327                    // TODO impl this values
328                    
329                    Map s = data.getSuggestion();
330                    if(s.size()>0) {
331                            String key;
332                            
333                            Iterator it = s.keySet().iterator();
334                            Struct keywords=new StructImpl();
335                            Struct keywordScore=new StructImpl();
336                            sct.set(KEYWORDS, keywords);
337                            sct.set(KEYWORD_SCORE, keywordScore);
338                            Object obj;
339                            
340                            while(it.hasNext()) {
341                                    key=(String) it.next();
342                                    // FUTURE add SuggestionItem as interface to public interface
343                                    
344                                    // the problem is a conflict between the SuggestionItem version from core and extension
345                                    obj=s.get(key);
346                                    if(obj instanceof SuggestionItem){
347                                            item=(SuggestionItem)obj;
348                                            keywords.set(key, item.getKeywords());
349                                            keywordScore.set(key, item.getKeywordScore());
350                                    }
351                                    else {
352                                            Class clazz = obj.getClass();
353                                            try {
354                                                            keywords.set(key, clazz.getMethod("getKeywords", new Class[0]).invoke(obj, new Object[0]));
355                                                            keywordScore.set(key, clazz.getMethod("getKeywordScore", new Class[0]).invoke(obj, new Object[0]));
356                                                    } 
357                                            catch (Exception e) {}
358                                    }
359                                    
360                                    
361                                    
362                            }
363                            
364                            
365                            String query = data.getSuggestionQuery();
366                            if(query!=null) {
367                                    String html = StringUtil.replace(query, "<suggestion>", "<b>", false);
368                                    html = StringUtil.replace(html, "</suggestion>", "</b>", false);
369                                    sct.set("suggestedQueryHTML", html);
370                                    
371                                    String plain = StringUtil.replace(query, "<suggestion>", "", false);
372                                    plain = StringUtil.replace(plain, "</suggestion>", "", false);
373                                    sct.set("suggestedQuery", plain);
374                            }
375                            
376                            
377                    }
378                    
379                    //if(suggestions!=SUGGESTIONS_NEVER)sct.set("suggestedQuery", "");
380                    //sct.set("keywords", "");
381                    //sct.set("keywordScore", "");
382                    
383                    
384                }
385                
386                
387                    return SKIP_BODY;
388            }
389    
390            /**
391            * @see javax.servlet.jsp.tagext.Tag#doEndTag()
392            */
393            public int doEndTag()   {
394                    return EVAL_PAGE;
395            }
396    
397            
398    }