diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/PropertyTypeBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/PropertyTypeBO.java index a341673d1f62aba79872653a9874bfcf167fe9d9..aa0e8dd10246f142751903aabc280aed02525dd8 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/PropertyTypeBO.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/PropertyTypeBO.java @@ -27,7 +27,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.shared.basic.utils.StringUtils; import ch.systemsx.cisd.common.utilities.XMLInfraStructure; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory; -import ch.systemsx.cisd.openbis.generic.server.util.XmlUtils; +import ch.systemsx.cisd.openbis.generic.server.dataaccess.XmlUtils; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataType; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode; diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java index 955fc18c607d60c37ebc37dcaa5fc36b2040d6c1..b61500e7b8d12e9372a6dedf2e668df8d14d4b7a 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java @@ -35,7 +35,6 @@ import ch.systemsx.cisd.common.collections.IToStringConverter; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.utilities.PropertyUtils; import ch.systemsx.cisd.common.utilities.PropertyUtils.Boolean; -import ch.systemsx.cisd.openbis.generic.server.util.XmlUtils; import ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant; import ch.systemsx.cisd.openbis.generic.shared.basic.ValidationUtilities.HyperlinkValidationHelper; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode; diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/XmlUtils.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/XmlUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..cf1ab96f4e9bee6e25e21552afd517d7c1c1268d --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/XmlUtils.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.server.dataaccess; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.URL; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; + +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.common.utilities.XMLInfraStructure; + +/** + * Utility methods for parsing and validating XML files. + * + * @author Piotr Buczek + */ +public class XmlUtils +{ + public static String XML_SCHEMA_XSD_URL = "http://www.w3.org/2001/XMLSchema.xsd"; + + public static String XSLT_XSD_URL = "http://www.w3.org/2007/schema-for-xslt20.xsd"; + + public static String XML_SCHEMA_XSD_FILE_RESOURCE = "/XMLSchema.xsd"; + + public static String XSLT_XSD_FILE_RESOURCE = "/schema-for-xslt20.xsd"; + + /** + * Parse given string as XML {@link Document}. + * + * @throws UserFailureException if provided value is not a well-formed XML document + */ + public static Document parseXmlDocument(String value) + { + DocumentBuilderFactory dBF = DocumentBuilderFactory.newInstance(); + dBF.setNamespaceAware(true); + InputSource is = new InputSource(new StringReader(value)); + try + { + return dBF.newDocumentBuilder().parse(is); + } catch (Exception e) + { + throw UserFailureException.fromTemplate( + "Provided value:\n\n%s\n\nisn't a well formed XML document. %s", value, e + .getMessage()); + } + } + + /** validate given document against the specified schema */ + public static void validate(Document document, String xmlSchema) throws SAXException, + IOException + { + validate(document, XMLInfraStructure.createSchema(new StreamSource(new StringReader( + xmlSchema)))); + } + + /** validate given document against the schema specified by URL */ + public static void validate(Document document, URL schemaURL) throws SAXException, IOException + { + validate(document, XMLInfraStructure.createSchema(schemaURL)); + } + + /** validate given document against the schema specified in File */ + public static void validate(Document document, File schemaFile) throws SAXException, + IOException + { + validate(document, XMLInfraStructure.createSchema(schemaFile)); + } + + /** validate given document against the specified schema */ + public static void validate(Document document, Schema schema) throws SAXException, IOException + { + // create a Validator instance, which can be used to validate an instance document + Validator validator = schema.newValidator(); + // validate the DOM tree + validator.validate(new DOMSource(document)); + } + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/DynamicPropertyCalculator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/DynamicPropertyCalculator.java index c9b19288b3a8fb758ae29b0a15eb10b99ca67a52..11e24a8bd2d9c670d0ba1749c57fac3e551556c9 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/DynamicPropertyCalculator.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/DynamicPropertyCalculator.java @@ -17,18 +17,24 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess.db.dynamic_property.calculator; import ch.systemsx.cisd.common.evaluator.Evaluator; -import ch.systemsx.cisd.openbis.generic.client.web.server.calculator.AbstractCalculator; +import ch.systemsx.cisd.common.evaluator.EvaluatorException; /** * @author Piotr Buczek */ -public class DynamicPropertyCalculator extends AbstractCalculator +public class DynamicPropertyCalculator { private static final String ENTITY_VARIABLE_NAME = "entity"; + private static final String BASIC_INITIAL_SCRIPT = "from " + + StandardFunctions.class.getCanonicalName() + " import *\n" + + "def int(x):return toInt(x)\n" + "def float(x):return toFloat(x)\n"; + + private final Evaluator evaluator; + public DynamicPropertyCalculator(String expression) { - super(new Evaluator(expression, Math.class, BASIC_INITIAL_SCRIPT)); + this.evaluator = new Evaluator(expression, Math.class, BASIC_INITIAL_SCRIPT); } public void setEntity(IEntityAdaptor entity) @@ -36,4 +42,14 @@ public class DynamicPropertyCalculator extends AbstractCalculator evaluator.set(ENTITY_VARIABLE_NAME, entity); } + public String evalAsString() throws EvaluatorException + { + return evaluator.evalAsString(); + } + + public Object eval() throws EvaluatorException + { + return evaluator.eval(); + } + } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/StandardFunctions.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/StandardFunctions.java new file mode 100644 index 0000000000000000000000000000000000000000..2b92ed2d0f33cfb118a73966fb9588350e472d53 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/dynamic_property/calculator/StandardFunctions.java @@ -0,0 +1,369 @@ +/* + * Copyright 2009 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.server.dataaccess.db.dynamic_property.calculator; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.xml.sax.InputSource; + +import ch.systemsx.cisd.common.utilities.ExceptionUtils; + +/** + * Set of standard functions used in jython expressions. + * <p> + * All public methods of this class are part of the Filter/Calculated Column API. + * + * @author Franz-Josef Elmer + */ +final class StandardFunctions +{ + static final Double DOUBLE_DEFAULT_VALUE = new Double(-Double.MAX_VALUE); + static final Integer INTEGER_DEFAULT_VALUE = new Integer(Integer.MIN_VALUE); + + private static final XPath XPATH = XPathFactory.newInstance().newXPath(); + + /** + * Returns <code>true</code> if the specified string matches the specified regular expression. + */ + public static boolean matches(String regex, String string) + { + return Pattern.matches(regex, string); + } + + /** + * Evaluates the specified XPath expression onto the specified XML string. + */ + public static String evalXPath(String xPath, String xmlString) + { + try + { + XPathExpression expression = XPATH.compile(xPath); + String str = StringEscapeUtils.unescapeXml(xmlString); + String result = expression.evaluate(new InputSource(new StringReader(str))); + return result; + } catch (XPathExpressionException ex) + { + return "ERROR " + ExceptionUtils.getEndOfChain(ex).toString(); + } + } + + /** + * Returns the specified value as an integer. Returns the smallest integer if <code>value</code> + * is <code>null</code> or its trimmed <code>toString()</code> representation is an empty + * string. + * + * @throws NumberFormatException if <code>value.toString()</code> can not be parsed as an + * integer. + */ + public static Integer toInt(Object value) + { + return toInt(value, INTEGER_DEFAULT_VALUE); + } + + /** + * Returns the specified value as an integer. Returns <code>defaultValue</code> if + * <code>value</code> is <code>null</code> or its trimmed <code>toString()</code> representation + * is an empty string. + * + * @throws NumberFormatException if <code>value.toString()</code> can not be parsed as an + * integer. + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value instanceof Number) + { + Number number = (Number) value; + return number.intValue(); + } + return isBlank(value) ? defaultValue : new Integer(value.toString()); + } + + /** + * Returns the specified value as a floating-point number. Returns the smallest floating-point + * number if <code>value</code> is <code>null</code> or its trimmed <code>toString()</code> + * representation is an empty string. + * + * @throws NumberFormatException if <code>value.toString()</code> can not be parsed as a + * floating-point number. + */ + public static Double toFloat(Object value) + { + return toFloat(value, DOUBLE_DEFAULT_VALUE); + } + + /** + * Returns the specified value as a floating-point number. Returns <code>defaultValue</code> if + * <code>value</code> is <code>null</code> or its trimmed <code>toString()</code> representation + * is an empty string. + * + * @throws NumberFormatException if <code>value.toString()</code> can not be parsed as a + * floating-point number. + */ + public static Double toFloat(Object value, Double defaultValue) + { + if (value instanceof Number) + { + Number number = (Number) value; + return number.doubleValue(); + } + return isBlank(value) ? defaultValue : new Double(value.toString()); + } + + /** + * Returns <code>thenValue</code> if <code>condition == true</code> otherwise + * <code>elseValue</code> is returned. + * + * @return <code>elseValue</code> if <code>condition == null</code>. + */ + public static Object choose(Boolean condition, Object thenValue, Object elseValue) + { + return condition != null && condition.booleanValue() ? thenValue : elseValue; + } + + /** + * Calculates the sum of the specified values. + * Blank strings or <code>null</code> values in the list are ignored. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + * @throws IllegalArgumentException if the list is empty. + */ + public static Double sum(List<Object> values) + { + List<Double> array = toDoubleArray(values); + String functionName = "sum"; + assertNotEmpty(array, functionName); + double sum = 0.0; + for (double value : array) + { + sum += value; + } + return sum; + } + + /** + * Calculates the mean of the specified values. + * Blank strings or <code>null</code> values in the list are ignored. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + * @throws IllegalArgumentException if the list is empty. + */ + public static Double avg(List<Object> values) + { + List<Double> array = toDoubleArray(values); + assertNotEmpty(array, "avg"); + double sum = 0.0; + for (double value : array) + { + sum += value; + } + return sum / array.size(); + } + + /** + * Calculates the standard deviation of the specified values. + * Blank strings or <code>null</code> values in the list are ignored. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + * @throws IllegalArgumentException if the list is empty. + */ + public static Double stdev(List<Object> values) + { + List<Double> array = toDoubleArray(values); + assertNotEmpty(array, "stdev"); + double s1 = 0.0; + double s2 = 0.0; + for (double value : array) + { + s1 += value; + s2 += value * value; + } + int n = array.size(); + double m1 = s1 / n; + double m2 = s2 / n; + return Math.sqrt(Math.max(0, m2 - m1 * m1)); + } + + + /** + * Calculates the median of the specified values. + * Blank strings or <code>null</code> values in the list are ignored. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + * @throws IllegalArgumentException if the list is empty. + */ + public static Double median(List<Object> values) + { + List<Double> array = toDoubleArray(values); + assertNotEmpty(array, "median"); + Collections.sort(array); + int i = array.size() / 2; + return array.size() % 2 == 0 ? (array.get(i - 1) + array.get(i)) / 2 : array.get(i); + } + + /** + * Calculates the minimum of the specified values. + * Blank strings or <code>null</code> values in the list are ignored. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + * @throws IllegalArgumentException if the list is empty. + */ + public static Double min(List<Object> values) + { + List<Double> array = toDoubleArray(values); + assertNotEmpty(array, "min"); + Collections.sort(array); + return array.get(0); + } + + /** + * Calculates the maximum of the specified values. + * Blank strings or <code>null</code> values in the list are ignored. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + * @throws IllegalArgumentException if the list is empty. + */ + public static Double max(List<Object> values) + { + List<Double> array = toDoubleArray(values); + assertNotEmpty(array, "max"); + Collections.sort(array); + return array.get(array.size() - 1); + } + + /** + * Like sum, but returns the default value if the list is empty. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + */ + public static Double sumOrDefault(List<Object> values, Double defaultValue) + { + if (values.isEmpty()) return defaultValue; + return sum(values); + } + + /** + * Like avg, but returns the default value if the list is empty. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + */ + public static Double avgOrDefault(List<Object> values, Double defaultValue) + { + if (values.isEmpty()) return defaultValue; + return avg(values); + } + + /** + * Like stdev, but returns the default value if the list is empty. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + */ + public static Double stdevOrDefault(List<Object> values, Double defaultValue) + { + if (values.isEmpty()) return defaultValue; + return stdev(values); + } + + + /** + * Like median, but returns the default value if the list is empty. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + */ + public static Double medianOrDefault(List<Object> values, Double defaultValue) + { + if (values.isEmpty()) return defaultValue; + return median(values); + } + + /** + * Like min, but returns the default value if the list is empty. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + */ + public static Double minOrDefault(List<Object> values, Double defaultValue) + { + if (values.isEmpty()) return defaultValue; + return min(values); + } + + /** + * Like max, but returns the default value if the list is empty. + * + * @throws NumberFormatException if an element can not be parsed as a floating-point number. + */ + public static Double maxOrDefault(List<Object> values, Double defaultValue) + { + if (values.isEmpty()) return defaultValue; + return max(values); + } + + + private static boolean isBlank(Object value) + { + return value == null || value.toString().trim().length() == 0; + } + + private static List<Double> toDoubleArray(List<Object> values) + { + ArrayList<Double> list = new ArrayList<Double>(); + for (Object value : values) + { + if (value == null) + { + continue; + } + if (value instanceof Number) + { + Number number = (Number) value; + list.add(number.doubleValue()); + } else + { + String stringValue = value.toString(); + if (StringUtils.isBlank(stringValue) == false) + { + list.add(new Double(stringValue)); + } + } + } + return list; + } + + private static void assertNotEmpty(List<Double> values, String functionName) + { + if (values.isEmpty()) + { + throw new IllegalArgumentException("Argument of function '" + functionName + + "' is an empty array."); + } + } + + private StandardFunctions() + { + } + +} \ No newline at end of file