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