package org.openedit.xml;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.entermedia.cache.CacheManager;
import org.openedit.Data;
import org.openedit.data.BaseData;
import org.openedit.data.BaseSearcher;
import org.openedit.data.PropertyDetail;
import org.openedit.data.PropertyDetails;
import org.openedit.util.DateStorageUtil;
import com.openedit.OpenEditException;
import com.openedit.OpenEditRuntimeException;
import com.openedit.Shutdownable;
import com.openedit.hittracker.DataHitTracker;
import com.openedit.hittracker.HitTracker;
import com.openedit.hittracker.SearchQuery;
import com.openedit.hittracker.Term;
import com.openedit.users.User;
import com.openedit.util.PathUtilities;
public class XmlSearcher extends BaseSearcher implements Shutdownable
{
protected XmlArchive fieldXmlArchive;
private static final Log log = LogFactory.getLog(XmlSearcher.class);
protected PropertyDetails fieldDefaultDetails;
protected CacheManager fieldCacheManager;
protected XmlFile fieldXmlFile;
protected long fieldEditCount = 0;
public CacheManager getCacheManager()
{
if (fieldCacheManager == null)
{
fieldCacheManager = new CacheManager(); //TODO: make this shared across catalogs?
}
return fieldCacheManager;
}
public void setCacheManager(CacheManager inCache)
{
fieldCacheManager = inCache;
}
public Object searchById(String inId)
{
return searchById(inId, true );
}
public Object searchById(String inId, boolean inCache)
{
if (inId != null)
{
SearchQuery query = createSearchQuery();
query.addMatches("id", inId);
HitTracker hits = search(query);
Object hit = hits.first();
return hit;
}
return null;
}
protected String cacheId()
{
return getCatalogId() + getSearchType();
}
public void reIndexAll() throws OpenEditException
{
getCacheManager().clear(cacheId() );
fieldXmlFile = null;
}
public boolean passes(Element inElement, SearchQuery inQuery) throws ParseException
{
for (Iterator iterator = inQuery.getTerms().iterator(); iterator.hasNext();)
{
Term term = (Term) iterator.next();
if("betweendates".equals(term.getOperation()))
{
Date before = inQuery.getDateFormat().parse(term.getParameter("lowDate"));
Date after = inQuery.getDateFormat().parse(term.getParameter("highDate"));
String id = term.getDetail().getId();//effectivedate
String date = inElement.attributeValue(id);
if(date == null)
{
return false;
}
Date target = getDefaultDateFormat().parse(date);
if(!(before.before(target) && after.after(target)))
{
return false;
}
}
else if("afterdate".equals(term.getOperation()))
{
Date after = inQuery.getDateFormat().parse(term.getParameter("highDate"));
String id = term.getDetail().getId();//effectivedate
String date = inElement.attributeValue(id);
if(date == null)
{
return false;
}
Date target = getDefaultDateFormat().parse(date);
if(!target.after(after))
{
return false;
}
}
else if("beforedate".equals(term.getOperation()))
{
String low = term.getParameter("beforeDate");
Date before = null;
if( low != null)
{
before = inQuery.getDateFormat().parse(low);
}
else
{
Object[] values = term.getValues();
if( values != null && values.length > 0)
{
before = (Date)values[0];
}
}
String id = term.getDetail().getId();//effectivedate
String date = inElement.attributeValue(id);
if(date == null)
{
return false;
}
Date target = DateStorageUtil.getStorageUtil().parseFromStorage(date);
if(before == null || !target.before(before))
{
return false;
}
}
else if("orgroup".equals(term.getOperation()))
{
String value = term.getValue();
Object[] values = term.getValues();
String attribval = inElement.attributeValue(term.getDetail().getId());
if(value != null && (attribval == null || !value.contains(attribval)) )
{
return false;
}
if(values != null){
boolean foundmatch = false;
for (int i = 0; i < values.length; i++) {
String val = (String) values[i];
if(attribval != null && val.contains(attribval)){
foundmatch =true;
}
}
if(!foundmatch){
return false;
}
}
}
else
{
String name = term.getDetail().getId();
String value = term.getValue();
if (name == null)
{
name = "id";
}
else if( name.equals("description") )
{
if( value != null && "*".equals(value))
{
return true;
}
name = "name"; //This is temporary until we support isKeyword
}
if( value == null && term.getDetail().isBoolean() )
{
value = "false";
}
if( value != null)
{
value = value.toLowerCase();
}
String attribval = null;
if( "name".equals(name))
{
attribval = inElement.getTextTrim();
if(attribval == null || attribval.length()==0){
attribval = inElement.attributeValue("name");
if(attribval != null){
attribval = attribval.trim();
}
}
}
else
{
attribval = inElement.attributeValue(name);
}
if( attribval != null )
{
attribval = attribval.toLowerCase();
}
if( attribval == null && term.getDetail().isBoolean() )
{
attribval = "false";
}
if( "not".equals( term.getOperation() ) )
{
if (value != null && attribval != null && ("*".equals(value) || doesMatch(term.getOperation(),attribval,value) ) )
{
return false;
}
}
else
{
if (value != null && attribval != null && ("*".equals(value) || doesMatch(term.getOperation(),attribval,value) ) )
{
if (!inQuery.isAndTogether())
{
return true;
}
}
else if (inQuery.isAndTogether())
{
return false;
}
}
}
}
if(inQuery.isAndTogether())
{
return true;
}
return false;
}
protected boolean doesMatch(String inOperation, String inAttribval, String inValue)
{
if( "contains".equals(inOperation) )
{
return inAttribval.contains(inValue);
}
if(inAttribval.contains("|")){
String[] vals = BaseData.VALUEDELMITER.split(inAttribval);
for (int i = 0; i < vals.length; i++)
{
String val = vals[i];
if(PathUtilities.match(inValue, val)){
return true;
}
}
}
return PathUtilities.match(inAttribval.toLowerCase(), inValue);
}
public String getIndexId()
{
XmlFile settings = getXmlFile();
return getSearchType() + settings.getLastModified() + fieldEditCount;
}
/**
* Because of the way SearchQuery is coded, we can't get to the operation information.
* So, this only supports exact matching.
*/
public HitTracker search(SearchQuery inQuery)
{
if( inQuery == null)
{
return null;
}
HitTracker hits = (HitTracker) getCacheManager().get(cacheId(), inQuery.toQuery() + inQuery.getSortBy());
if(hits != null)
{
if( log.isDebugEnabled() )
{
log.debug("Cached search " + getSearchType() + " " + inQuery.toQuery() + " (sorted by " + inQuery.getSortBy() + ") found " + hits.size() + " in " + getCatalogId());
}
return hits;
}
XmlFile settings = getXmlFile();
List results = new ArrayList();
if (settings.isExist())
{
for (Iterator iterator = settings.getElements().iterator(); iterator.hasNext();)
{
Element element = (Element) iterator.next();
try
{
if (passes(element, inQuery))
{
results.add(new ElementData(element));
}
}
catch (ParseException e)
{
throw new OpenEditRuntimeException(e);
}
}
}
else
{
log.info("Xml does not exist " + settings.getPath());
}
sortResults(inQuery, results);
hits = new DataHitTracker();
hits.setSearcher(this);
hits.setSearchQuery(inQuery);
hits.setIndexId(getSearchType() + settings.getLastModified());
hits.addAll(results);
// if( getCache().size() > 500)
// {
// clearIndex();
// }
getCacheManager().put(cacheId(),inQuery.toQuery() + inQuery.getSortBy(), hits);
//if( log.isDebugEnabled() )
{
log.debug("Search " + getSearchType() + " " + inQuery.toQuery() + " (sorted by " + inQuery.getSortBy() + ") found " + hits.size());
}
return hits;
}
private void sortResults(SearchQuery inQuery, List results)
{
final List sorts = inQuery.getSorts();
if (!sorts.isEmpty() )
{
ElementSorter sorter = new ElementSorter(sorts);
Collections.sort(results,sorter);
}
}
protected XmlFile getXmlFile()
{
if( fieldXmlFile == null )
{
synchronized (this)
{
if( fieldXmlFile == null )
{
fieldXmlFile = loadXmlFile();
}
}
}
return fieldXmlFile;
}
public void setXmlFile(XmlFile inFile)
{
fieldXmlFile = inFile;
}
protected XmlFile loadXmlFile()
{
try
{
String inName = getSearchType();
String path = getPropertyDetailsArchive().getConfigurationPath("/lists"
+ "/" + inName + ".xml");
//No sure why we do this.
PropertyDetails details = getPropertyDetailsArchive().getPropertyDetailsCached(inName);
if( details == null)
{
inName = "property";
}
inName = inName.replace('/','_'); //if we load up foldername/name, we can't have a slash in the xml
XmlFile settings = getXmlArchive().getXml(path,path,inName);
return settings;
} catch ( OpenEditException ex)
{
throw new OpenEditRuntimeException(ex);
}
}
public XmlArchive getXmlArchive()
{
return fieldXmlArchive;
}
public void setXmlArchive(XmlArchive inXmlArchive)
{
fieldXmlArchive = inXmlArchive;
}
public SearchQuery createSearchQuery()
{
SearchQuery query = new SearchQuery();
query.setPropertyDetails(getPropertyDetails());
query.setCatalogId(getCatalogId());
query.setSearcherManager(getSearcherManager());
query.setResultType(getSearchType());
return query;
}
public void saveData(Data inData, User inUser)
{
//If this element is manipulated then the instance is the same
//No need to read it ElementData data = (ElementData)inData;
XmlFile settings = getXmlFile();
String path = "/WEB-INF/data/" + getCatalogId() + "/lists" + "/" + getSearchType() + ".xml";
settings.setPath(path);
Element element = null;
if( inData.getId() == null)
{
inData.setId( String.valueOf( new Date().getTime() ));
}
else
{
element = settings.getElementById(inData.getId());
}
if( element == null )
{
//New element
element = settings.getRoot().addElement(settings.getElementName());
element.addAttribute("id", inData.getId());
}
if( inData instanceof ElementData)
{
ElementData data = (ElementData)inData;
List attributes = data.getElement().attributes();
element.setAttributes(attributes);
//element.setText(inData.getName());
//existing row exists
element.setContent(data.getElement().content());
}
else
{
element.clearContent();
element.setAttributes(null);
ElementData data = new ElementData(element);
data.setId(inData.getId());
data.setName(inData.getName());
data.setSourcePath(inData.getSourcePath());
for (Iterator iterator = inData.getProperties().keySet().iterator(); iterator.hasNext();)
{
String key = (String) iterator.next();
data.setProperty(key, inData.get(key));
}
}
log.info("Saved to " + settings.getPath());
getXmlArchive().saveXml(settings, inUser);
clearIndex();
}
public void saveAllData(Collection inAll, User inUser){
String path = "/WEB-INF/data/" + getCatalogId() + "/lists"
+ "/" + getSearchType() + ".xml";
saveAllData(inAll, inUser, path);
}
public void saveAllData(Collection inAll, User inUser, String path)
{
XmlFile settings = getXmlFile();
settings.setPath(path);
for (Iterator iterator = inAll.iterator(); iterator.hasNext();)
{
Data data = (Data) iterator.next();
if( data.getId() == null)
{
//TODO: Use counter
data.setId( String.valueOf( new Date().getTime() ));
}
if( data instanceof ElementData)
{
ElementData edata = (ElementData)data;
if( edata.getElement().getParent() == null)
{
settings.getRoot().add(edata.getElement());
continue;
}
if( edata.getElement().getParent() == settings.getRoot())
{
continue;
}
}
Element contained = settings.getRoot().elementByID(data.getId()); //This only works for upper case ID
if( contained == null )
{
Element newone = settings.addNewElement();
List details = getProperties();
for (Iterator iterator2 = details.iterator(); iterator2.hasNext();)
{
PropertyDetail field = (PropertyDetail) iterator2.next();
String value = data.get(field.getId());
if( "name".equals(field.getId()) )
{
newone.setText(value);
}
else
{
newone.addAttribute(field.getId(), value);
}
}
}
}
clearIndex();
log.info("Saved to " + settings.getPath());
getXmlArchive().saveXml(settings, inUser);
}
public Data createNewData()
{
XmlFile settings = getXmlFile();
Element newone = DocumentHelper.createElement(settings.getElementName());
ElementData data = new ElementData(newone);
return data;
}
public List getIndexProperties()
{
PropertyDetails details = getPropertyDetailsArchive().getPropertyDetailsCached(getSearchType());
if( details == null || details.size() == 0)
{
return getDefaultDetails().findIndexProperties();
}
return details.findIndexProperties();
}
public List getStoredProperties()
{
PropertyDetails details = getPropertyDetailsArchive().getPropertyDetailsCached(getSearchType());
if( details == null || details.size() == 0)
{
return getDefaultDetails().findStoredProperties();
}
return details.findStoredProperties();
}
public List getSearchProperties(User inUser)
{
List details = getDetailsForView(getSearchType() + "/" + getSearchType() + "search", inUser);
if (details == null || details.size() == 0)
{
return getDefaultDetails().findStoredProperties();
}
return details;
}
public List getProperties()
{
PropertyDetails details = getPropertyDetailsArchive().getPropertyDetailsCached(getSearchType());
if( details == null || details.size() == 0)
{
return getDefaultDetails().getDetails();
}
return details.getDetails();
}
public PropertyDetails getPropertyDetails() {
PropertyDetails details = getPropertyDetailsArchive().getPropertyDetailsCached(getSearchType());
if( details == null || details.size() == 0)
{
return getDefaultDetails();
}
return details;
}
public PropertyDetails getDefaultDetails()
{
if( fieldDefaultDetails == null)
{
//fake one
PropertyDetails details = new PropertyDetails();
PropertyDetail id = new PropertyDetail();
id.setIndex(true);
id.setStored(true);
id.setText("Id");
id.setId("id");
id.setEditable(true);
id.setIndex(true);
id.setStored(true);
details.addDetail(id);
id = new PropertyDetail();
id.setIndex(true);
id.setStored(true);
id.setText("Name");
id.setId("name");
id.setEditable(true);
details.addDetail(id);
fieldDefaultDetails = details;
}
return fieldDefaultDetails;
}
public void deleteAll(User inUser)
{
HitTracker all = getAllHits();
XmlFile settings = getXmlFile();
for (Iterator iterator = all.iterator(); iterator.hasNext();)
{
Data object = (Data)iterator.next();
Element record = settings.getElementById(object.getId());
if( record != null)
{
settings.getRoot().remove(record);
}
}
getXmlArchive().saveXml(settings, inUser);
clearIndex();
}
public void delete(Data inData, User inUser)
{
XmlFile settings = getXmlFile();
Element record = settings.getElementById(inData.getId());
if( record != null)
{
settings.getRoot().remove(record);
getXmlArchive().saveXml(settings, inUser);
}
clearIndex();
}
public void clearIndex()
{
fieldEditCount++;
synchronized (this)
{
fieldXmlFile = null; //reload it each time?
getCacheManager().clear(cacheId());
}
}
public void shutdown()
{
clearIndex();
}
}