001    package railo.runtime.search;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.io.OutputStream;
006    
007    import org.apache.xerces.parsers.DOMParser;
008    import org.apache.xml.serialize.OutputFormat;
009    import org.apache.xml.serialize.XMLSerializer;
010    import org.w3c.dom.Document;
011    import org.w3c.dom.Element;
012    import org.w3c.dom.Node;
013    import org.w3c.dom.NodeList;
014    import org.xml.sax.InputSource;
015    import org.xml.sax.SAXException;
016    
017    import railo.commons.io.IOUtil;
018    import railo.commons.io.log.LogAndSource;
019    import railo.commons.io.res.Resource;
020    import railo.commons.io.res.ResourceProvider;
021    import railo.commons.io.res.ResourcesImpl;
022    import railo.commons.lang.StringUtil;
023    import railo.runtime.Info;
024    import railo.runtime.config.Config;
025    import railo.runtime.engine.ThreadLocalPageContext;
026    import railo.runtime.exp.DatabaseException;
027    import railo.runtime.exp.PageException;
028    import railo.runtime.op.date.DateCaster;
029    import railo.runtime.type.Collection;
030    import railo.runtime.type.KeyImpl;
031    import railo.runtime.type.List;
032    import railo.runtime.type.Query;
033    import railo.runtime.type.QueryImpl;
034    import railo.runtime.type.Struct;
035    import railo.runtime.type.StructImpl;
036    import railo.runtime.type.dt.DateTime;
037    import railo.runtime.type.dt.DateTimeImpl;
038    
039    /**
040     * 
041     */
042    public abstract class SearchEngineSupport implements SearchEngine {
043        
044            private Resource searchFile;
045            private Resource searchDir;
046            private LogAndSource log;
047        private Document doc;
048        Struct collections=new StructImpl();
049            
050            /**
051             * @see railo.runtime.search.SearchEngine#init(railo.runtime.config.Config, railo.commons.io.res.Resource, railo.commons.io.log.LogAndSource)
052             */
053            public void init(railo.runtime.config.Config config,Resource searchDir, LogAndSource log) throws SAXException, IOException, SearchException {
054                    this.searchDir=searchDir;
055                    this.searchFile=searchDir.getRealResource("search.xml");
056                    if(!searchFile.exists()) createSearchFile(searchFile);
057                    
058                    DOMParser parser = new DOMParser();
059                    InputStream is=null;
060                try {
061                            is = IOUtil.toBufferedInputStream(searchFile.getInputStream());
062                    InputSource source = new InputSource(is);
063                    parser.parse(source);
064                }
065                finally {
066                    IOUtil.closeEL(is);
067                }
068            doc = parser.getDocument();
069                    
070            this.log=log;
071            
072            readCollections(config);
073            } 
074            
075            /**
076         * @see railo.runtime.search.SearchEngine#getCollectionByName(java.lang.String)
077         */
078            public final SearchCollection getCollectionByName(String name) throws SearchException {
079                    Object o=collections.get(name.toLowerCase(),null);
080                    if(o!=null)return (SearchCollection) o; 
081                    throw new SearchException("collection "+name+" is undefined");
082            }
083            
084            /**
085         * @see railo.runtime.search.SearchEngine#getCollectionsAsQuery()
086         */
087            public final Query getCollectionsAsQuery() {
088            final String v="VARCHAR";
089            Query query=null;
090            String[] cols = new String[]{"external","language","mapped","name","online","path","registered","lastmodified","categories","charset","created",
091                                                                     "size","doccount"};
092            String[] types = new String[]{"BOOLEAN",v,"BOOLEAN",v,"BOOLEAN",v,v,"DATE","BOOLEAN",v,"OBJECT","DOUBLE","DOUBLE"};
093            try {
094                query=new QueryImpl(cols,types, collections.size(),"query");
095            } catch (DatabaseException e) {
096                query=new QueryImpl(cols, collections.size(),"query");
097            }
098            
099            Collection.Key[] keys = collections.keys();
100                
101            for(int i=0;i<keys.length;i++) {
102                    try {
103                            SearchCollection coll = (SearchCollection) collections.get(keys[i]);
104                    query.setAt("external",i+1,Boolean.FALSE);
105                    query.setAt("charset",i+1,"UTF-8");
106                    query.setAt("created",i+1,coll.created());
107                    
108                    query.setAt("categories",i+1,Boolean.TRUE);
109                            query.setAt("language",i+1,coll.getLanguage());
110                            query.setAt("mapped",i+1,Boolean.FALSE);
111                            query.setAt("name",i+1,coll.getName());
112                            query.setAt("online",i+1,Boolean.TRUE);
113                            query.setAt("path",i+1,coll.getPath().getAbsolutePath());
114                            query.setAt("registered",i+1,"CF");
115                            query.setAt("lastmodified",i+1,coll.getLastUpdate());
116                            query.setAt("size",i+1,new Double(coll.getSize()));
117                            query.setAt("doccount",i+1,new Double(coll.getDocumentCount())); 
118                    }
119                        catch(PageException pe) {}
120                }
121                    return query;
122            }
123            
124            /**
125             *
126             * @see railo.runtime.search.SearchEngine#createCollection(java.lang.String, railo.commons.io.res.Resource, java.lang.String, boolean)
127             */
128            public final SearchCollection createCollection(String name,Resource path, String language, boolean allowOverwrite) throws SearchException {
129                SearchCollection coll = _createCollection(name,path,language);
130                coll.create();
131                addCollection(coll,allowOverwrite);
132                return coll;
133            }
134            
135            /**
136             * Creates a new Collection, will be invoked by createCollection
137             * @param name The Name of the Collection
138             * @param path the path to store
139             * @param language The language of the collection
140             * @return New SearchCollection
141             * @throws SearchException
142             */
143            protected abstract SearchCollection _createCollection(String name,Resource path, String language) throws SearchException;
144            
145            /**
146             * adds a new Collection to the storage
147             * @param collection
148             * @param allowOverwrite if allowOverwrite is false and a collection already exist -> throw Exception
149             * @throws SearchException
150             */
151            private final synchronized void addCollection(SearchCollection collection, boolean allowOverwrite) throws SearchException {
152                Object o = collections.get(collection.getName(),null);
153                if(!allowOverwrite && o!=null)
154                        throw new SearchException("there is already a collection with name "+collection.getName());
155                    collections.setEL(collection.getName(),collection);
156                    // update
157                    if(o!=null) {
158                        setAttributes(getCollectionElement(collection.getName()),collection);
159                    }
160                    // create
161                    else {
162                        doc.getDocumentElement().appendChild(toElement(collection));
163                    }
164                    store();
165            }
166    
167            /**
168             * removes a Collection from the storage
169             * @param collection Collection to remove
170             * @throws SearchException
171             */
172            protected final synchronized void removeCollection(SearchCollection collection)
173                            throws SearchException {
174                removeCollection(collection.getName());
175                _removeCollection(collection);
176            }
177        
178        /**
179         * removes a Collection from the storage
180         * @param collection Collection to remove
181         * @throws SearchException
182         */
183        protected abstract void _removeCollection(SearchCollection collection) throws SearchException;
184        
185        /**
186         * removes a Collection from the storage
187         * @param name Name of the Collection to remove
188         * @throws SearchException
189         */
190        private final synchronized void removeCollection(String name) throws SearchException {
191            try {
192                collections.remove(KeyImpl.init(name));
193                doc.getDocumentElement().removeChild(getCollectionElement(name));
194                store();
195            } 
196            catch (PageException e) {
197                throw new SearchException("can't remove collection "+name+", collection doesn't exist");
198            }
199        }
200        
201        
202        /**
203         * purge a Collection 
204         * @param collection Collection to purge
205         * @throws SearchException
206         */
207        protected final synchronized void purgeCollection(SearchCollection collection)
208                throws SearchException {
209            
210            purgeCollection(collection.getName());
211        }
212        
213        /**
214         * purge a Collection
215         * @param name Name of the Collection to purge
216         * @throws SearchException
217         */
218        private final synchronized void purgeCollection(String name) throws SearchException {
219            
220                //Map map=(Map)collections.get(name);
221                //if(map!=null)map.clear();
222                Element parent = getCollectionElement(name);
223                NodeList list=parent.getChildNodes();
224                int len=list.getLength();
225                for(int i=len-1;i>=0;i--) {
226                    parent.removeChild(list.item(i));
227                }
228                //doc.getDocumentElement().removeChild(getCollectionElement(name));
229                store();
230            
231        }
232    
233            /**
234         * @see railo.runtime.search.SearchEngine#getDirectory()
235         */
236            public Resource getDirectory() {
237                    return searchDir;
238            }
239    
240            /**
241         * @see railo.runtime.search.SearchEngine#getLogger()
242         */
243            public LogAndSource getLogger() {
244                    return log;
245            }
246    
247        /**
248         * return XML Element matching collection name
249         * @param name
250         * @return matching XML Element
251         */
252        protected final Element getCollectionElement(String name) {
253            Element root = doc.getDocumentElement();
254            NodeList children = root.getChildNodes();
255            int len=children.getLength();
256            for(int i=0;i<len;i++) {
257                Node n=children.item(i);
258                if(n instanceof Element && n.getNodeName().equals("collection")) {
259                    Element el = (Element)n;
260                    if(el.getAttribute("name").equalsIgnoreCase(name)) return el;
261                }
262            }
263            return null;
264        }
265    
266        /**
267         * @see railo.runtime.search.SearchEngine#getIndexElement(org.w3c.dom.Element, java.lang.String)
268         */
269        public Element getIndexElement(Element collElement, String id) {
270            
271            NodeList children = collElement.getChildNodes();
272            int len=children.getLength();
273            for(int i=0;i<len;i++) {
274                Node n=children.item(i);
275                if(n instanceof Element && n.getNodeName().equals("index")) {
276                    Element el = (Element)n;
277                    if(el.getAttribute("id").equals(id)) return el;
278                }
279            }
280            return null;
281        }
282    
283        /**
284         * translate a collection object to a XML Element
285         * @param coll Collection to translate
286         * @return XML Element
287         */
288        private final Element toElement(SearchCollection coll) {
289            Element el = doc.createElement("collection");
290            setAttributes(el,coll);   
291            return el;
292        }
293    
294        /**
295         * translate a collection object to a XML Element
296         * @param index Index to translate
297         * @return XML Element
298         * @throws SearchException
299         */
300        protected final Element toElement(SearchIndex index) throws SearchException {
301            Element el = doc.createElement("index");
302            setAttributes(el,index);   
303            return el;
304        }
305        
306        /**
307         * sets all attributes in XML Element from Search Collection
308         * @param el
309         * @param coll
310         */
311        private final void setAttributes(Element el,SearchCollection coll) {
312            if(el==null) return;
313            setAttribute(el,"language",coll.getLanguage());
314            setAttribute(el,"name",coll.getName());
315            
316            String value = coll.getLastUpdate().castToString(null);
317            if(value!=null)setAttribute(el,"lastUpdate",value);
318            value=coll.getCreated().castToString(null);
319            if(value!=null)setAttribute(el,"created",value);
320            
321            setAttribute(el,"path",coll.getPath().getAbsolutePath());
322        }
323        
324        /**
325         * sets all attributes in XML Element from Search Index
326         * @param el
327         * @param index
328         * @throws SearchException
329         */
330        protected final void setAttributes(Element el,SearchIndex index) throws SearchException {
331            if(el==null) return;
332            setAttribute(el,"categoryTree",index.getCategoryTree());
333            setAttribute(el,"category",List.arrayToList(index.getCategories(),","));
334            setAttribute(el,"custom1",index.getCustom1());
335            setAttribute(el,"custom2",index.getCustom2());
336            setAttribute(el,"custom3",index.getCustom3());
337            setAttribute(el,"custom4",index.getCustom4());
338            setAttribute(el,"id",index.getId());
339            setAttribute(el,"key",index.getKey());
340            setAttribute(el,"language",index.getLanguage());
341            setAttribute(el,"title",index.getTitle());
342            setAttribute(el,"extensions",List.arrayToList(index.getExtensions(),","));
343            setAttribute(el,"type",SearchIndex.toStringType(index.getType()));
344            setAttribute(el,"urlpath",index.getUrlpath());
345            setAttribute(el,"query",index.getQuery());
346        }
347    
348            /**
349             * helper method to set a attribute
350         * @param el
351         * @param name
352         * @param value
353         */
354        private void setAttribute(Element el, String name, String value) {
355            if(value!=null)el.setAttribute(name,value);
356        }
357    
358        /**
359             * read in collections
360         * @param config 
361             * @throws SearchException
362         */
363        private void readCollections(Config config) throws SearchException {
364            Element root = doc.getDocumentElement();
365            NodeList children = root.getChildNodes();
366            int len=children.getLength();
367            for(int i=0;i<len;i++) {
368                Node n=children.item(i);
369                if(n instanceof Element && n.getNodeName().equals("collection")) {
370                    readCollection(config,(Element)n);
371                }
372            }
373        }
374    
375        /**
376         * read in a single collection element
377         * @param config 
378         * @param el
379         * @throws SearchException
380         */
381        private final void readCollection(Config config, Element el) throws SearchException {
382            SearchCollectionPlus sc;
383            //try {
384                // Collection
385                DateTime last = DateCaster.toDateAdvanced(el.getAttribute("lastUpdate"),ThreadLocalPageContext.getTimeZone(config),null);
386                if(last==null)last=new DateTimeImpl();
387                DateTime cre = DateCaster.toDateAdvanced(el.getAttribute("created"),ThreadLocalPageContext.getTimeZone(config),null);
388                if(cre==null)cre=new DateTimeImpl();
389                ResourceProvider frp = ResourcesImpl.getFileResourceProvider();
390                sc =(SearchCollectionPlus) _readCollection(
391                        el.getAttribute("name"),
392                        frp.getResource(el.getAttribute("path")),
393                        el.getAttribute("language"),
394                        last,cre
395                );
396                collections.setEL(KeyImpl.init(sc.getName()),sc);
397                
398                // Indexes
399                NodeList children = el.getChildNodes();
400                int len=children.getLength();
401                for(int i=0;i<len;i++) {
402                    Node n=children.item(i);
403                    if(n instanceof Element && n.getNodeName().equals("index")) {
404                        readIndex(sc,(Element)n);
405                    }
406                }
407            /*} 
408            catch (PageException e) {
409                throw new SearchException(e);
410            }*/
411        }
412    
413        /**
414         * read in a single Index
415         * @param sc
416         * @param el
417         * @throws SearchException
418         * @throws PageException
419         */
420        protected void readIndex(SearchCollectionPlus sc, Element el) throws SearchException {
421                // Index
422                SearchIndex si=new SearchIndex(
423                        _attr(el,"id"),
424                        _attr(el,"title"),
425                        _attr(el,"key"),
426                        SearchIndex.toType(_attr(el,"type")),
427                        _attr(el,"query"),
428                        List.listToStringArray(_attr(el,"extensions"),','),
429                        _attr(el,"language"),
430                        _attr(el,"urlpath"),
431                        _attr(el,"categoryTree"),
432                        List.listToStringArray(_attr(el,"category"),','),
433                        _attr(el,"custom1"),
434                        _attr(el,"custom2"),
435                        _attr(el,"custom3"),
436                        _attr(el,"custom4"));
437               sc.addIndex(si);
438        }
439    
440      private String _attr(Element el, String attr) {
441            return StringUtil.emptyIfNull(el.getAttribute(attr));
442        }
443    
444          /**
445         * read in a existing collection
446         * @param name
447         * @param parh
448         * @param language
449         * @param count
450         * @param lastUpdate
451         * @param created 
452         * @return SearchCollection
453         * @throws SearchException
454         */
455        protected abstract SearchCollection _readCollection(String name, Resource parh, String language, DateTime lastUpdate, DateTime created) throws SearchException;
456    
457            /**
458         * store loaded data to xml file
459             * @throws SearchException
460         */
461        protected final synchronized void store() throws SearchException {
462            String[] keys=collections.keysAsString();
463            for(int i=0;i<keys.length;i++) {
464                Element collEl = getCollectionElement(keys[i]);
465                SearchCollection sc = getCollectionByName(keys[i]);
466                setAttributes(collEl,sc);  
467            }
468    
469            OutputFormat format = new OutputFormat(doc, null, true);
470                    format.setLineSeparator("\r\n");
471                    format.setLineWidth(72);
472                    OutputStream os=null;
473                    try {
474                        XMLSerializer serializer = new XMLSerializer(os=IOUtil.toBufferedOutputStream(searchFile.getOutputStream()), format);
475                            serializer.serialize(doc.getDocumentElement());
476                    } catch (IOException e) {
477                        throw new SearchException(e);
478                    }
479                    finally {
480                            IOUtil.closeEL(os);
481                    }
482        }
483    
484        /**
485             * if no search xml exist create a empty one
486             * @param searchFile 
487             * @throws IOException
488             */
489            private final static void createSearchFile(Resource searchFile) throws IOException {
490            
491                    searchFile.createFile(true);
492                    InputStream in = new Info().getClass().getResourceAsStream("/resource/search/default.xml");
493                    IOUtil.copy(in,searchFile,true);
494            
495            }
496        
497        /**
498         * @see railo.runtime.search.SearchEngine#getDisplayName()
499         */
500        public abstract String getDisplayName();
501    }