/**Copyright 2010 Research Studios Austria Forschungsgesellschaft mBH
*
* This file is part of easyrec.
*
* easyrec is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* easyrec is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with easyrec. If not, see <http://www.gnu.org/licenses/>.
*/
package org.easyrec.service.core.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.easyrec.model.core.ItemVO;
import org.easyrec.model.core.web.Item;
import org.easyrec.service.core.ProfileService;
import org.easyrec.service.core.exception.FieldNotFoundException;
import org.easyrec.service.core.exception.MultipleProfileFieldsFoundException;
import org.easyrec.service.domain.TypeMappingService;
import org.easyrec.store.dao.IDMappingDAO;
import org.easyrec.store.dao.core.ProfileDAO;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.SchemaFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* @author szavrel
*/
public class ProfileServiceImpl implements ProfileService {
private ProfileDAO profileDAO;
private IDMappingDAO idMappingDAO;
private TypeMappingService typeMappingService;
private SchemaFactory sf;
private DocumentBuilderFactory dbf;
private Transformer trans;
// logging
private final Log logger = LogFactory.getLog(this.getClass());
public ProfileServiceImpl(ProfileDAO profileDAO, IDMappingDAO idMappingDAO, TypeMappingService typeMappingService) {
this(profileDAO, null, idMappingDAO, typeMappingService);
}
public ProfileServiceImpl(ProfileDAO profileDAO,
String docBuilderFactory, IDMappingDAO idMappingDAO, TypeMappingService typeMappingService) {
this.profileDAO = profileDAO;
this.idMappingDAO = idMappingDAO;
this.typeMappingService = typeMappingService;
if (docBuilderFactory != null)
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", docBuilderFactory);
dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
if (logger.isDebugEnabled()) {
logger.debug("DocumentBuilderFactory: " + dbf.getClass().getName());
ClassLoader cl = Thread.currentThread().getContextClassLoader().getSystemClassLoader();
URL url = cl.getResource("org/apache/xerces/jaxp/DocumentBuilderFactoryImpl.class");
logger.debug("Parser loaded from: " + url);
}
TransformerFactory tf = TransformerFactory.newInstance();
try {
trans = tf.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
} catch (Exception e) {
}
}
public boolean storeProfile(Integer tenantId, Integer itemId, String itemTypeId, String profileXML) {
return profileDAO.storeProfile(tenantId, itemId,
typeMappingService.getIdOfItemType(tenantId, itemTypeId), profileXML) != 0;
}
public boolean storeProfile(Integer tenantId, String itemId, String itemType, String profileXML) {
return profileDAO.storeProfile(tenantId, idMappingDAO.lookup(itemId),
typeMappingService.getIdOfItemType(tenantId, itemType), profileXML) != 0;
}
public boolean deleteProfile(Integer tenantId, String itemId, String itemType) {
return profileDAO.deleteProfile(tenantId, idMappingDAO.lookup(itemId),
typeMappingService.getIdOfItemType(tenantId, itemType));
}
public String getProfile(Integer tenantId, String itemId, Integer itemTypeId) {
Integer mappedItemId = idMappingDAO.lookup(itemId);
return profileDAO.getProfile(tenantId, mappedItemId, itemTypeId);
}
public String getProfile(Integer tenantId, Integer itemId, Integer itemTypeId) {
return profileDAO.getProfile(tenantId, itemId, itemTypeId);
}
public String getProfile(Integer tenantId, Integer itemId, String itemTypeId) {
return profileDAO.getProfile(tenantId, itemId, typeMappingService.getIdOfItemType(tenantId, itemTypeId));
}
public String getProfile(Item item) {
return getProfile(item.getTenantId(), item.getItemId(), item.getItemType());
}
public String getProfile(ItemVO<Integer, Integer> item) {
return getProfile(item.getTenant(), item.getItem(), item.getType());
}
public String getProfile(Integer tenantId, String itemId, String itemTypeId) {
Integer mappedItemId = idMappingDAO.lookup(itemId);
return getProfile(tenantId, mappedItemId, itemTypeId);
}
public Set<String> getMultiDimensionValue(Integer tenantId, Integer itemId, String itemType,
String dimensionXPath) {
return profileDAO.getMultiDimensionValue(tenantId, itemId,
typeMappingService.getIdOfItemType(tenantId, itemType), dimensionXPath);
}
public Set<String> getMultiDimensionValue(Integer tenantId, String itemId, String itemType,
String dimensionXPath) {
return profileDAO.getMultiDimensionValue(tenantId, idMappingDAO.lookup(itemId),
typeMappingService.getIdOfItemType(tenantId, itemType), dimensionXPath);
}
public Set<String> loadProfileField(Integer tenantId, String itemId, String itemType,
String dimensionXPath)
throws XPathExpressionException, SAXException {
Set<String> result = new HashSet<String>();
try {
int itemIntID = idMappingDAO.lookup(itemId);
XPathFactory xpf = XPathFactory.newInstance();
Document doc = getProfileXMLDocument(tenantId, itemIntID, itemType);
XPath xp = xpf.newXPath();
NodeList nodeList = (NodeList) xp.evaluate(dimensionXPath, doc, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// result.add(node.getTextContent());
result.add(node.getNodeValue());
}
} catch (Exception e) {
logger.error("Error loading profile field: " + e.getMessage());
e.printStackTrace();
if (e instanceof SAXException)
throw (SAXException) e;
if (e instanceof XPathExpressionException)
throw (XPathExpressionException) e;
if (e instanceof DOMException)
throw (DOMException) e;
if (e instanceof IllegalArgumentException)
throw (IllegalArgumentException) e;
return null;
}
return result;
}
public String getSimpleDimensionValue(Integer tenantId, Integer itemId, String itemTypeId, String dimensionXPath) {
return profileDAO
.getSimpleDimensionValue(tenantId, itemId, typeMappingService.getIdOfItemType(tenantId, itemTypeId),
dimensionXPath);
}
public String getSimpleDimensionValue(Integer tenantId, String itemId, String itemTypeId, String dimensionXPath) {
return profileDAO.getSimpleDimensionValue(
tenantId,
idMappingDAO.lookup(itemId),
typeMappingService.getIdOfItemType(tenantId, itemTypeId),
dimensionXPath);
}
public boolean insertOrUpdateMultiDimension(Integer tenantId, Integer itemId, String itemType, String dimensionXPath,
List<String> values) {
XPathFactory xpf = XPathFactory.newInstance();
try {
// load and parse the profile
Document doc = getProfileXMLDocument(tenantId, itemId, itemType);
// check if the element exists
Node node = null;
Node parent = null;
XPath xp = xpf.newXPath();
for (Iterator<String> it = values.iterator(); it.hasNext(); ) {
String value = it.next();
// look if value already exists
node = (Node) xp.evaluate(dimensionXPath + "[text()='" + value + "']", doc, XPathConstants.NODE);
// if value exists, value can be discarded
if (node != null) {
// optimization: if a node was found, store the parent; later no new XPath evaluation is necessary
parent = node.getParentNode();
it.remove();
}
}
if (values.isEmpty()) return true; // nothing left to do
String parentPath = dimensionXPath.substring(0, dimensionXPath.lastIndexOf("/"));
parent = (Node) xp.evaluate(parentPath, doc, XPathConstants.NODE);
// find path to parent
if (parent == null) {
String tmpPath = parentPath;
while (parent == null) {
tmpPath = parentPath.substring(0, tmpPath.lastIndexOf("/"));
parent = (Node) xp.evaluate(tmpPath, doc, XPathConstants.NODE);
}
parent = insertElement(doc, parent, parentPath.substring(tmpPath.length()), null);
}
String tag = dimensionXPath.substring(parentPath.length() + 1);
for (String value : values) {
Element el = doc.createElement(tag);
el.setNodeValue(value);
//el.setTextContent(value);
parent.appendChild(el);
}
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
trans.transform(new DOMSource(doc), result);
writer.close();
String xml = writer.toString();
logger.debug(xml);
storeProfile(tenantId, itemId, itemType, xml);
} catch (Exception e) {
logger.error("Error inserting Multi Dimension: " + e.getMessage());
e.printStackTrace();
return false;
}
return true;
}
public boolean insertOrUpdateMultiDimension(Integer tenantId, String itemId,
String itemType, String dimensionXPath,
List<String> values) {
return insertOrUpdateMultiDimension(tenantId, idMappingDAO.lookup(itemId), itemType, dimensionXPath, values);
}
public boolean insertOrUpdateSimpleDimension(Integer tenantId, Integer itemId, String itemTypeId,
String dimensionXPath, String value) {
XPathFactory xpf = XPathFactory.newInstance();
try {
// load and parse the profile
Document doc = getProfileXMLDocument(tenantId, itemId, itemTypeId);
// check if the element exists
XPath xp = xpf.newXPath();
Node node = (Node) xp.evaluate(dimensionXPath, doc, XPathConstants.NODE);
// if the element exists, just update the value
if (node != null) {
// if value doesn't change, there is no need to alter the profile and write it to database
// if (value.equals(node.getTextContent())) return true;
// node.setTextContent(value);
if(value.equals(node.getNodeValue())) return true;
node.setNodeValue(value);
} else { // if the element cannot be found, insert it at the position given in the dimensionXPath
// follow the XPath from bottom to top until you find the first existing path element
String tmpPath = dimensionXPath;
while (node == null) {
tmpPath = dimensionXPath.substring(0, tmpPath.lastIndexOf("/"));
node = (Node) xp.evaluate(tmpPath, doc, XPathConstants.NODE);
}
// found the correct node to insert or ended at Document root, hence insert
insertElement(doc, node, dimensionXPath.substring(tmpPath.length()/*, dimensionXPath.length()*/),
value);
}
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
trans.transform(new DOMSource(doc), result);
writer.close();
String xml = writer.toString();
logger.debug(xml);
storeProfile(tenantId, itemId, itemTypeId, xml);
} catch (Exception e) {
logger.error("Error inserting Simple Dimension: " + e.getMessage());
e.printStackTrace();
return false;
}
return true;
}
public boolean insertOrUpdateSimpleDimension(Integer tenantId, String itemId, String itemTypeId,
String dimensionXPath, String value) {
return insertOrUpdateSimpleDimension(tenantId, idMappingDAO.lookup(itemId), itemTypeId,
dimensionXPath, value);
}
public synchronized boolean storeProfileField(Integer tenantId, String itemId, String itemTypeId,
String dimensionXPath, String value)
throws XPathExpressionException, TransformerException, SAXException,
DOMException, MultipleProfileFieldsFoundException {
try {
int itemIntID = idMappingDAO.lookup(itemId);
XPathFactory xpf = XPathFactory.newInstance();
// load and parse the profile
Document doc = getProfileXMLDocument(tenantId, itemIntID, itemTypeId);
// follow the XPath from bottom to top until you find the first existing path element
XPath xp = xpf.newXPath();
String tmpPath = dimensionXPath;
NodeList nodeList = (NodeList) xp.evaluate(tmpPath, doc, XPathConstants.NODESET);
if (nodeList.getLength() > 1)
throw new MultipleProfileFieldsFoundException(nodeList.getLength() + " nodes found.");
Node node = null;
if (nodeList.getLength() == 1)
// nodeList.item(0).setTextContent(value);
nodeList.item(0).setNodeValue(value);
else {
while (node == null) {
tmpPath = dimensionXPath.substring(0, tmpPath.lastIndexOf("/"));
if ("".equals(tmpPath))
tmpPath = "/";
node = (Node) xp.evaluate(tmpPath, doc, XPathConstants.NODE);
}
insertElement(doc, node,
dimensionXPath.substring(tmpPath.length()), value);
}
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
trans.transform(new DOMSource(doc), result);
writer.close();
String xml = writer.toString();
logger.debug(xml);
storeProfile(tenantId, itemId, itemTypeId, xml);
} catch (Exception e) {
logger.error("Error inserting Simple Dimension: " + e.getMessage());
e.printStackTrace();
if (e instanceof SAXException)
throw (SAXException) e;
if (e instanceof TransformerException)
throw (TransformerException) e;
if (e instanceof XPathExpressionException)
throw (XPathExpressionException) e;
if (e instanceof DOMException)
throw (DOMException) e;
if (e instanceof MultipleProfileFieldsFoundException)
throw (MultipleProfileFieldsFoundException) e;
if (e instanceof IllegalArgumentException)
throw (IllegalArgumentException) e;
return false;
}
return true;
}
public boolean deleteProfileField(Integer tenantId, String itemId, String itemType, String deleteXPath)
throws XPathExpressionException, TransformerException, SAXException, FieldNotFoundException {
XPathFactory xpf = XPathFactory.newInstance();
try {
// load and parse the profile
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(
getProfile(tenantId, itemId, itemType))));
// check if the element exists
XPath xp = xpf.newXPath();
NodeList nodeList = (NodeList) xp.evaluate(deleteXPath, doc, XPathConstants.NODESET);
if (nodeList.getLength() == 0)
throw new FieldNotFoundException("Field does not exist in this profile!");
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
node.getParentNode().removeChild(node);
}
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
trans.transform(new DOMSource(doc), result);
writer.close();
String xml = writer.toString();
logger.debug(xml);
storeProfile(tenantId, itemId, itemType, xml);
return true;
} catch (Exception e) {
logger.error("Error deleting field: " + e.getMessage());
e.printStackTrace();
if (e instanceof SAXException)
throw (SAXException) e;
if (e instanceof TransformerException)
throw (TransformerException) e;
if (e instanceof XPathExpressionException)
throw (XPathExpressionException) e;
if (e instanceof DOMException)
throw (DOMException) e;
if (e instanceof FieldNotFoundException)
throw (FieldNotFoundException) e;
if (e instanceof IllegalArgumentException)
throw (IllegalArgumentException) e;
return false;
}
}
public List<ItemVO<Integer, Integer>> getItemsByDimensionValue(Integer tenantId, String itemType,
String dimensionXPath, String value) {
return profileDAO.getItemsByDimensionValue(tenantId, typeMappingService.getIdOfItemType(tenantId, itemType),
dimensionXPath, value);
}
public List<ItemVO<Integer, Integer>> getItemsByItemType(Integer tenantId, String itemType, int count) {
return profileDAO.getItemsByItemType(tenantId, typeMappingService.getIdOfItemType(tenantId, itemType), count);
}
/**
* Inserts a new element and value into an XML Document at the position given in xPathExpression
* relative to the Node given in startNode.
*
* @param doc the Document in which the Element is inserted
* @param startNode the Node in the Document used as start point for the XPath Expression
* @param xPathExpression the XPath from the startNode to the new Element
* @param value the value of the new Element
*/
private Node insertElement(Document doc, Node startNode, String xPathExpression, String value) {
if (!"".equals(xPathExpression)) {
String[] xPathTokens = xPathExpression.split("/");
for (String tag : xPathTokens) {
if (!"".equals(tag)) {
Element el = doc.createElement(tag);
startNode.appendChild(el);
startNode = startNode.getLastChild();
}
}
// if (value != null) startNode.setTextContent(value);
if (value != null) startNode.setNodeValue(value);
}
return startNode;
}
private Document getProfileXMLDocument(Integer tenantId, Integer itemId, String itemTypeId)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder db = dbf.newDocumentBuilder();
String profile = getProfile(tenantId, itemId, itemTypeId);
if (profile == null || profile.equals(""))
return db.newDocument();
else
return db.parse(new InputSource(new StringReader(profile)));
}
}