You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by sh...@apache.org on 2016/11/21 08:07:58 UTC

svn commit: r1770621 [3/5] - in /ofbiz/trunk/specialpurpose/pricat: ./ config/ data/ entitydef/ groovyScripts/ groovyScripts/pricat/ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/ofbiz/ src/main/jav...

Added: ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportEncoder.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportEncoder.java?rev=1770621&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportEncoder.java (added)
+++ ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportEncoder.java Mon Nov 21 08:07:57 2016
@@ -0,0 +1,678 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ofbiz.htmlreport.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The ReportEncoder class provides static methods to decode and encode data.<p>
+ * 
+ * The methods in this class are substitutes for <code>java.net.URLEncoder.encode()</code> and
+ * <code>java.net.URLDecoder.decode()</code>.<p>
+ * 
+ * The de- and encoding uses the same coding mechanism as JavaScript, special characters are
+ * replaced with <code>%hex</code> where hex is a two digit hex number.<p>
+ * 
+ * <b>Note:</b> On the client side (browser) instead of using corresponding <code>escape</code>
+ * and <code>unescape</code> JavaScript functions, better use <code>encodeURIComponent</code> and
+ * <code>decodeURIComponent</code> functions which are work properly with unicode characters.
+ * These functions are supported in IE 5.5+ and NS 6+ only.<p>
+ * 
+ */
+public final class ReportEncoder {
+
+    /** Constant for the standard <code>ISO-8859-1</code> encoding. */
+    public static final String ENCODING_ISO_8859_1 = "ISO-8859-1";
+
+    /** Constant for the standard <code>US-ASCII</code> encoding. */
+    public static final String ENCODING_US_ASCII = "US-ASCII";
+
+    /** 
+     * Constant for the standard <code>UTF-8</code> encoding.<p>
+     * 
+     * Default encoding for JavaScript decodeUriComponent methods is <code>UTF-8</code> by w3c standard. 
+     */
+    public static final String ENCODING_UTF_8 = "UTF-8";
+
+    /** The regex pattern to match HTML entities. */
+    private static final Pattern ENTITIY_PATTERN = Pattern.compile("\\&#\\d+;");
+
+    /** The prefix for HTML entities. */
+    private static final String ENTITY_PREFIX = "&#";
+
+    /** The replacement for HTML entity prefix in parameters. */
+    private static final String ENTITY_REPLACEMENT = "$$";
+
+    /** A cache for encoding name lookup. */
+    private static Map<String, String> encodingCache = new HashMap<String, String>(16);
+
+    /** The plus entity. */
+    private static final String PLUS_ENTITY = ENTITY_PREFIX + "043;";
+
+    /**
+     * Constructor.<p>
+     */
+    private ReportEncoder() {
+        // empty
+    }
+
+    /**
+     * Adjusts the given String by making sure all characters that can be displayed 
+     * in the given charset are contained as chars, whereas all other non-displayable
+     * characters are converted to HTML entities.<p> 
+     * 
+     * Just calls {@link #decodeHtmlEntities(String, String)} first and feeds the result
+     * to {@link #encodeHtmlEntities(String, String)}. <p>
+     *  
+     * @param input the input to adjust the HTML encoding for
+     * @param encoding the charset to encode the result with\
+     * 
+     * @return the input with the decoded/encoded HTML entities
+     */
+    public static String adjustHtmlEncoding(String input, String encoding) {
+
+        return encodeHtmlEntities(decodeHtmlEntities(input, encoding), encoding);
+    }
+
+    /**
+     * Changes the encoding of a byte array that represents a String.<p>
+     * 
+     * @param input the byte array to convert
+     * @param oldEncoding the current encoding of the byte array
+     * @param newEncoding the new encoding of the byte array
+     * 
+     * @return the byte array encoded in the new encoding
+     */
+    public static byte[] changeEncoding(byte[] input, String oldEncoding, String newEncoding) {
+
+        if ((oldEncoding == null) || (newEncoding == null)) {
+            return input;
+        }
+        if (oldEncoding.trim().equalsIgnoreCase(newEncoding.trim())) {
+            return input;
+        }
+        byte[] result = input;
+        try {
+            result = (new String(input, oldEncoding)).getBytes(newEncoding);
+        } catch (UnsupportedEncodingException e) {
+            // return value will be input value
+        }
+        return result;
+    }
+
+    /**
+     * Creates a String out of a byte array with the specified encoding, falling back
+     * to the system default in case the encoding name is not valid.<p>
+     * 
+     * Use this method as a replacement for <code>new String(byte[], encoding)</code>
+     * to avoid possible encoding problems.<p>
+     * 
+     * @param bytes the bytes to decode 
+     * @param encoding the encoding scheme to use for decoding the bytes
+     * 
+     * @return the bytes decoded to a String
+     */
+    public static String createString(byte[] bytes, String encoding) {
+
+        String enc = encoding.intern();
+        if (enc != ENCODING_UTF_8) {
+            enc = lookupEncoding(enc, null);
+        }
+        if (enc != null) {
+            try {
+                return new String(bytes, enc);
+            } catch (UnsupportedEncodingException e) {
+                // this can _never_ happen since the charset was looked up first 
+            }
+        } else {
+            enc = ENCODING_UTF_8;
+            try {
+                return new String(bytes, enc);
+            } catch (UnsupportedEncodingException e) {
+                // this can also _never_ happen since the default encoding is always valid
+            }
+        }
+        // this code is unreachable in practice
+        return null;
+    }
+
+    /**
+     * Decodes a String using UTF-8 encoding, which is the standard for http data transmission
+     * with GET ant POST requests.<p>
+     * 
+     * @param source the String to decode
+     * 
+     * @return String the decoded source String
+     */
+    public static String decode(String source) {
+
+        return decode(source, ENCODING_UTF_8);
+    }
+
+    /**
+     * This method is a substitute for <code>URLDecoder.decode()</code>.<p>
+     * 
+     * In case you don't know what encoding to use, set the value of 
+     * the <code>encoding</code> parameter to <code>null</code>. 
+     * This method will then default to UTF-8 encoding, which is probably the right one.<p>
+     * 
+     * @param source The string to decode
+     * @param encoding The encoding to use (if null, the system default is used)
+     * 
+     * @return The decoded source String
+     */
+    public static String decode(String source, String encoding) {
+
+        if (source == null) {
+            return null;
+        }
+        if (encoding != null) {
+            try {
+                return URLDecoder.decode(source, encoding);
+            } catch (java.io.UnsupportedEncodingException e) {
+                // will fallback to default
+            }
+        }
+        // fallback to default decoding
+        try {
+            return URLDecoder.decode(source, ENCODING_UTF_8);
+        } catch (java.io.UnsupportedEncodingException e) {
+            // ignore
+        }
+        return source;
+    }
+
+    /**
+     * Decodes HTML entity references like <code>&amp;#8364;</code> that are contained in the 
+     * String to a regular character, but only if that character is contained in the given 
+     * encodings charset.<p> 
+     * 
+     * @param input the input to decode the HTML entities in
+     * @param encoding the charset to decode the input for
+     * @return the input with the decoded HTML entities
+     * 
+     * @see #encodeHtmlEntities(String, String)
+     */
+    public static String decodeHtmlEntities(String input, String encoding) {
+
+        Matcher matcher = ENTITIY_PATTERN.matcher(input);
+        StringBuffer result = new StringBuffer(input.length());
+        Charset charset = Charset.forName(encoding);
+        CharsetEncoder encoder = charset.newEncoder();
+
+        while (matcher.find()) {
+            String entity = matcher.group();
+            String value = entity.substring(2, entity.length() - 1);
+            int c = Integer.valueOf(value).intValue();
+            if (c < 128) {
+                // first 128 chars are contained in almost every charset
+                entity = new String(new char[] {(char)c});
+                // this is intended as performance improvement since 
+                // the canEncode() operation appears quite CPU heavy
+            } else if (encoder.canEncode((char)c)) {
+                // encoder can encode this char
+                entity = new String(new char[] {(char)c});
+            }
+            matcher.appendReplacement(result, entity);
+        }
+        matcher.appendTail(result);
+        return result.toString();
+    }
+
+    /**
+     * Decodes a string used as parameter in an uri in a way independent of other encodings/decodings applied before.<p>
+     * 
+     * @param input the encoded parameter string
+     * 
+     * @return the decoded parameter string
+     * 
+     * @see #encodeParameter(String)
+     */
+    public static String decodeParameter(String input) {
+
+        String result = ReportStringUtil.substitute(input, ENTITY_REPLACEMENT, ENTITY_PREFIX);
+        return ReportEncoder.decodeHtmlEntities(result, ENCODING_UTF_8);
+    }
+
+    /**
+     * Encodes a String using UTF-8 encoding, which is the standard for http data transmission
+     * with GET ant POST requests.<p>
+     * 
+     * @param source the String to encode
+     * 
+     * @return String the encoded source String
+     */
+    public static String encode(String source) {
+
+        return encode(source, ENCODING_UTF_8);
+    }
+
+    /**
+     * This method is a substitute for <code>URLEncoder.encode()</code>.<p>
+     * 
+     * In case you don't know what encoding to use, set the value of 
+     * the <code>encoding</code> parameter to <code>null</code>. 
+     * This method will then default to UTF-8 encoding, which is probably the right one.<p>
+     * 
+     * @param source the String to encode
+     * @param encoding the encoding to use (if null, the system default is used)
+     * 
+     * @return the encoded source String
+     */
+    public static String encode(String source, String encoding) {
+
+        if (source == null) {
+            return null;
+        }
+        if (encoding != null) {
+            try {
+                return URLEncoder.encode(source, encoding);
+            } catch (java.io.UnsupportedEncodingException e) {
+                // will fallback to default
+            }
+        }
+        // fallback to default encoding
+        try {
+            return URLEncoder.encode(source, ENCODING_UTF_8);
+        } catch (java.io.UnsupportedEncodingException e) {
+            // ignore
+        }
+        return source;
+    }
+
+    /**
+     * Encodes all characters that are contained in the String which can not displayed 
+     * in the given encodings charset with HTML entity references
+     * like <code>&amp;#8364;</code>.<p>
+     * 
+     * This is required since a Java String is 
+     * internally always stored as Unicode, meaning it can contain almost every character, but 
+     * the HTML charset used might not support all such characters.<p>
+     * 
+     * @param input the input to encode for HTML
+     * @param encoding the charset to encode the result with
+     * 
+     * @return the input with the encoded HTML entities
+     * 
+     * @see #decodeHtmlEntities(String, String)
+     */
+    public static String encodeHtmlEntities(String input, String encoding) {
+
+        StringBuffer result = new StringBuffer(input.length() * 2);
+        CharBuffer buffer = CharBuffer.wrap(input.toCharArray());
+        Charset charset = Charset.forName(encoding);
+        CharsetEncoder encoder = charset.newEncoder();
+        for (int i = 0; i < buffer.length(); i++) {
+            int c = buffer.get(i);
+            if (c < 128) {
+                // first 128 chars are contained in almost every charset
+                result.append((char)c);
+                // this is intended as performance improvement since 
+                // the canEncode() operation appears quite CPU heavy
+            } else if (encoder.canEncode((char)c)) {
+                // encoder can encode this char
+                result.append((char)c);
+            } else {
+                // append HTML entity reference
+                result.append(ENTITY_PREFIX);
+                result.append(c);
+                result.append(";");
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Encodes all characters that are contained in the String which can not displayed 
+     * in the given encodings charset with Java escaping like <code>\u20ac</code>.<p>
+     * 
+     * This can be used to escape values used in Java property files.<p>
+     * 
+     * @param input the input to encode for Java
+     * @param encoding the charset to encode the result with
+     * 
+     * @return the input with the encoded Java entities
+     */
+    public static String encodeJavaEntities(String input, String encoding) {
+
+        StringBuffer result = new StringBuffer(input.length() * 2);
+        CharBuffer buffer = CharBuffer.wrap(input.toCharArray());
+        Charset charset = Charset.forName(encoding);
+        CharsetEncoder encoder = charset.newEncoder();
+        for (int i = 0; i < buffer.length(); i++) {
+            int c = buffer.get(i);
+            if (c < 128) {
+                // first 128 chars are contained in almost every charset
+                result.append((char)c);
+                // this is intended as performance improvement since 
+                // the canEncode() operation appears quite CPU heavy
+            } else if (encoder.canEncode((char)c)) {
+                // encoder can encode this char
+                result.append((char)c);
+            } else {
+                // append Java entity reference
+                result.append("\\u");
+                String hex = Integer.toHexString(c);
+                int pad = 4 - hex.length();
+                for (int p = 0; p < pad; p++) {
+                    result.append('0');
+                }
+                result.append(hex);
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Encodes a string used as parameter in an uri in a way independent of other encodings/decodings applied later.<p>
+     * 
+     * Used to ensure that GET parameters are not wrecked by wrong or incompatible configuration settings.
+     * In order to ensure this, the String is first encoded with html entities for any character that cannot encoded
+     * in US-ASCII; additionally, the plus sign is also encoded to avoid problems with the white-space replacer.
+     * Finally, the entity prefix is replaced with characters not used as delimiters in urls.<p>
+     * 
+     * @param input the parameter string
+     * 
+     * @return the encoded parameter string
+     */
+    public static String encodeParameter(String input) {
+
+        String result = ReportEncoder.encodeHtmlEntities(input, ReportEncoder.ENCODING_US_ASCII);
+        result = ReportStringUtil.substitute(result, "+", PLUS_ENTITY);
+        return ReportStringUtil.substitute(result, ENTITY_PREFIX, ENTITY_REPLACEMENT);
+    }
+
+    /**
+     * Encodes a String in a way that is compatible with the JavaScript escape function.
+     * 
+     * @param source The text to be encoded
+     * @param encoding the encoding type
+     * 
+     * @return The JavaScript escaped string
+     */
+    public static String escape(String source, String encoding) {
+
+        // the blank is encoded into "+" not "%20" when using standard encode call
+        return ReportStringUtil.substitute(encode(source, encoding), "+", "%20");
+    }
+
+    /**
+     * Escapes special characters in a HTML-String with their number-based 
+     * entity representation, for example &amp; becomes &amp;#38;.<p>
+     * 
+     * A character <code>num</code> is replaced if<br>
+     * <code>((ch != 32) && ((ch > 122) || (ch < 48) || (ch == 60) || (ch == 62)))</code><p>
+     * 
+     * @param source the String to escape
+     * 
+     * @return String the escaped String
+     * 
+     * @see #escapeXml(String)
+     */
+    public static String escapeHtml(String source) {
+
+        int terminatorIndex;
+        if (source == null) {
+            return null;
+        }
+        StringBuffer result = new StringBuffer(source.length() * 2);
+        for (int i = 0; i < source.length(); i++) {
+            int ch = source.charAt(i);
+            // avoid escaping already escaped characters            
+            if (ch == 38) {
+                terminatorIndex = source.indexOf(";", i);
+                if (terminatorIndex > 0) {
+                    if (source.substring(i + 1, terminatorIndex).matches("#[0-9]+|lt|gt|amp|quote")) {
+                        result.append(source.substring(i, terminatorIndex + 1));
+                        // Skip remaining chars up to (and including) ";"
+                        i = terminatorIndex;
+                        continue;
+                    }
+                }
+            }
+            if ((ch != 32) && ((ch > 122) || (ch < 48) || (ch == 60) || (ch == 62))) {
+                result.append(ENTITY_PREFIX);
+                result.append(ch);
+                result.append(";");
+            } else {
+                result.append((char)ch);
+            }
+        }
+        return new String(result);
+    }
+
+    /**
+     * Escapes non ASCII characters in a HTML-String with their number-based 
+     * entity representation, for example &amp; becomes &amp;#38;.<p>
+     * 
+     * A character <code>num</code> is replaced if<br>
+     * <code>(ch > 255)</code><p>
+     * 
+     * @param source the String to escape
+     * 
+     * @return String the escaped String
+     * 
+     * @see #escapeXml(String)
+     */
+    public static String escapeNonAscii(String source) {
+
+        if (source == null) {
+            return null;
+        }
+        StringBuffer result = new StringBuffer(source.length() * 2);
+        for (int i = 0; i < source.length(); i++) {
+            int ch = source.charAt(i);
+            if (ch > 255) {
+                result.append(ENTITY_PREFIX);
+                result.append(ch);
+                result.append(";");
+            } else {
+                result.append((char)ch);
+            }
+        }
+        return new String(result);
+    }
+
+    /**
+     * Encodes a String in a way that is compatible with the JavaScript escape function.
+     * Multiple blanks are encoded _multiply _with <code>%20</code>.<p>
+     * 
+     * @param source The text to be encoded
+     * @param encoding the encoding type
+     * 
+     * @return The JavaScript escaped string
+     */
+    public static String escapeWBlanks(String source, String encoding) {
+
+        if (ReportStringUtil.isEmpty(source)) {
+            return source;
+        }
+        StringBuffer ret = new StringBuffer(source.length() * 2);
+
+        // URLEncode the text string
+        // this produces a very similar encoding to JavaSscript encoding, 
+        // except the blank which is not encoded into "%20" instead of "+"
+
+        String enc = encode(source, encoding);
+        for (int z = 0; z < enc.length(); z++) {
+            char c = enc.charAt(z);
+            if (c == '+') {
+                ret.append("%20");
+            } else {
+                ret.append(c);
+            }
+        }
+        return ret.toString();
+    }
+
+    /**
+     * Escapes a String so it may be printed as text content or attribute
+     * value in a HTML page or an XML file.<p>
+     * 
+     * This method replaces the following characters in a String:
+     * <ul>
+     * <li><b>&lt;</b> with &amp;lt;
+     * <li><b>&gt;</b> with &amp;gt;
+     * <li><b>&amp;</b> with &amp;amp;
+     * <li><b>&quot;</b> with &amp;quot;
+     * </ul><p>
+     * 
+     * @param source the string to escape
+     * 
+     * @return the escaped string
+     * 
+     * @see #escapeHtml(String)
+     */
+    public static String escapeXml(String source) {
+
+        return escapeXml(source, false);
+    }
+
+    /**
+     * Escapes a String so it may be printed as text content or attribute
+     * value in a HTML page or an XML file.<p>
+     * 
+     * This method replaces the following characters in a String:
+     * <ul>
+     * <li><b>&lt;</b> with &amp;lt;
+     * <li><b>&gt;</b> with &amp;gt;
+     * <li><b>&amp;</b> with &amp;amp;
+     * <li><b>&quot;</b> with &amp;quot;
+     * </ul><p>
+     * 
+     * @param source the string to escape
+     * @param doubleEscape if <code>false</code>, all entities that already are escaped are left untouched
+     * 
+     * @return the escaped string
+     * 
+     * @see #escapeHtml(String)
+     */
+    public static String escapeXml(String source, boolean doubleEscape) {
+
+        if (source == null) {
+            return null;
+        }
+        StringBuffer result = new StringBuffer(source.length() * 2);
+
+        for (int i = 0; i < source.length(); ++i) {
+            char ch = source.charAt(i);
+            switch (ch) {
+                case '<':
+                    result.append("&lt;");
+                    break;
+                case '>':
+                    result.append("&gt;");
+                    break;
+                case '&':
+                    // don't escape already escaped international and special characters
+                    if (!doubleEscape) {
+                        int terminatorIndex = source.indexOf(";", i);
+                        if (terminatorIndex > 0) {
+                            if (source.substring(i + 1, terminatorIndex).matches("#[0-9]+")) {
+                                result.append(ch);
+                                break;
+                            }
+                        }
+                    }
+                    // note that to other "break" in the above "if" block
+                    result.append("&amp;");
+                    break;
+                case '"':
+                    result.append("&quot;");
+                    break;
+                default:
+                    result.append(ch);
+            }
+        }
+        return new String(result);
+    }
+
+    /**
+     * Checks if a given encoding name is actually supported, and if so
+     * resolves it to it's canonical name, if not it returns the given fallback 
+     * value.<p> 
+     * 
+     * Charsets have a set of aliases. For example, valid aliases for "UTF-8"
+     * are "UTF8", "utf-8" or "utf8". This method resolves any given valid charset name 
+     * to it's "canonical" form, so that simple String comparison can be used
+     * when checking charset names internally later.<p>
+     * 
+     * Please see <a href="http://www.iana.org/assignments/character-sets">http://www.iana.org/assignments/character-sets</a> 
+     * for a list of valid charset alias names.<p>
+     * 
+     * @param encoding the encoding to check and resolve
+     * @param fallback the fallback encoding scheme
+     * 
+     * @return the resolved encoding name, or the fallback value
+     */
+    public static String lookupEncoding(String encoding, String fallback) {
+
+        String result = (String) encodingCache.get(encoding);
+        if (result != null) {
+            return result;
+        }
+
+        try {
+            result = Charset.forName(encoding).name();
+            encodingCache.put(encoding, result);
+            return result;
+        } catch (Throwable t) {
+            // we will use the default value as fallback
+        }
+
+        return fallback;
+    }
+
+    /**
+     * Decodes a String in a way that is compatible with the JavaScript 
+     * unescape function.<p>
+     * 
+     * @param source The String to be decoded
+     * @param encoding the encoding type
+     * 
+     * @return The JavaScript unescaped String
+     */
+    public static String unescape(String source, String encoding) {
+
+        if (source == null) {
+            return null;
+        }
+        int len = source.length();
+        // to use standard decoder we need to replace '+' with "%20" (space)
+        StringBuffer preparedSource = new StringBuffer(len);
+        for (int i = 0; i < len; i++) {
+            char c = source.charAt(i);
+            if (c == '+') {
+                preparedSource.append("%20");
+            } else {
+                preparedSource.append(c);
+            }
+        }
+        return decode(preparedSource.toString(), encoding);
+    }
+}
\ No newline at end of file

Added: ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportStringUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportStringUtil.java?rev=1770621&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportStringUtil.java (added)
+++ ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/htmlreport/util/ReportStringUtil.java Mon Nov 21 08:07:57 2016
@@ -0,0 +1,570 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ofbiz.htmlreport.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Provides String utility functions.<p>
+ * 
+ */
+public final class ReportStringUtil {
+
+    /** Constant for <code>"false"</code>. */
+    public static final String FALSE = Boolean.toString(false);
+
+    /** a convenient shorthand to the line separator constant. */
+    public static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /** Contains all chars that end a sentence in the {@link #trimToSize(String, int, int, String)} method. */
+    public static final char[] SENTENCE_ENDING_CHARS = {'.', '!', '?'};
+
+    /** a convenient shorthand for tabulations.  */
+    public static final String TABULATOR = "  ";
+
+    /** Constant for <code>"true"</code>. */
+    public static final String TRUE = Boolean.toString(true);
+
+    /** Day constant. */
+    private static final long DAYS = 1000 * 60 * 60 * 24;
+
+    /** Hour constant. */
+    private static final long HOURS = 1000 * 60 * 60;
+
+    /** Minute constant. */
+    private static final long MINUTES = 1000 * 60;
+
+    /** Second constant. */
+    private static final long SECONDS = 1000;
+
+    /** 
+     * Default constructor (empty), private because this class has only 
+     * static methods.<p>
+     */
+    private ReportStringUtil() {
+        // empty
+    }
+
+    /**
+     * Changes the filename suffix. 
+     * 
+     * @param filename the filename to be changed
+     * @param suffix the new suffix of the file
+     * 
+     * @return the filename with the replaced suffix
+     */
+    public static String changeFileNameSuffixTo(String filename, String suffix) {
+
+        int dotPos = filename.lastIndexOf('.');
+        if (dotPos != -1) {
+            return filename.substring(0, dotPos + 1) + suffix;
+        } else {
+            // the string has no suffix
+            return filename;
+        }
+    }
+
+    /**
+     * Returns a string representation for the given collection using the given separator.<p>
+     * 
+     * @param collection the collection to print
+     * @param separator the item separator
+     * 
+     * @return the string representation for the given collection
+     */
+    public static String collectionAsString(Collection<String> collection, String separator) {
+
+        StringBuffer string = new StringBuffer(128);
+        Iterator<String> it = collection.iterator();
+        while (it.hasNext()) {
+            string.append(it.next());
+            if (it.hasNext()) {
+                string.append(separator);
+            }
+        }
+        return string.toString();
+    }
+
+    /**
+     * Replaces occurrences of special control characters in the given input with 
+     * a HTML representation.<p>
+     * 
+     * This method currently replaces line breaks to <code>&lt;br/&gt;</code> and special HTML chars 
+     * like <code>&lt; &gt; &amp; &quot;</code> with their HTML entity representation.<p>
+     * 
+     * @param source the String to escape
+     * 
+     * @return the escaped String
+     */
+    public static String escapeHtml(String source) {
+
+        if (source == null) {
+            return null;
+        }
+        source = ReportEncoder.escapeXml(source);
+        source = substitute(source, "\r", "");
+        source = substitute(source, "\n", "<br/>\n");
+        return source;
+    }
+
+    /**
+     * Escapes a String so it may be used in JavaScript String definitions.<p>
+     * 
+     * This method replaces line breaks, quotation marks and \ characters.<p>
+     * 
+     * @param source the String to escape
+     * 
+     * @return the escaped String
+     */
+    public static String escapeJavaScript(String source) {
+
+        source = substitute(source, "\\", "\\\\");
+        source = substitute(source, "\"", "\\\"");
+        source = substitute(source, "\'", "\\\'");
+        source = substitute(source, "\r\n", "\\n");
+        source = substitute(source, "\n", "\\n");
+        return source;
+    }
+
+    /**
+     * Escapes a String so it may be used as a Perl5 regular expression.<p>
+     * 
+     * This method replaces the following characters in a String:<br>
+     * <code>{}[]()\$^.*+/</code><p>
+     * 
+     * @param source the string to escape
+     * 
+     * @return the escaped string
+     */
+    public static String escapePattern(String source) {
+
+        if (source == null) {
+            return null;
+        }
+        StringBuffer result = new StringBuffer(source.length() * 2);
+        for (int i = 0; i < source.length(); ++i) {
+            char ch = source.charAt(i);
+            switch (ch) {
+                case '\\':
+                    result.append("\\\\");
+                    break;
+                case '/':
+                    result.append("\\/");
+                    break;
+                case '$':
+                    result.append("\\$");
+                    break;
+                case '^':
+                    result.append("\\^");
+                    break;
+                case '.':
+                    result.append("\\.");
+                    break;
+                case '*':
+                    result.append("\\*");
+                    break;
+                case '+':
+                    result.append("\\+");
+                    break;
+                case '|':
+                    result.append("\\|");
+                    break;
+                case '?':
+                    result.append("\\?");
+                    break;
+                case '{':
+                    result.append("\\{");
+                    break;
+                case '}':
+                    result.append("\\}");
+                    break;
+                case '[':
+                    result.append("\\[");
+                    break;
+                case ']':
+                    result.append("\\]");
+                    break;
+                case '(':
+                    result.append("\\(");
+                    break;
+                case ')':
+                    result.append("\\)");
+                    break;
+                default:
+                    result.append(ch);
+            }
+        }
+        return new String(result);
+    }
+
+    /**
+     * Formats a runtime in the format hh:mm:ss, to be used e.g. in reports.<p>
+     * 
+     * If the runtime is greater then 24 hours, the format dd:hh:mm:ss is used.<p> 
+     * 
+     * @param runtime the time to format
+     * 
+     * @return the formatted runtime
+     */
+    public static String formatRuntime(long runtime) {
+
+        long seconds = (runtime / SECONDS) % 60;
+        long minutes = (runtime / MINUTES) % 60;
+        long hours = (runtime / HOURS) % 24;
+        long days = runtime / DAYS;
+        StringBuffer strBuf = new StringBuffer();
+
+        if (days > 0) {
+            if (days < 10) {
+                strBuf.append('0');
+            }
+            strBuf.append(days);
+            strBuf.append(':');
+        }
+
+        if (hours < 10) {
+            strBuf.append('0');
+        }
+        strBuf.append(hours);
+        strBuf.append(':');
+
+        if (minutes < 10) {
+            strBuf.append('0');
+        }
+        strBuf.append(minutes);
+        strBuf.append(':');
+
+        if (seconds < 10) {
+            strBuf.append('0');
+        }
+        strBuf.append(seconds);
+
+        return strBuf.toString();
+    }
+
+    /**
+     * Returns <code>true</code> if the provided String is either <code>null</code>
+     * or the empty String <code>""</code>.<p> 
+     * 
+     * @param value the value to check
+     * 
+     * @return true, if the provided value is null or the empty String, false otherwise
+     */
+    public static boolean isEmpty(String value) {
+
+        return (value == null) || (value.length() == 0);
+    }
+
+    /**
+     * Returns <code>true</code> if the provided String is either <code>null</code>
+     * or contains only white spaces.<p> 
+     * 
+     * @param value the value to check
+     * 
+     * @return true, if the provided value is null or contains only white spaces, false otherwise
+     */
+    public static boolean isEmptyOrWhitespaceOnly(String value) {
+
+        return isEmpty(value) || (value.trim().length() == 0);
+    }
+
+    /**
+     * Returns <code>true</code> if the provided Objects are either both <code>null</code> 
+     * or equal according to {@link Object#equals(Object)}.<p>
+     * 
+     * @param value1 the first object to compare
+     * @param value2 the second object to compare
+     * 
+     * @return <code>true</code> if the provided Objects are either both <code>null</code> 
+     *              or equal according to {@link Object#equals(Object)} 
+     */
+    public static boolean isEqual(Object value1, Object value2) {
+
+        if (value1 == null) {
+            return (value2 == null);
+        }
+        return value1.equals(value2);
+    }
+
+    /**
+     * Returns <code>true</code> if the provided String is neither <code>null</code>
+     * nor the empty String <code>""</code>.<p> 
+     * 
+     * @param value the value to check
+     * 
+     * @return true, if the provided value is not null and not the empty String, false otherwise
+     */
+    public static boolean isNotEmpty(String value) {
+
+        return (value != null) && (value.length() != 0);
+    }
+
+    /**
+     * Returns <code>true</code> if the provided String is neither <code>null</code>
+     * nor contains only white spaces.<p> 
+     * 
+     * @param value the value to check
+     * 
+     * @return <code>true</code>, if the provided value is <code>null</code> 
+     *          or contains only white spaces, <code>false</code> otherwise
+     */
+    public static boolean isNotEmptyOrWhitespaceOnly(String value) {
+
+        return (value != null) && (value.trim().length() > 0);
+    }
+
+    /**
+     * Returns the last index of any of the given chars in the given source.<p> 
+     * 
+     * If no char is found, -1 is returned.<p>
+     * 
+     * @param source the source to check
+     * @param chars the chars to find
+     * 
+     * @return the last index of any of the given chars in the given source, or -1
+     */
+    public static int lastIndexOf(String source, char[] chars) {
+
+        // now try to find an "sentence ending" char in the text in the "findPointArea"
+        int result = -1;
+        for (int i = 0; i < chars.length; i++) {
+            int pos = source.lastIndexOf(chars[i]);
+            if (pos > result) {
+                // found new last char
+                result = pos;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the last index a whitespace char the given source.<p> 
+     * 
+     * If no whitespace char is found, -1 is returned.<p>
+     * 
+     * @param source the source to check
+     * 
+     * @return the last index a whitespace char the given source, or -1
+     */
+    public static int lastWhitespaceIn(String source) {
+
+        if (isEmpty(source)) {
+            return -1;
+        }
+        int pos = -1;
+        for (int i = source.length() - 1; i >= 0; i--) {
+            if (Character.isWhitespace(source.charAt(i))) {
+                pos = i;
+                break;
+            }
+        }
+        return pos;
+    }
+
+    /**
+     * Substitutes <code>searchString</code> in the given source String with <code>replaceString</code>.<p>
+     * 
+     * This is a high-performance implementation which should be used as a replacement for 
+     * <code>{@link String#replaceAll(java.lang.String, java.lang.String)}</code> in case no
+     * regular expression evaluation is required.<p>
+     * 
+     * @param source the content which is scanned
+     * @param searchString the String which is searched in content
+     * @param replaceString the String which replaces <code>searchString</code>
+     * 
+     * @return the substituted String
+     */
+    public static String substitute(String source, String searchString, String replaceString) {
+
+        if (source == null) {
+            return null;
+        }
+
+        if (isEmpty(searchString)) {
+            return source;
+        }
+
+        if (replaceString == null) {
+            replaceString = "";
+        }
+        int len = source.length();
+        int sl = searchString.length();
+        int rl = replaceString.length();
+        int length;
+        if (sl == rl) {
+            length = len;
+        } else {
+            int c = 0;
+            int s = 0;
+            int e;
+            while ((e = source.indexOf(searchString, s)) != -1) {
+                c++;
+                s = e + sl;
+            }
+            if (c == 0) {
+                return source;
+            }
+            length = len - (c * (sl - rl));
+        }
+
+        int s = 0;
+        int e = source.indexOf(searchString, s);
+        if (e == -1) {
+            return source;
+        }
+        StringBuffer sb = new StringBuffer(length);
+        while (e != -1) {
+            sb.append(source.substring(s, e));
+            sb.append(replaceString);
+            s = e + sl;
+            e = source.indexOf(searchString, s);
+        }
+        e = len;
+        sb.append(source.substring(s, e));
+        return sb.toString();
+    }
+
+    /**
+     * Returns the java String literal for the given String. <p>
+     *  
+     * This is the form of the String that had to be written into source code 
+     * using the unicode escape sequence for special characters. <p> 
+     * 
+     * Example: "�" would be transformed to "\\u00C4".<p>
+     * 
+     * @param s a string that may contain non-ascii characters 
+     * 
+     * @return the java unicode escaped string Literal of the given input string
+     */
+    public static String toUnicodeLiteral(String s) {
+
+        StringBuffer result = new StringBuffer();
+        char[] carr = s.toCharArray();
+
+        String unicode;
+        for (int i = 0; i < carr.length; i++) {
+            result.append("\\u");
+            // append leading zeros
+            unicode = Integer.toHexString(carr[i]).toUpperCase();
+            for (int j = 4 - unicode.length(); j > 0; j--) {
+                result.append("0");
+            }
+            result.append(unicode);
+        }
+        return result.toString();
+    }
+
+    /**
+     * Returns a substring of the source, which is at most length characters long.<p>
+     * 
+     * This is the same as calling {@link #trimToSize(String, int, String)} with the 
+     * parameters <code>(source, length, " ...")</code>.<p>
+     * 
+     * @param source the string to trim
+     * @param length the maximum length of the string to be returned
+     * 
+     * @return a substring of the source, which is at most length characters long
+     */
+    public static String trimToSize(String source, int length) {
+
+        return trimToSize(source, length, length, " ...");
+    }
+
+    /**
+     * Returns a substring of the source, which is at most length characters long.<p>
+     * 
+     * If a char is cut, the given <code>suffix</code> is appended to the result.<p>
+     * 
+     * This is almost the same as calling {@link #trimToSize(String, int, int, String)} with the 
+     * parameters <code>(source, length, length*, suffix)</code>. If <code>length</code>
+     * if larger then 100, then <code>length* = length / 2</code>,
+     * otherwise <code>length* = length</code>.<p>
+     * 
+     * @param source the string to trim
+     * @param length the maximum length of the string to be returned
+     * @param suffix the suffix to append in case the String was trimmed
+     * 
+     * @return a substring of the source, which is at most length characters long
+     */
+    public static String trimToSize(String source, int length, String suffix) {
+
+        int area = (length > 100) ? length / 2 : length;
+        return trimToSize(source, length, area, suffix);
+    }
+
+    /**
+     * Returns a substring of the source, which is at most length characters long, cut 
+     * in the last <code>area</code> chars in the source at a sentence ending char or whitespace.<p>
+     * 
+     * If a char is cut, the given <code>suffix</code> is appended to the result.<p>
+     * 
+     * @param source the string to trim
+     * @param length the maximum length of the string to be returned
+     * @param area the area at the end of the string in which to find a sentence ender or whitespace
+     * @param suffix the suffix to append in case the String was trimmed
+     * 
+     * @return a substring of the source, which is at most length characters long
+     */
+    public static String trimToSize(String source, int length, int area, String suffix) {
+
+        if ((source == null) || (source.length() <= length)) {
+            // no operation is required
+            return source;
+        }
+        if (isEmpty(suffix)) {
+            // we need an empty suffix
+            suffix = "";
+        }
+        // must remove the length from the after sequence chars since these are always added in the end
+        int modLength = length - suffix.length();
+        if (modLength <= 0) {
+            // we are to short, return beginning of the suffix
+            return suffix.substring(0, length);
+        }
+        int modArea = area + suffix.length();
+        if ((modArea > modLength) || (modArea < 0)) {
+            // area must not be longer then max length
+            modArea = modLength;
+        }
+
+        // first reduce the String to the maximum allowed length
+        String findPointSource = source.substring(modLength - modArea, modLength);
+
+        String result;
+        // try to find an "sentence ending" char in the text
+        int pos = lastIndexOf(findPointSource, SENTENCE_ENDING_CHARS);
+        if (pos >= 0) {
+            // found a sentence ender in the lookup area, keep the sentence ender
+            result = source.substring(0, modLength - modArea + pos + 1) + suffix;
+        } else {
+            // no sentence ender was found, try to find a whitespace
+            pos = lastWhitespaceIn(findPointSource);
+            if (pos >= 0) {
+                // found a whitespace, don't keep the whitespace
+                result = source.substring(0, modLength - modArea + pos) + suffix;
+            } else {
+                // not even a whitespace was found, just cut away what's to long
+                result = source.substring(0, modLength) + suffix;
+            }
+        }
+
+        return result;
+    }
+}
\ No newline at end of file

Added: ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/AbstractPricatParser.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/AbstractPricatParser.java?rev=1770621&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/AbstractPricatParser.java (added)
+++ ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/AbstractPricatParser.java Mon Nov 21 08:07:57 2016
@@ -0,0 +1,663 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ofbiz.pricat;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
+import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.ss.util.WorkbookUtil;
+import org.apache.poi.xssf.usermodel.OFBizPricatUtil;
+import org.apache.poi.xssf.usermodel.XSSFAnchor;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFComment;
+import org.apache.poi.xssf.usermodel.XSSFCreationHelper;
+import org.apache.poi.xssf.usermodel.XSSFDrawing;
+import org.apache.poi.xssf.usermodel.XSSFFont;
+import org.apache.poi.xssf.usermodel.XSSFPicture;
+import org.apache.poi.xssf.usermodel.XSSFPictureData;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFShape;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.commons.fileupload.FileItem;
+
+import org.apache.ofbiz.htmlreport.InterfaceReport;
+import org.apache.ofbiz.order.finaccount.FinAccountHelper;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.FileUtil;
+import org.apache.ofbiz.base.util.UtilDateTime;
+import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.UtilValidate;
+import org.apache.ofbiz.entity.Delegator;
+import org.apache.ofbiz.entity.GenericEntityException;
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.entity.condition.EntityCondition;
+import org.apache.ofbiz.entity.condition.EntityOperator;
+import org.apache.ofbiz.entity.util.EntityUtil;
+import org.apache.ofbiz.service.LocalDispatcher;
+import org.apache.ofbiz.service.ServiceUtil;
+
+/**
+ * Abstract class of pricat parser.
+ * 
+ */
+public abstract class AbstractPricatParser implements InterfacePricatParser {
+	
+	public static final String module = AbstractPricatParser.class.getName();
+	
+	protected LocalDispatcher dispatcher;
+	
+	protected Delegator delegator;
+	
+	protected List<FileItem> fileItems;
+	
+	protected File pricatFile;
+	
+	protected String userLoginId;
+	
+	protected GenericValue userLogin;
+	
+	protected String pricatFileVersion;
+	
+	protected String currencyId;
+	
+	protected Map<CellReference, String> errorMessages = new HashMap<CellReference, String>();
+	
+	protected HSSFDataFormatter formatter = new HSSFDataFormatter();
+	
+	protected Map<String, String[]> facilities = new HashMap<String, String[]>();
+	
+    protected HttpSession session;
+    
+    protected List<EntityCondition> basicCategoryConds;
+    
+    protected List<EntityCondition> basicBrandConds;
+    
+    protected String selectedPricatType = DEFAULT_PRICAT_TYPE;
+    
+    protected String selectedFacilityId;
+    
+    protected InterfaceReport report;
+    
+    protected Locale locale;
+    
+    protected long sequenceNum = -1L;
+
+    public AbstractPricatParser(LocalDispatcher dispatcher, Delegator delegator, Locale locale, InterfaceReport report, Map<String, String[]> facilities, File pricatFile, GenericValue userLogin) {
+    	this.dispatcher = dispatcher;
+    	this.delegator = delegator;
+    	this.locale = locale;
+    	this.report = report;
+    	this.userLogin = userLogin;
+    	if (UtilValidate.isNotEmpty(userLogin)) {
+    		this.userLoginId = userLogin.getString("userLoginId");
+    	}
+    	this.facilities = facilities;
+    	this.pricatFile = pricatFile;
+		initBasicConds(UtilMisc.toList(userLogin.getString("partyId")));
+    }
+    
+	public void writeCommentsToFile(XSSFWorkbook workbook, XSSFSheet sheet) {
+		report.println();
+		report.print(UtilProperties.getMessage(resource, "WriteCommentsBackToExcel", locale), InterfaceReport.FORMAT_NOTE);
+		FileOutputStream fos = null;
+		XSSFCreationHelper factory = workbook.getCreationHelper();
+		XSSFFont boldFont = workbook.createFont();
+		boldFont.setFontName("Arial");
+		boldFont.setBold(true);
+		boldFont.setCharSet(134);
+		boldFont.setFontHeightInPoints((short) 9);
+		XSSFFont plainFont = workbook.createFont();
+		plainFont.setFontName("Arial");
+		plainFont.setCharSet(134);
+		plainFont.setFontHeightInPoints((short) 9);
+		
+		XSSFSheet errorSheet = null;
+		if (errorMessages.keySet().size() > 0) {
+			String errorSheetName = UtilDateTime.nowDateString("yyyy-MM-dd HHmm") + " Errors";
+			errorSheetName = WorkbookUtil.createSafeSheetName(errorSheetName);
+			errorSheet = workbook.createSheet(errorSheetName);
+			workbook.setSheetOrder(errorSheetName, 0);
+			workbook.setActiveSheet(workbook.getSheetIndex(errorSheetName));
+			XSSFDrawing drawingPatriarch = errorSheet.getDrawingPatriarch();
+			if (drawingPatriarch == null) {
+				drawingPatriarch = errorSheet.createDrawingPatriarch();
+			}
+			for (int i = 0; i <= getHeaderRowNo(); i++) {
+				XSSFRow newRow = errorSheet.createRow(i);
+				XSSFRow row = sheet.getRow(i);
+				newRow.setHeight(row.getHeight());
+				copyRow(row, newRow, factory, drawingPatriarch);
+			}
+			
+			// copy merged regions
+			for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
+			    CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
+			    if (mergedRegion.getFirstRow() < getHeaderRowNo()) {
+				    errorSheet.addMergedRegion(mergedRegion);
+			    }
+			}
+			
+			// copy images
+			List<XSSFPictureData> pics = workbook.getAllPictures();
+			List<XSSFShape> shapes = sheet.getDrawingPatriarch().getShapes();
+			for (int i = 0; i < shapes.size(); i++) {
+				XSSFShape shape = shapes.get(i);
+				XSSFAnchor anchor = shape.getAnchor();
+				if (shape instanceof XSSFPicture && anchor instanceof XSSFClientAnchor) {
+					XSSFPicture pic = (XSSFPicture) shape;
+					XSSFClientAnchor clientAnchor = (XSSFClientAnchor) anchor;
+					if (clientAnchor.getRow1() < getHeaderRowNo()) {
+						for (int j = 0; j < pics.size(); j++) {
+							XSSFPictureData picture = pics.get(j);
+							if (picture.getPackagePart().getPartName().equals(pic.getPictureData().getPackagePart().getPartName())) {
+								drawingPatriarch.createPicture(clientAnchor, j);
+							}
+						}
+					}
+				}
+			}
+		}
+		
+		try {
+			// set comments in the original sheet
+			XSSFDrawing patriarch = sheet.getDrawingPatriarch();
+			for (CellReference cell : errorMessages.keySet()) {
+				if (cell != null && errorMessages.get(cell) != null) {
+					XSSFComment comment = sheet.getCellComment(new CellAddress(cell.getRow(), cell.getCol()));
+					boolean isNewComment = false;
+					if (comment == null) {
+						XSSFClientAnchor anchor = factory.createClientAnchor();
+						anchor.setDx1(100);
+						anchor.setDx2(100);
+						anchor.setDy1(100);
+						anchor.setDy2(100);
+					    anchor.setCol1(cell.getCol());
+					    anchor.setCol2(cell.getCol() + 4);
+					    anchor.setRow1(cell.getRow());
+					    anchor.setRow2(cell.getRow() + 4);
+					    anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE);
+
+					    comment = patriarch.createCellComment(anchor);
+						isNewComment = true;
+					}
+					XSSFRichTextString rts = factory.createRichTextString("OFBiz PriCat:\n");
+					rts.applyFont(boldFont);
+					rts.append(errorMessages.get(cell), plainFont);
+					comment.setString(rts);
+					comment.setAuthor("Apache OFBiz PriCat");
+					if (isNewComment) {
+						sheet.getRow(cell.getRow()).getCell(cell.getCol()).setCellComment(comment);
+				        OFBizPricatUtil.formatCommentShape(sheet, cell);
+					}
+				}
+			}
+			
+			// set comments in the new error sheet
+			XSSFDrawing errorPatriarch = errorSheet.getDrawingPatriarch();
+			int newRowNum = getHeaderRowNo() + 1;
+			Map<Integer, Integer> rowMapping = new HashMap<Integer, Integer>();
+			for (CellReference cell : errorMessages.keySet()) {
+				if (cell != null && errorMessages.get(cell) != null) {
+					XSSFRow row = sheet.getRow(cell.getRow());
+					Integer rowNum = Integer.valueOf(row.getRowNum());
+					int errorRow = newRowNum;
+					if (rowMapping.containsKey(rowNum)) {
+						errorRow = rowMapping.get(rowNum).intValue();
+					} else {
+						XSSFRow newRow = errorSheet.getRow(errorRow);
+						if (newRow == null) {
+							newRow = errorSheet.createRow(errorRow);
+						}
+						rowMapping.put(rowNum, Integer.valueOf(errorRow));
+						newRow.setHeight(row.getHeight());
+						copyRow(row, newRow, factory, errorPatriarch);
+						newRowNum ++;
+					}
+				}
+			}
+
+			// write to file
+			if (sequenceNum > 0L) {
+				File commentedExcel = FileUtil.getFile(tempFilesFolder + userLoginId + "/" + sequenceNum + ".xlsx");
+				fos = new FileOutputStream(commentedExcel);
+				workbook.write(fos);
+			} else {
+				fos = new FileOutputStream(pricatFile);
+				workbook.write(fos);
+			}
+			fos.flush();
+			fos.close();
+			workbook.close();
+		} catch (FileNotFoundException e) {
+			report.println(e);
+			Debug.logError(e, module);
+		} catch (IOException e) {
+			report.println(e);
+			Debug.logError(e, module);
+		} finally {
+			if (fos != null) {
+				try {
+					fos.close();
+				} catch (IOException e) {
+					Debug.logError(e, module);
+				}
+			}
+			if (workbook != null) {
+				try {
+					workbook.close();
+				} catch (IOException e) {
+					Debug.logError(e, module);
+				}
+			}
+		}
+		report.println(UtilProperties.getMessage(resource, "ok", locale), InterfaceReport.FORMAT_OK);
+		report.println();
+	}
+
+	private void copyRow(XSSFRow sourceRow, XSSFRow targetRow, XSSFCreationHelper factory, XSSFDrawing patriarch) {
+		for (int j = 0; j < sourceRow.getPhysicalNumberOfCells(); j++) {
+			XSSFCell cell = sourceRow.getCell(j);
+			if (cell != null) {
+				XSSFCell newCell = targetRow.createCell(j);
+				int cellType = cell.getCellType();
+				newCell.setCellType(cellType);
+				switch (cellType) {
+					case XSSFCell.CELL_TYPE_BOOLEAN:
+						newCell.setCellValue(cell.getBooleanCellValue());
+						break;
+					case XSSFCell.CELL_TYPE_ERROR:
+						newCell.setCellErrorValue(cell.getErrorCellValue());
+						break;
+					case XSSFCell.CELL_TYPE_FORMULA:
+						newCell.setCellFormula(cell.getCellFormula());
+						break;
+					case XSSFCell.CELL_TYPE_NUMERIC:
+						newCell.setCellValue(cell.getNumericCellValue());
+						break;
+					case XSSFCell.CELL_TYPE_STRING:
+						newCell.setCellValue(cell.getRichStringCellValue());
+						break;
+					default:
+						newCell.setCellValue(formatter.formatCellValue(cell));
+				}
+				if (cell.getCellComment() != null) {
+					XSSFClientAnchor anchor = factory.createClientAnchor();
+					anchor.setDx1(100);
+					anchor.setDx2(100);
+					anchor.setDy1(100);
+					anchor.setDy2(100);
+				    anchor.setCol1(newCell.getColumnIndex());
+				    anchor.setCol2(newCell.getColumnIndex() + 4);
+				    anchor.setRow1(newCell.getRowIndex());
+				    anchor.setRow2(newCell.getRowIndex() + 4);
+				    anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE);
+
+				    XSSFComment comment = patriarch.createCellComment(anchor);
+				    comment.setString(cell.getCellComment().getString());
+					newCell.setCellComment(comment);
+				}
+				newCell.setCellStyle(cell.getCellStyle());
+				newCell.getSheet().setColumnWidth(newCell.getColumnIndex(), cell.getSheet().getColumnWidth(cell.getColumnIndex()));
+			}
+		}
+	}
+
+	public void initBasicConds(List<String> orgPartyIds) {
+		basicCategoryConds = new ArrayList<EntityCondition>();
+		basicCategoryConds.add(EntityCondition.makeCondition("isPublic", "N"));
+		//basicCategoryConds.add(EntityCondition.makeCondition("isDefault", "Y"));
+		
+		basicBrandConds = new ArrayList<EntityCondition>();
+		basicBrandConds.add(EntityCondition.makeCondition("isPublic", "N"));
+		basicBrandConds.add(EntityCondition.makeCondition("productFeatureTypeId", "BRAND"));
+		
+		List<EntityCondition> partyIdConds = new ArrayList<EntityCondition>();
+		for (String orgPartyId : orgPartyIds) {
+			partyIdConds.add(EntityCondition.makeCondition("ownerPartyId", orgPartyId));
+		}
+		if (UtilValidate.isNotEmpty(partyIdConds)) {
+			basicCategoryConds.add(EntityCondition.makeCondition(partyIdConds, EntityOperator.OR));
+			basicBrandConds.add(EntityCondition.makeCondition(partyIdConds, EntityOperator.OR));
+		}
+	}
+
+	public Map<String, Object> updateSkuPrice(String skuId, String ownerPartyId, BigDecimal memberPrice) {
+		return ServiceUtil.returnSuccess();
+	}
+
+	public Map<String, Object> updateColorAndDimension(String productId, String ownerPartyId, String color, String dimension) {
+		Map<String, Object> results = ServiceUtil.returnSuccess();
+		results.put("colorId", "sampleColorId");
+		results.put("dimensionId", "sampleDimensionId");
+		return results;
+	}
+
+	public Map<String, Object> getDimensionIds(String productId, String ownerPartyId, String dimension) {
+		Map<String, Object> results = ServiceUtil.returnSuccess();
+		results.put("dimensionId", "sampleDimensionId");
+		return results;
+	}
+
+	public Map<String, Object> getColorIds(String productId, String ownerPartyId, String color) {
+		Map<String, Object> results = ServiceUtil.returnSuccess();
+		results.put("foundColor", Boolean.TRUE);
+		results.put("colorId", "sampleColorId");
+		return results;
+	}
+
+	public String getBrandId(String brandName, String ownerPartyId) {
+		return "sampleBrandId";
+	}
+
+	public boolean isNumOfSheetsOK(XSSFWorkbook workbook) {
+		report.print(UtilProperties.getMessage(resource, "CheckPricatHasSheet", locale), InterfaceReport.FORMAT_NOTE);
+		int sheets = workbook.getNumberOfSheets();
+		if (sheets < 1) {
+			report.println(UtilProperties.getMessage(resource, "PricatTableNoSheet", locale), InterfaceReport.FORMAT_ERROR);
+			return false;
+		} else if (sheets >= 1) {
+			report.println(UtilProperties.getMessage(resource, "ok", locale), InterfaceReport.FORMAT_OK);
+			report.println(UtilProperties.getMessage(resource, "PricatTableOnlyParse1stSheet", locale), InterfaceReport.FORMAT_WARNING);
+		}
+		return true;
+	}
+
+	/**
+	 * Get data by version definition.
+	 * 
+	 * @param row
+	 * @param colNames 
+	 * @param size 
+	 * @return
+	 */
+	public List<Object> getCellContents(XSSFRow row, List<Object[]> colNames, int size) {
+		List<Object> results = new ArrayList<Object>();
+		boolean foundError = false;
+		if (isEmptyRow(row, size, true)) {
+			return null;
+		}
+		for (int i = 0; i < size; i++) {
+			XSSFCell cell = null;
+			if (row.getPhysicalNumberOfCells() > i) {
+				cell = row.getCell(i);
+			}
+			if (cell == null) {
+				if (((Boolean) colNames.get(i)[2]).booleanValue()) {
+					report.print(UtilProperties.getMessage(resource, "ErrorColCannotEmpty", new Object[] {colNames.get(i)[0]}, locale), InterfaceReport.FORMAT_WARNING);
+					errorMessages.put(new CellReference(cell), UtilProperties.getMessage(resource, "ErrorColCannotEmpty", new Object[] {colNames.get(i)[0]}, locale));
+					foundError = true;
+					continue;
+				} else {
+					cell = row.createCell(i);
+				}
+			}
+			int cellType = cell.getCellType();
+			String cellValue = formatter.formatCellValue(cell);
+			if (UtilValidate.isNotEmpty(cellValue)) {
+				if (cellType == XSSFCell.CELL_TYPE_FORMULA) {
+					cellValue = BigDecimal.valueOf(cell.getNumericCellValue()).setScale(FinAccountHelper.decimals, FinAccountHelper.rounding).toString();
+					report.print(((i == 0)?"":", ") + cellValue, InterfaceReport.FORMAT_NOTE);
+				} else {
+					report.print(((i == 0)?"":", ") + cellValue, InterfaceReport.FORMAT_NOTE);
+				}
+			} else {
+				report.print(((i == 0)?"":","), InterfaceReport.FORMAT_NOTE);
+			}
+			if (((Boolean) colNames.get(i)[2]).booleanValue() && UtilValidate.isEmpty(cellValue)) {
+				report.print(UtilProperties.getMessage(resource, "ErrorColCannotEmpty", new Object[] {colNames.get(i)[0]}, locale), InterfaceReport.FORMAT_WARNING);
+				errorMessages.put(new CellReference(cell), UtilProperties.getMessage(resource, "ErrorColCannotEmpty", new Object[] {colNames.get(i)[0]}, locale));
+				foundError = true;
+				results.add(null);
+				continue;
+			}
+			if (((Boolean) colNames.get(i)[2]).booleanValue() && cellType != (int) colNames.get(i)[1]) {
+				// String warningMessage = "";
+				if ((int) colNames.get(i)[1] == XSSFCell.CELL_TYPE_STRING) {
+					results.add(cellValue);
+				} else if ((int) colNames.get(i)[1] == XSSFCell.CELL_TYPE_NUMERIC) {
+					if (cell.getCellType() != XSSFCell.CELL_TYPE_STRING) {
+						cell.setCellType(XSSFCell.CELL_TYPE_STRING);
+					}
+					try {
+						results.add(BigDecimal.valueOf(Double.parseDouble(cell.getStringCellValue())).setScale(FinAccountHelper.decimals, FinAccountHelper.rounding));
+					} catch (NumberFormatException e) {
+						results.add(null);
+						errorMessages.put(new CellReference(cell), UtilProperties.getMessage(resource, "ErrorParseValueToNumeric", locale));
+					}
+				}
+			} else {
+				if (UtilValidate.isEmpty(cellValue)) {
+					results.add(null);
+					continue;
+				}
+				if ((int) colNames.get(i)[1] == XSSFCell.CELL_TYPE_STRING) {
+					if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) {
+						results.add(cell.getStringCellValue());
+					} else {
+						results.add(cellValue);
+					}
+				} else if ((int) colNames.get(i)[1] == XSSFCell.CELL_TYPE_NUMERIC) {
+					if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) {
+						try {
+							results.add(BigDecimal.valueOf(Double.valueOf(cell.getStringCellValue())));
+						} catch (NumberFormatException e) {
+							results.add(null);
+							errorMessages.put(new CellReference(cell), UtilProperties.getMessage(resource, "ErrorParseValueToNumeric", locale));
+						}
+					} else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) {
+						try {
+							results.add(BigDecimal.valueOf(cell.getNumericCellValue()).setScale(FinAccountHelper.decimals, FinAccountHelper.rounding));
+						} catch (NumberFormatException e) {
+							results.add(null);
+							errorMessages.put(new CellReference(cell), UtilProperties.getMessage(resource, "ErrorParseValueToNumeric", locale));
+						}
+					} else {
+						try {
+							results.add(BigDecimal.valueOf(Double.valueOf(cellValue)).setScale(FinAccountHelper.decimals, FinAccountHelper.rounding));
+						} catch (NumberFormatException e) {
+							results.add(null);
+							errorMessages.put(new CellReference(cell), UtilProperties.getMessage(resource, "ErrorParseValueToNumeric", locale));
+						}
+					}
+				}
+			}
+		}
+		if (foundError) {
+			return null;
+		}
+		return results;
+	}
+
+	public void setFacilityId(String selectedFacilityId) {
+		this.selectedFacilityId = selectedFacilityId;
+	}
+
+	protected boolean isEmptyRow(XSSFRow row, int size, boolean display) {
+		// check whether this row is empty
+		if (UtilValidate.isEmpty(row)) {
+			report.print(UtilProperties.getMessage(resource, "ExcelEmptyRow", locale), InterfaceReport.FORMAT_NOTE);
+			return true;
+		}
+		boolean isEmptyRow = true;
+		int physicalNumberOfCells = row.getPhysicalNumberOfCells();
+		int i = 0;
+		for (; i < size; i++) {
+			XSSFCell cell = null;
+			if (physicalNumberOfCells > i) {
+				cell = row.getCell(i);
+			}
+			if (cell != null && UtilValidate.isNotEmpty(formatter.formatCellValue(cell)) && UtilValidate.isNotEmpty(formatter.formatCellValue(cell).trim())) {
+				isEmptyRow = false;
+				break;
+			}
+		}
+		if (isEmptyRow) {
+			if (display) {
+				report.print(UtilProperties.getMessage(resource, "ExcelEmptyRow", locale), InterfaceReport.FORMAT_NOTE);
+			}
+			return true;
+		} else if (!isEmptyRow && i > size) {
+			if (display) {
+				report.print(UtilProperties.getMessage(resource, "IgnoreDataOutOfRange", locale), InterfaceReport.FORMAT_NOTE);
+			}
+			return true;
+		}
+		return isEmptyRow;
+	}
+	
+	protected abstract int getHeaderRowNo();
+	
+
+	public synchronized void endExcelImportHistory(String logFileName, String thruReasonId) {
+		Thread currentThread = Thread.currentThread();
+		String threadName = null;
+		if (currentThread instanceof PricatParseExcelHtmlThread) {
+			threadName = ((PricatParseExcelHtmlThread) currentThread).getUUID().toString();
+		}
+		if (UtilValidate.isEmpty(threadName)) {
+			return;
+		}
+		try {
+			GenericValue historyValue = null;
+			if (sequenceNum < 1L) {
+				historyValue = EntityUtil.getFirst(EntityUtil.filterByDate(delegator.findByAnd("ExcelImportHistory", 
+													UtilMisc.toMap("userLoginId", userLoginId, "logFileName", logFileName), UtilMisc.toList("sequenceNum DESC"), false)));
+			} else {
+				historyValue = delegator.findOne("ExcelImportHistory", UtilMisc.toMap("userLoginId", userLoginId, "sequenceNum", (Long) sequenceNum), false);
+			}
+			Timestamp now = UtilDateTime.nowTimestamp();
+			if (UtilValidate.isEmpty(historyValue)) {
+				historyValue = delegator.makeValue("ExcelImportHistory", UtilMisc.toMap("sequenceNum", Long.valueOf(sequenceNum), "userLoginId", userLoginId,
+													"fileName", pricatFile.getName(), "statusId", "EXCEL_IMPORTED", "fromDate", now,  
+													"thruDate", now, "threadName", threadName, "logFileName", logFileName));
+			} else {
+				historyValue.set("statusId", "EXCEL_IMPORTED");
+				historyValue.set("thruDate", now);
+				if (pricatFile != null && pricatFile.exists()) {
+					historyValue.set("fileName", pricatFile.getName());
+				}
+				historyValue.set("thruReasonId", thruReasonId);
+			}
+			delegator.createOrStore(historyValue);
+		} catch (GenericEntityException e) {
+			// do nothing
+		}
+	}
+	
+	public boolean hasErrorMessages() {
+		return !errorMessages.keySet().isEmpty();
+	}
+
+	/**
+	 * Check whether a commented file exists.
+	 * 
+	 * @param request
+	 * @param sequenceNum
+	 * @return
+	 */
+	public static boolean isCommentedExcelExists(HttpServletRequest request, Long sequenceNum) {
+	    GenericValue userLogin = (GenericValue) request.getSession().getAttribute("userLogin");
+		if (UtilValidate.isEmpty(sequenceNum) || UtilValidate.isEmpty(userLogin)) {
+			Debug.logError("sequenceNum[" + sequenceNum + "] or userLogin is empty", module);
+			return false;
+		}
+	    String userLoginId = userLogin.getString("userLoginId");
+		Delegator delegator = (Delegator) request.getAttribute("delegator");
+		GenericValue historyValue = null;
+		try {
+			historyValue = delegator.findOne("ExcelImportHistory", UtilMisc.toMap("userLoginId", userLoginId, "sequenceNum", Long.valueOf(sequenceNum)), false);
+		} catch (NumberFormatException e) {
+			Debug.logError(e.getMessage(), module);
+			return false;
+		} catch (GenericEntityException e) {
+			Debug.logError(e.getMessage(), module);
+			return false;
+		}
+		if (UtilValidate.isEmpty(historyValue)) {
+			Debug.logError("No ExcelImportHistory value found by sequenceNum[" + sequenceNum + "] and userLoginId[" + userLoginId + "].", module);
+			return false;
+		}
+		File file = FileUtil.getFile(tempFilesFolder + userLoginId + "/" + sequenceNum + ".xlsx");
+		if (file.exists()) {
+			return true;
+		}
+        return false;
+	}
+
+	protected void cleanupLogAndCommentedExcel() {
+		try {
+			report.print(UtilProperties.getMessage(resource, "CLEANUP_LOGANDEXCEL_BEGIN", locale), InterfaceReport.FORMAT_DEFAULT);
+			List<GenericValue> historyValues = delegator.findByAnd("ExcelImportHistory", UtilMisc.toMap("userLoginId", userLoginId), UtilMisc.toList("sequenceNum DESC"), false);
+			if (UtilValidate.isEmpty(historyValues) || historyValues.size() <= HISTORY_MAX_FILENUMBER) {
+				report.print(UtilProperties.getMessage(resource, "HistoryLessThan", new Object[] {String.valueOf(HISTORY_MAX_FILENUMBER)}, locale), InterfaceReport.FORMAT_NOTE);
+				report.println(" ... " + UtilProperties.getMessage(resource, "skipped", locale), InterfaceReport.FORMAT_NOTE);
+			} else {
+				report.print(" ... " + UtilProperties.getMessage(resource, "HistoryEntryToRemove", new Object[] {historyValues.size() - HISTORY_MAX_FILENUMBER}, locale), InterfaceReport.FORMAT_NOTE);
+				List<GenericValue> valuesToRemove = new ArrayList<GenericValue>();
+				for (int i = HISTORY_MAX_FILENUMBER; i < historyValues.size(); i++) {
+					GenericValue historyValue = historyValues.get(i);
+					valuesToRemove.add(historyValue);
+					File excelFile = FileUtil.getFile(tempFilesFolder + userLoginId + "/" + historyValue.getLong("sequenceNum") + ".xlsx");
+					if (excelFile.exists()) {
+						try {
+							excelFile.delete();
+						} catch (SecurityException e) {
+							Debug.logError(e.getMessage(), module);
+							report.print(e.getMessage(), InterfaceReport.FORMAT_ERROR);
+						}
+					}
+					File logFile = FileUtil.getFile(tempFilesFolder + userLoginId + "/" + historyValue.getLong("sequenceNum") + ".log");
+					if (logFile.exists()) {
+						try {
+							logFile.delete();
+						} catch (SecurityException e) {
+							Debug.logError(e.getMessage(), module);
+							report.print(e.getMessage(), InterfaceReport.FORMAT_ERROR);
+						}
+					}
+				}
+				delegator.removeAll(valuesToRemove);
+				report.println(" ... " + UtilProperties.getMessage(resource, "ok", locale), InterfaceReport.FORMAT_OK);
+			}
+			report.println();
+		} catch (GenericEntityException e) {
+			Debug.logError(e.getMessage(), module);
+		}
+	}
+}

Added: ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/InterfacePricatParser.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/InterfacePricatParser.java?rev=1770621&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/InterfacePricatParser.java (added)
+++ ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/InterfacePricatParser.java Mon Nov 21 08:07:57 2016
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ofbiz.pricat;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.entity.transaction.GenericTransactionException;
+
+/**
+ * Interface of pricat parser.
+ * 
+ */
+public interface InterfacePricatParser {
+	
+	public static final String PARSE_EXCEL = "parse_excel";
+	
+	public static final String CONFIRM = "confirm_action";
+	
+	public static final String[] messageLabels = new String[] {"FORMAT_DEFAULT", "FORMAT_WARNING", "FORMAT_HEADLINE", "FORMAT_NOTE", "FORMAT_OK", "FORMAT_ERROR", "FORMAT_THROWABLE"};
+	
+	public static final List<String> messages = Collections.unmodifiableList(Arrays.asList(messageLabels));
+	
+    public static final String tempFilesFolder = "runtime/pricat/";
+    
+    public static final String FileDateTimePattern = "yyyyMMddHHmmss";
+    
+    public static final String defaultColorName = "DefaultColor";
+    
+    public static final String defaultDimensionName = "DefaultDimension";
+    
+    public static final String defaultCategoryName = "DefaultCategory";
+    
+    public static final String EXCEL_TEMPLATE_TYPE = "excelTemplateType";
+    
+    public static final String FACILITY_ID = "facilityId";
+    
+    public static final String resource = "PricatUiLabels";
+    
+    public static final String PRICAT_FILE = "__PRICAT_FILE__";
+
+    public static final String DEFAULT_PRICAT_TYPE = "ApacheOFBiz";
+    
+    public static final Map<String, String> PricatTypeLabels = UtilMisc.toMap(DEFAULT_PRICAT_TYPE, "ApacheOFBizPricatTemplate", "SamplePricat", "SamplePricatTemplate");
+    
+    public static final int HISTORY_MAX_FILENUMBER = UtilProperties.getPropertyAsInteger("pricat.properties", "pricat.history.max.filenumber", 20);
+    
+	abstract void parsePricatExcel();
+	
+	public void writeCommentsToFile(XSSFWorkbook workbook, XSSFSheet sheet);
+
+	public void initBasicConds(List<String> orgPartyIds);
+
+	public boolean existsCurrencyId(XSSFSheet sheet);
+
+	abstract void parseRowByRow(XSSFSheet sheet);
+
+	abstract boolean parseCellContentsAndStore(XSSFRow row, List<Object> cellContents) throws GenericTransactionException;
+	
+	public Map<String, Object> updateSkuPrice(String skuId, String ownerPartyId, BigDecimal memberPrice);
+
+	abstract String updateSku(XSSFRow row, String productId, String ownerPartyId, String facilityId, String barcode, BigDecimal inventory,
+			String colorId, String color, String dimensionId, String dimension, BigDecimal listPrice, BigDecimal averageCost);
+
+	public Map<String, Object> updateColorAndDimension(String productId, String ownerPartyId, String color, String dimension);
+	
+	public Map<String, Object> getDimensionIds(String productId, String ownerPartyId, String dimension);
+	
+	public Map<String, Object> getColorIds(String productId, String ownerPartyId, String color);
+
+	abstract String getProductId(XSSFRow row, String brandId, String modelName, String productName, String productCategoryId, String ownerPartyId, BigDecimal listPrice);
+
+	public String getBrandId(String brandName, String ownerPartyId);
+
+	abstract Object getCellContent(List<Object> cellContents, String colName);
+
+	abstract String getProductCategoryId(List<Object> cellContents, String ownerPartyId);
+
+	abstract boolean isFacilityOk(XSSFRow row, String facilityName, String facilityId);
+
+	abstract List<Object> getCellContents(XSSFRow row, List<Object[]> colNames, int size);
+
+	abstract boolean isTableHeaderMatched(XSSFSheet sheet);
+
+	abstract boolean isVersionSupported(XSSFSheet sheet);
+
+	abstract boolean containsDataRows(XSSFSheet sheet);
+
+	public boolean isNumOfSheetsOK(XSSFWorkbook workbook);
+
+	abstract void setFacilityId(String selectedFacilityId);
+
+	public void endExcelImportHistory(String logFileName, String thruReasonId);
+	
+	public boolean hasErrorMessages();
+}

Added: ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/PricatEvents.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/PricatEvents.java?rev=1770621&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/PricatEvents.java (added)
+++ ofbiz/trunk/specialpurpose/pricat/src/main/java/org/apache/ofbiz/pricat/PricatEvents.java Mon Nov 21 08:07:57 2016
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ofbiz.pricat;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.jdom.JDOMException;
+import org.apache.ofbiz.base.location.ComponentLocationResolver;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.FileUtil;
+import org.apache.ofbiz.base.util.UtilHttp;
+import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.UtilValidate;
+import org.apache.ofbiz.entity.Delegator;
+import org.apache.ofbiz.entity.GenericEntityException;
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.pricat.AbstractPricatParser;
+import org.apache.ofbiz.pricat.InterfacePricatParser;
+import org.apache.ofbiz.pricat.PricatParseExcelHtmlThread;
+
+public class PricatEvents {
+	
+	public static final String module = PricatEvents.class.getName();
+	
+	public static final String PricatLatestVersion = UtilProperties.getPropertyValue("pricat", "pricat.latest.version", "V1.1");
+	
+	public static final String PricatFileName = "PricatTemplate_" + PricatLatestVersion + ".xlsx";
+	
+	public static final String PricatPath = "component://pricat/webapp/pricat/downloads/";
+	
+    /**
+	 * Download excel template.
+	 * 
+	 * @param request
+	 * @param response
+	 * @return
+	 * @throws IOException
+	 * @throws JDOMException
+	 */
+	public static String downloadExcelTemplate(HttpServletRequest request, HttpServletResponse response) {
+		String templateType = request.getParameter("templateType");
+		if (UtilValidate.isEmpty(templateType)) {
+			return "error";
+		}
+		try {
+			String path = ComponentLocationResolver.getBaseLocation(PricatPath).toString();
+			String fileName = null;
+			if ("pricatExcelTemplate".equals(templateType)) {
+				fileName = PricatFileName;
+			}
+			if (UtilValidate.isEmpty(fileName)) {
+				return "error";
+			}
+	        Path file = Paths.get(path + fileName);
+	        byte[] bytes = Files.readAllBytes(file);
+	        UtilHttp.streamContentToBrowser(response, bytes, "application/octet-stream", URLEncoder.encode(fileName, "UTF-8"));
+		} catch (MalformedURLException e) {
+			Debug.logError(e.getMessage(), module);
+			return "error";
+		} catch (IOException e) {
+			Debug.logError(e.getMessage(), module);
+			return "error";
+		}
+        return "success";
+    }
+	
+	/**
+	 * Upload a pricat.
+	 */
+	public static String pricatUpload(HttpServletRequest request, HttpServletResponse response) {
+		boolean isMultiPart = ServletFileUpload.isMultipartContent(request);
+		if (isMultiPart) {
+			return "parse_pricat";
+		} else {
+			String action = request.getParameter("action");
+			if (UtilValidate.isNotEmpty(action) && "downloadPricat".equals(action)) {
+				String sequenceNumString = (String) request.getParameter("sequenceNum");
+				long sequenceNum = -1;
+				if (UtilValidate.isNotEmpty(sequenceNumString)) {
+					try {
+						sequenceNum = Long.valueOf(sequenceNumString);
+					} catch (NumberFormatException e) {
+						// do nothing
+					}
+				}
+				String originalPricatFileName = (String) request.getSession().getAttribute(PricatParseExcelHtmlThread.PRICAT_FILE);
+				String pricatFileName = originalPricatFileName;
+				if (sequenceNum > 0 && AbstractPricatParser.isCommentedExcelExists(request, sequenceNum)) {
+				    GenericValue userLogin = (GenericValue) request.getSession().getAttribute("userLogin");
+				    String userLoginId = userLogin.getString("userLoginId");
+					pricatFileName = InterfacePricatParser.tempFilesFolder + userLoginId + "/" + sequenceNum + ".xlsx";
+				}
+				if (UtilValidate.isNotEmpty(pricatFileName) && UtilValidate.isNotEmpty(originalPricatFileName)) {
+					try {
+				        Path path = Paths.get(pricatFileName);
+				        byte[] bytes = Files.readAllBytes(path);
+				        path = Paths.get(originalPricatFileName);
+				        UtilHttp.streamContentToBrowser(response, bytes, "application/octet-stream", URLEncoder.encode(path.getName(path.getNameCount() - 1).toString(), "UTF-8"));
+					} catch (MalformedURLException e) {
+						Debug.logError(e.getMessage(), module);
+						return "error";
+					} catch (IOException e) {
+						Debug.logError(e.getMessage(), module);
+						return "error";
+					}
+					request.getSession().removeAttribute(PricatParseExcelHtmlThread.PRICAT_FILE);
+					return "download";
+				}
+		    }
+		}
+        return "success";
+    }
+
+    /**
+	 * Download commented excel file after it's parsed.
+	 * 
+	 * @param request
+	 * @param response
+	 * @return
+	 * @throws IOException
+	 * @throws JDOMException
+	 */
+	public static String downloadCommentedExcel(HttpServletRequest request, HttpServletResponse response) {
+		String sequenceNum = request.getParameter("sequenceNum");
+	    GenericValue userLogin = (GenericValue) request.getSession().getAttribute("userLogin");
+		if (UtilValidate.isEmpty(sequenceNum) || UtilValidate.isEmpty(userLogin)) {
+			Debug.logError("sequenceNum[" + sequenceNum + "] or userLogin is empty", module);
+			return "error";
+		}
+	    String userLoginId = userLogin.getString("userLoginId");
+		Delegator delegator = (Delegator) request.getAttribute("delegator");
+		GenericValue historyValue = null;
+		try {
+			historyValue = delegator.findOne("ExcelImportHistory", UtilMisc.toMap("userLoginId", userLoginId, "sequenceNum", Long.valueOf(sequenceNum)), false);
+		} catch (NumberFormatException e) {
+			Debug.logError(e.getMessage(), module);
+			return "error";
+		} catch (GenericEntityException e) {
+			Debug.logError(e.getMessage(), module);
+			return "error";
+		}
+		if (UtilValidate.isEmpty(historyValue)) {
+			Debug.logError("No ExcelImportHistory value found by sequenceNum[" + sequenceNum + "] and userLoginId[" + userLoginId + "].", module);
+			return "error";
+		}
+		String fileName = historyValue.getString("fileName");
+		if (UtilValidate.isEmpty(fileName)) {
+			fileName = sequenceNum + ".xlsx";
+		}
+		try {
+			File file = FileUtil.getFile(InterfacePricatParser.tempFilesFolder + userLoginId + "/" + sequenceNum + ".xlsx");
+			if (file.exists()) {
+		        Path path = Paths.get(file.getPath());
+		        byte[] bytes = Files.readAllBytes(path);
+		        UtilHttp.streamContentToBrowser(response, bytes, "application/octet-stream", URLEncoder.encode(fileName, "UTF-8"));
+			}
+		} catch (MalformedURLException e) {
+			Debug.logError(e.getMessage(), module);
+			return "error";
+		} catch (IOException e) {
+			Debug.logError(e.getMessage(), module);
+			return "error";
+		}
+        return "success";
+    }
+}