package railo.runtime.search;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import railo.commons.io.IOUtil;
import railo.commons.io.log.LogAndSource;
import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourceProvider;
import railo.commons.io.res.ResourcesImpl;
import railo.commons.lang.StringUtil;
import railo.runtime.Info;
import railo.runtime.config.Config;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.DatabaseException;
import railo.runtime.exp.PageException;
import railo.runtime.op.date.DateCaster;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Query;
import railo.runtime.type.QueryImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.dt.DateTime;
import railo.runtime.type.dt.DateTimeImpl;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;
/**
*
*/
public abstract class SearchEngineSupport implements SearchEngine {
private Resource searchFile;
private Resource searchDir;
private LogAndSource log;
private Document doc;
Struct collections=new StructImpl();
@Override
public void init(railo.runtime.config.Config config,Resource searchDir, LogAndSource log) throws SAXException, IOException, SearchException {
this.searchDir=searchDir;
this.searchFile=searchDir.getRealResource("search.xml");
if(!searchFile.exists()) createSearchFile(searchFile);
DOMParser parser = new DOMParser();
InputStream is=null;
try {
is = IOUtil.toBufferedInputStream(searchFile.getInputStream());
InputSource source = new InputSource(is);
parser.parse(source);
}
finally {
IOUtil.closeEL(is);
}
doc = parser.getDocument();
this.log=log;
readCollections(config);
}
@Override
public final SearchCollection getCollectionByName(String name) throws SearchException {
Object o=collections.get(name.toLowerCase(),null);
if(o!=null)return (SearchCollection) o;
throw new SearchException("collection "+name+" is undefined");
}
@Override
public final Query getCollectionsAsQuery() {
final String v="VARCHAR";
Query query=null;
String[] cols = new String[]{"external","language","mapped","name","online","path","registered","lastmodified","categories","charset","created",
"size","doccount"};
String[] types = new String[]{"BOOLEAN",v,"BOOLEAN",v,"BOOLEAN",v,v,"DATE","BOOLEAN",v,"OBJECT","DOUBLE","DOUBLE"};
try {
query=new QueryImpl(cols,types, collections.size(),"query");
} catch (DatabaseException e) {
query=new QueryImpl(cols, collections.size(),"query");
}
//Collection.Key[] keys = collections.keys();
Iterator<Object> it = collections.valueIterator();
int i=-1;
while(it.hasNext()) {
i++;
try {
SearchCollection coll = (SearchCollection) it.next();
query.setAt(KeyConstants._external,i+1,Boolean.FALSE);
query.setAt(KeyConstants._charset,i+1,"UTF-8");
query.setAt(KeyConstants._created,i+1,coll.created());
query.setAt("categories",i+1,Boolean.TRUE);
query.setAt(KeyConstants._language,i+1,coll.getLanguage());
query.setAt("mapped",i+1,Boolean.FALSE);
query.setAt(KeyConstants._name,i+1,coll.getName());
query.setAt(KeyConstants._online,i+1,Boolean.TRUE);
query.setAt(KeyConstants._path,i+1,coll.getPath().getAbsolutePath());
query.setAt("registered",i+1,"CF");
query.setAt(KeyConstants._lastmodified,i+1,coll.getLastUpdate());
query.setAt(KeyConstants._size,i+1,new Double(coll.getSize()));
query.setAt("doccount",i+1,new Double(coll.getDocumentCount()));
}
catch(PageException pe) {}
}
return query;
}
@Override
public final SearchCollection createCollection(String name,Resource path, String language, boolean allowOverwrite) throws SearchException {
SearchCollection coll = _createCollection(name,path,language);
coll.create();
addCollection(coll,allowOverwrite);
return coll;
}
/**
* Creates a new Collection, will be invoked by createCollection
* @param name The Name of the Collection
* @param path the path to store
* @param language The language of the collection
* @return New SearchCollection
* @throws SearchException
*/
protected abstract SearchCollection _createCollection(String name,Resource path, String language) throws SearchException;
/**
* adds a new Collection to the storage
* @param collection
* @param allowOverwrite if allowOverwrite is false and a collection already exist -> throw Exception
* @throws SearchException
*/
private final synchronized void addCollection(SearchCollection collection, boolean allowOverwrite) throws SearchException {
Object o = collections.get(collection.getName(),null);
if(!allowOverwrite && o!=null)
throw new SearchException("there is already a collection with name "+collection.getName());
collections.setEL(collection.getName(),collection);
// update
if(o!=null) {
setAttributes(getCollectionElement(collection.getName()),collection);
}
// create
else {
doc.getDocumentElement().appendChild(toElement(collection));
}
store();
}
/**
* removes a Collection from the storage
* @param collection Collection to remove
* @throws SearchException
*/
protected final synchronized void removeCollection(SearchCollection collection)
throws SearchException {
removeCollection(collection.getName());
_removeCollection(collection);
}
/**
* removes a Collection from the storage
* @param collection Collection to remove
* @throws SearchException
*/
protected abstract void _removeCollection(SearchCollection collection) throws SearchException;
/**
* removes a Collection from the storage
* @param name Name of the Collection to remove
* @throws SearchException
*/
private final synchronized void removeCollection(String name) throws SearchException {
try {
collections.remove(KeyImpl.init(name));
doc.getDocumentElement().removeChild(getCollectionElement(name));
store();
}
catch (PageException e) {
throw new SearchException("can't remove collection "+name+", collection doesn't exist");
}
}
/**
* purge a Collection
* @param collection Collection to purge
* @throws SearchException
*/
protected final synchronized void purgeCollection(SearchCollection collection)
throws SearchException {
purgeCollection(collection.getName());
}
/**
* purge a Collection
* @param name Name of the Collection to purge
* @throws SearchException
*/
private final synchronized void purgeCollection(String name) throws SearchException {
//Map map=(Map)collections.get(name);
//if(map!=null)map.clear();
Element parent = getCollectionElement(name);
NodeList list=parent.getChildNodes();
int len=list.getLength();
for(int i=len-1;i>=0;i--) {
parent.removeChild(list.item(i));
}
//doc.getDocumentElement().removeChild(getCollectionElement(name));
store();
}
@Override
public Resource getDirectory() {
return searchDir;
}
@Override
public LogAndSource getLogger() {
return log;
}
/**
* return XML Element matching collection name
* @param name
* @return matching XML Element
*/
protected final Element getCollectionElement(String name) {
Element root = doc.getDocumentElement();
NodeList children = root.getChildNodes();
int len=children.getLength();
for(int i=0;i<len;i++) {
Node n=children.item(i);
if(n instanceof Element && n.getNodeName().equals("collection")) {
Element el = (Element)n;
if(el.getAttribute("name").equalsIgnoreCase(name)) return el;
}
}
return null;
}
@Override
public Element getIndexElement(Element collElement, String id) {
NodeList children = collElement.getChildNodes();
int len=children.getLength();
for(int i=0;i<len;i++) {
Node n=children.item(i);
if(n instanceof Element && n.getNodeName().equals("index")) {
Element el = (Element)n;
if(el.getAttribute("id").equals(id)) return el;
}
}
return null;
}
/**
* translate a collection object to a XML Element
* @param coll Collection to translate
* @return XML Element
*/
private final Element toElement(SearchCollection coll) {
Element el = doc.createElement("collection");
setAttributes(el,coll);
return el;
}
/**
* translate a collection object to a XML Element
* @param index Index to translate
* @return XML Element
* @throws SearchException
*/
protected final Element toElement(SearchIndex index) throws SearchException {
Element el = doc.createElement("index");
setAttributes(el,index);
return el;
}
/**
* sets all attributes in XML Element from Search Collection
* @param el
* @param coll
*/
private final void setAttributes(Element el,SearchCollection coll) {
if(el==null) return;
setAttribute(el,"language",coll.getLanguage());
setAttribute(el,"name",coll.getName());
String value = coll.getLastUpdate().castToString(null);
if(value!=null)setAttribute(el,"lastUpdate",value);
value=coll.getCreated().castToString(null);
if(value!=null)setAttribute(el,"created",value);
setAttribute(el,"path",coll.getPath().getAbsolutePath());
}
/**
* sets all attributes in XML Element from Search Index
* @param el
* @param index
* @throws SearchException
*/
protected final void setAttributes(Element el,SearchIndex index) throws SearchException {
if(el==null) return;
setAttribute(el,"categoryTree",index.getCategoryTree());
setAttribute(el,"category",ListUtil.arrayToList(index.getCategories(),","));
setAttribute(el,"custom1",index.getCustom1());
setAttribute(el,"custom2",index.getCustom2());
setAttribute(el,"custom3",index.getCustom3());
setAttribute(el,"custom4",index.getCustom4());
setAttribute(el,"id",index.getId());
setAttribute(el,"key",index.getKey());
setAttribute(el,"language",index.getLanguage());
setAttribute(el,"title",index.getTitle());
setAttribute(el,"extensions",ListUtil.arrayToList(index.getExtensions(),","));
setAttribute(el,"type",SearchIndex.toStringType(index.getType()));
setAttribute(el,"urlpath",index.getUrlpath());
setAttribute(el,"query",index.getQuery());
}
/**
* helper method to set a attribute
* @param el
* @param name
* @param value
*/
private void setAttribute(Element el, String name, String value) {
if(value!=null)el.setAttribute(name,value);
}
/**
* read in collections
* @param config
* @throws SearchException
*/
private void readCollections(Config config) throws SearchException {
Element root = doc.getDocumentElement();
NodeList children = root.getChildNodes();
int len=children.getLength();
for(int i=0;i<len;i++) {
Node n=children.item(i);
if(n instanceof Element && n.getNodeName().equals("collection")) {
readCollection(config,(Element)n);
}
}
}
/**
* read in a single collection element
* @param config
* @param el
* @throws SearchException
*/
private final void readCollection(Config config, Element el) throws SearchException {
SearchCollectionPlus sc;
//try {
// Collection
DateTime last = DateCaster.toDateAdvanced(el.getAttribute("lastUpdate"),ThreadLocalPageContext.getTimeZone(config),null);
if(last==null)last=new DateTimeImpl();
DateTime cre = DateCaster.toDateAdvanced(el.getAttribute("created"),ThreadLocalPageContext.getTimeZone(config),null);
if(cre==null)cre=new DateTimeImpl();
ResourceProvider frp = ResourcesImpl.getFileResourceProvider();
sc =(SearchCollectionPlus) _readCollection(
el.getAttribute("name"),
frp.getResource(el.getAttribute("path")),
el.getAttribute("language"),
last,cre
);
collections.setEL(KeyImpl.init(sc.getName()),sc);
// Indexes
NodeList children = el.getChildNodes();
int len=children.getLength();
for(int i=0;i<len;i++) {
Node n=children.item(i);
if(n instanceof Element && n.getNodeName().equals("index")) {
readIndex(sc,(Element)n);
}
}
/*}
catch (PageException e) {
throw new SearchException(e);
}*/
}
/**
* read in a single Index
* @param sc
* @param el
* @throws SearchException
* @throws PageException
*/
protected void readIndex(SearchCollectionPlus sc, Element el) throws SearchException {
// Index
SearchIndex si=new SearchIndex(
_attr(el,"id"),
_attr(el,"title"),
_attr(el,"key"),
SearchIndex.toType(_attr(el,"type")),
_attr(el,"query"),
ListUtil.listToStringArray(_attr(el,"extensions"),','),
_attr(el,"language"),
_attr(el,"urlpath"),
_attr(el,"categoryTree"),
ListUtil.listToStringArray(_attr(el,"category"),','),
_attr(el,"custom1"),
_attr(el,"custom2"),
_attr(el,"custom3"),
_attr(el,"custom4"));
sc.addIndex(si);
}
private String _attr(Element el, String attr) {
return StringUtil.emptyIfNull(el.getAttribute(attr));
}
/**
* read in a existing collection
* @param name
* @param parh
* @param language
* @param count
* @param lastUpdate
* @param created
* @return SearchCollection
* @throws SearchException
*/
protected abstract SearchCollection _readCollection(String name, Resource parh, String language, DateTime lastUpdate, DateTime created) throws SearchException;
/**
* store loaded data to xml file
* @throws SearchException
*/
protected final synchronized void store() throws SearchException {
//Collection.Key[] keys=collections.keys();
Iterator<Key> it = collections.keyIterator();
Key k;
while(it.hasNext()) {
k=it.next();
Element collEl = getCollectionElement(k.getString());
SearchCollection sc = getCollectionByName(k.getString());
setAttributes(collEl,sc);
}
OutputFormat format = new OutputFormat(doc, null, true);
format.setLineSeparator("\r\n");
format.setLineWidth(72);
OutputStream os=null;
try {
XMLSerializer serializer = new XMLSerializer(os=IOUtil.toBufferedOutputStream(searchFile.getOutputStream()), format);
serializer.serialize(doc.getDocumentElement());
} catch (IOException e) {
throw new SearchException(e);
}
finally {
IOUtil.closeEL(os);
}
}
/**
* if no search xml exist create a empty one
* @param searchFile
* @throws IOException
*/
private final static void createSearchFile(Resource searchFile) throws IOException {
searchFile.createFile(true);
InputStream in = new Info().getClass().getResourceAsStream("/resource/search/default.xml");
IOUtil.copy(in,searchFile,true);
}
@Override
public abstract String getDisplayName();
}