001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.iso.packager;
020
021import org.jpos.core.ConfigurationException;
022import org.jpos.core.SimpleConfiguration;
023import org.jpos.iso.DatasetFieldPackager;
024import org.jpos.iso.ISOBasePackager;
025import org.jpos.iso.ISOBaseValidator;
026import org.jpos.iso.ISOComponent;
027import org.jpos.iso.ISODatasetPackager;
028import org.jpos.iso.ISOException;
029import org.jpos.iso.ISOFieldPackager;
030import org.jpos.iso.ISOFieldValidator;
031import org.jpos.iso.ISOMsgFieldPackager;
032import org.jpos.iso.ISOMsgFieldValidator;
033import org.jpos.iso.ISOValidator;
034import org.jpos.iso.validator.ISOVException;
035import org.jpos.util.LogEvent;
036import org.jpos.util.Logger;
037import org.xml.sax.Attributes;
038import org.xml.sax.SAXException;
039import org.xml.sax.SAXParseException;
040import org.xml.sax.XMLReader;
041import org.xml.sax.helpers.DefaultHandler;
042import org.xml.sax.helpers.XMLReaderFactory;
043
044import java.io.InputStream;
045import java.util.ArrayList;
046import java.util.List;
047import java.util.Map;
048import java.util.Map.Entry;
049import java.util.Properties;
050import java.util.Stack;
051import java.util.TreeMap;
052
053
054/**
055 * Generic Packager that configure validators too.
056 * <p>Title: jPOS</p>
057 * <p>Description: Java Framework for Financial Systems</p>
058 * <p>Copyright: Copyright (c) 2000 jPOS.org.  All rights reserved.</p>
059 * <p>Company: www.jPOS.org</p>
060 * @author Jose Eduardo Leon
061 * @version 1.0
062 */
063@SuppressWarnings("unchecked")
064public class GenericValidatingPackager extends GenericPackager implements ISOValidator {
065
066    /**
067     * Default constructor.
068     *
069     * @throws ISOException if the underlying packager cannot be initialized
070     */
071    public GenericValidatingPackager(  ) throws ISOException{
072        super();
073    }
074    /**
075     * Constructs a packager loading its field/validator descriptions from an XML file.
076     *
077     * @param fileName XML descriptor file
078     * @throws ISOException if the file cannot be parsed
079     */
080    public GenericValidatingPackager( String fileName ) throws ISOException {
081        super( fileName );
082    }
083    /**
084     * Constructs a packager loading its field/validator descriptions from an XML stream.
085     *
086     * @param stream XML descriptor input stream
087     * @throws ISOException if the stream cannot be parsed
088     */
089    public GenericValidatingPackager (InputStream stream) throws ISOException {
090        super (stream);
091    }
092
093    /**
094     * Convert the ISOFieldPackagers in the Map
095     * to an array of ISOFieldPackagers
096     */
097    private ISOFieldPackager[] makeFieldArray(Map<Integer,ISOFieldPackager> m)
098    {
099        int maxField = 0;
100
101        // First find the largest field number in the Map
102        for (Entry<Integer,ISOFieldPackager> ent :m.entrySet())
103            if (ent.getKey() > maxField)
104                maxField = ent.getKey();
105
106        // Create the array
107        ISOFieldPackager fld[] = new ISOFieldPackager[maxField+1];
108
109        // Populate it
110        for (Entry<Integer,ISOFieldPackager> ent :m.entrySet())
111           fld[ent.getKey()] = ent.getValue();
112        return fld;
113    }
114
115    /**
116     * It define GenericValidatorContentHandler like handler.
117     */
118    public void readFile(String filename) throws org.jpos.iso.ISOException {
119        try {
120            XMLReader reader = XMLReaderFactory.createXMLReader(
121                    System.getProperty( "sax.parser",
122                    "org.apache.crimson.parser.XMLReaderImpl"));
123            reader.setFeature ("http://xml.org/sax/features/validation", true);
124            GenericValidatorContentHandler handler = new GenericValidatorContentHandler();
125            reader.setContentHandler(handler);
126            reader.setErrorHandler(handler);
127            reader.setEntityResolver(new GenericEntityResolver());
128            reader.parse(filename);
129        }
130        catch (Exception e)
131        {
132            e.printStackTrace();
133            throw new ISOException(e);
134        }
135    }
136    @Override
137    public void setGenericPackagerParams ( Attributes atts ) {
138        String maxField  = atts.getValue( "maxValidField" );
139        String emitBmap  = atts.getValue( "emitBitmap" );
140        String bmapfield = atts.getValue( "bitmapField" );
141        if ( maxField != null )
142            maxValidField = Integer.parseInt( maxField );
143        if ( emitBmap != null )
144            emitBitmap = Boolean.valueOf(emitBmap);
145        if ( bmapfield != null )
146            bitmapField = Integer.parseInt( bmapfield );
147    }
148
149    /**
150     * Replaces the message-level validator array.
151     *
152     * @param msgVlds new message-level validators
153     */
154    public void setMsgValidator( ISOBaseValidator[] msgVlds ){
155        this.mvlds = msgVlds;
156    }
157
158    /**
159     * Replaces the field-level validator array.
160     *
161     * @param fvlds new field-level validators
162     */
163    public void setFieldValidator( ISOFieldValidator[] fvlds ){
164        this.fvlds = fvlds;
165    }
166
167    /**
168     * Runs all configured field- and message-level validators against {@code m}.
169     *
170     * @param m component (typically an {@code ISOMsg}) to validate
171     * @return the validated component, possibly with error fields attached
172     * @throws ISOException if validation fails and a validator throws
173     */
174    public ISOComponent validate(ISOComponent m) throws ISOException {
175        LogEvent evt = new LogEvent( this, "validate" );
176        try {
177            ISOComponent c;
178            Map<Object,ISOComponent> fields = m.getChildren();
179            /** Field  validations **/
180            for (ISOValidator val :fvlds) {
181                if ( (c=fields.get (((ISOFieldValidator) val).getFieldId())) != null ){
182                    try {
183                        m.set( val.validate( c ) );
184                    } catch ( ISOVException e ) {
185                        if ( !e.treated() ) {
186                            m.set( e.getErrComponent() );
187                            e.setTreated( true );
188                        }
189                        evt.addMessage( "Component Validation Error." );
190                        throw e;
191                    }
192                }
193            }
194            /** msg validations **/
195            try {
196                for (ISOBaseValidator mval :mvlds)
197                    m = mval.validate( m );
198            }
199            catch (ISOVException ex) {
200                evt.addMessage( "Component Validation Error." );
201                throw ex;
202            }
203            return m;
204        }
205        finally {
206            Logger.log( evt );
207        }
208    }
209
210/*  Values copied from ISOBasePackager
211These can be changes using attributes on the isopackager node */
212    /** Highest valid field number; copied from ISOBasePackager and overridable via XML. */
213    protected  int maxValidField=128;
214    /** Whether the primary bitmap is emitted on pack; overridable via XML. */
215    protected boolean emitBitmap=true;
216    /** Field number that carries the primary bitmap; overridable via XML. */
217    protected int bitmapField=1;
218    /** FieldValidator array. **/
219    protected ISOValidator[] fvlds = {};
220    /** MsgValidator array **/
221    protected ISOBaseValidator[] mvlds = {};
222    /** incr used to put validators in the same hashtable of
223     *  fieldpackagers. packagers will stay on index 1, 2, 3...
224     *  and validators in inc+1, inc+2, inc+3,... **/
225    static final int inc = 500;
226
227
228    /** SAX handler that builds the packager's field-and-validator graph from the descriptor XML. */
229    @SuppressWarnings("unchecked")
230    public class GenericValidatorContentHandler extends DefaultHandler {
231        /** Default constructor; no instance state to initialise. */
232        public GenericValidatorContentHandler() {}
233        @Override
234        public void startDocument(){
235            fieldStack = new Stack<Object>();
236            validatorStack = new Stack<Object>();
237        }
238
239        @Override
240        public void endDocument() throws SAXException {
241            if ( !fieldStack.isEmpty() )
242                throw new SAXException ( "Format error in XML Field Description File" );
243        }
244
245        @Override
246        public void startElement( String namespaceURI, String localName, String qName, Attributes atts )
247                throws SAXException {
248            try {
249                if ( localName.equals( "isopackager" ) ) {
250                    // Stick a new Map on stack to collect the fields
251                    fieldStack.push( new TreeMap() );
252
253                    /** used to insert msg-level validators **/
254                    Map m = new TreeMap();
255                    m.put(VALIDATOR_INDEX, new ArrayList() );
256
257                    validatorStack.push( m );
258                    setGenericPackagerParams ( atts );
259                }
260                if (localName.equals("isofield")){
261                    /** getID global for isofieldvalidator **/
262                    fldID = atts.getValue("id");
263                    String type = atts.getValue("class");
264                    String name = atts.getValue("name");
265                    String size = atts.getValue("length");
266                    String pad  = atts.getValue("pad");
267                    Class c = Class.forName(type);
268                    ISOFieldPackager f;
269                    f = (ISOFieldPackager) c.newInstance();
270                    f.setDescription(name);
271                    f.setLength(Integer.parseInt(size));
272                    f.setPad(Boolean.parseBoolean(pad));
273                    // Insert this new isofield into the Map
274                    // on the top of the stack using the fieldID as the key
275                    Map m = (Map) fieldStack.peek();
276                    m.put(Integer.valueOf(fldID), f);
277                }
278                if ( localName.equals( "isofieldvalidator" ) ){
279                    String type = atts.getValue( "class" );
280                    String breakOnError = atts.getValue( "break-on-error" );
281                    String minLen = atts.getValue( "minlen" );
282                    String maxLen = atts.getValue( "maxlen" );
283                    Class c = Class.forName( type );
284                    ISOFieldValidator v = (ISOFieldValidator)c.newInstance();
285                    if ( breakOnError != null ) v.setBreakOnError(Boolean.valueOf(breakOnError));
286                    if ( minLen != null ) v.setMinLength( Integer.parseInt( minLen ) );
287                    if ( maxLen != null ) v.setMaxLength( Integer.parseInt( maxLen ) );
288                    v.setFieldId( Integer.parseInt(fldID) );
289                    /** insert validator on stack waiting for properties **/
290                    validatorStack.push( v );
291                    validatorStack.push( new Properties() );
292                }
293                if ( localName.equals( "property" ) ){
294                    ((Properties)validatorStack.peek()).setProperty(
295                            atts.getValue( "name" ),
296                            atts.getValue( "value" ) );
297                }
298                if ( localName.equals( "isovalidator" ) ){
299                    String type = atts.getValue( "class" );
300                    String breakOnError = atts.getValue( "break-on-error" );
301                    Class c = Class.forName( type );
302                    ISOBaseValidator v = (ISOBaseValidator)c.newInstance();
303                    if ( breakOnError != null ) v.setBreakOnError(Boolean.valueOf(breakOnError));
304                    /** insert validator on stack waiting for properties **/
305                    validatorStack.push( v );
306                    validatorStack.push( new Properties() );
307                }
308                if ( localName.equals("isofieldpackager") ) {
309                    String id   = atts.getValue("id");
310                    String type = atts.getValue("class");
311                    String name = atts.getValue("name");
312                    String size = atts.getValue("length");
313                    String pad  = atts.getValue("pad");
314/*
315For a isofield packager node push the following fields
316onto the stack.
3171) an Integer indicating the field ID
3182) an instance of the specified ISOFieldPackager class
3193) an instance of the specified ISOBasePackager (msgPackager) class
3204) a Map to collect the subfields
321*/
322                    String packager = atts.getValue("packager");
323                    fieldStack.push(Integer.valueOf(id));
324                    ISOFieldPackager f;
325                    f = (ISOFieldPackager) Class.forName(type).newInstance();
326                    f.setDescription(name);
327                    f.setLength(Integer.parseInt(size));
328                    f.setPad(Boolean.parseBoolean(pad));
329                    fieldStack.push(f);
330                    ISOBasePackager p;
331                    p = (ISOBasePackager) Class.forName(packager).newInstance();
332                    if (p instanceof GenericValidatingPackager){
333                        GenericValidatingPackager gp = (GenericValidatingPackager) p;
334                        gp.setGenericPackagerParams (atts);
335                    }
336                    fieldStack.push(p);
337                    String validator = atts.getValue( "validator" );
338                    ISOBaseValidatingPackager v;
339                    v = (ISOBaseValidatingPackager) Class.forName(validator).newInstance();
340                    validatorStack.push( v );
341                    Map m = new TreeMap();
342                    m.put(VALIDATOR_INDEX, new ArrayList() );
343                    validatorStack.push( m );
344                    fieldStack.push( new TreeMap() );
345                }
346            } catch (Exception ex){
347                throw new SAXException(ex);
348            }
349        }
350
351        /**
352         * Convert the ISOFieldPackagers in the Map
353         * to an array of ISOFieldPackagers
354         */
355        private ISOFieldPackager[] makeFieldPackArray(Map<Integer,ISOFieldPackager> m){
356            int maxField = 0;
357            // First find the largest field number in the Map
358            for (Entry<Integer,ISOFieldPackager> ent :m.entrySet())
359                if (ent.getKey() > maxField)
360                    maxField = ent.getKey();
361            // Create the array
362            ISOFieldPackager fld[] = new ISOFieldPackager[maxField+1];
363            // Populate it
364            for (Entry<Integer,ISOFieldPackager> ent :m.entrySet())
365               fld[ent.getKey()] = ent.getValue();
366            return fld;
367        }
368
369        @Override
370        public void endElement(String namespaceURI, String localName, String qName) {
371            if (localName.equals("isopackager")){
372                Map m  = (Map)fieldStack.pop();
373                setFieldPackager( makeFieldPackArray(m) );
374                m = (Map)validatorStack.pop();
375                setFieldValidator ( makeFieldValidatorArray( m ));
376                setMsgValidator( makeMsgValidatorArray( m ) );
377            }
378            if ( localName.equals( "isofieldvalidator" ) ){
379                /** pop properties **/
380                Properties p = (Properties)validatorStack.pop();
381                SimpleConfiguration cfg = null;
382                if ( !p.entrySet().isEmpty() )
383                    cfg = new SimpleConfiguration( p );
384                /** pop validator and add it to the hash **/
385                ISOFieldValidator f = (ISOFieldValidator)validatorStack.pop();
386                if ( cfg != null ){
387                    try {
388                        f.setConfiguration( cfg );
389                    }
390                    catch (ConfigurationException ex) {
391                        ex.printStackTrace(  );
392                    }
393                }
394                ((Map)validatorStack.peek()).put(Integer.valueOf(fldID), f );
395            }
396            if ( localName.equals( "isovalidator" ) ){
397                /** pop properties **/
398                Properties p = (Properties)validatorStack.pop();
399                SimpleConfiguration cfg = null;
400                if ( !p.entrySet().isEmpty() )
401                    cfg = new SimpleConfiguration( p );
402                /** pop validator and add it to the hash **/
403                ISOBaseValidator v = (ISOBaseValidator)validatorStack.pop();
404                if ( cfg != null ){
405                    try {
406                        v.setConfiguration( cfg );
407                    }
408                    catch (ConfigurationException ex) {
409                        ex.printStackTrace(  );
410                    }
411                }
412                /** add validator to the has **/
413                ((List)((Map)validatorStack.peek()).get(VALIDATOR_INDEX)).add( v );
414            }
415            if (localName.equals("isofieldpackager")){
416                // Pop the 4 entries off the stack in the correct order
417                Map m = (Map)fieldStack.pop();
418                ISOBasePackager msgPackager = (ISOBasePackager) fieldStack.pop();
419                msgPackager.setFieldPackager (makeFieldArray(m));
420                msgPackager.setLogger (getLogger(), "Generic Packager");
421                ISOFieldPackager fieldPackager = (ISOFieldPackager) fieldStack.pop();
422                Integer fno = (Integer) fieldStack.pop();
423                ISOFieldPackager mfp;
424                if (msgPackager instanceof ISODatasetPackager) {
425                    mfp = new DatasetFieldPackager(fieldPackager, (ISODatasetPackager) msgPackager);
426                } else {
427                    mfp = new ISOMsgFieldPackager(fieldPackager, msgPackager);
428                }
429
430                // Add the newly created ISOMsgField packager to the
431                // lower level field stack
432                m=(Map)fieldStack.peek();
433                m.put(fno, mfp);
434                Map val = (Map)validatorStack.pop();
435                ISOBaseValidatingPackager v = (ISOBaseValidatingPackager) validatorStack.pop();
436                v.setFieldValidator( makeFieldValidatorArray ( val ) );
437                v.setMsgValidator( makeMsgValidatorArray ( val ) );
438                ISOMsgFieldValidator mfv = new ISOMsgFieldValidator ( fieldPackager.getDescription(), v );
439                mfv.setFieldId(fno);
440                v.setLogger (getLogger(), "Generic validating Packager");
441                m=(Map)validatorStack.peek();
442                m.put(fno, mfv);
443            }
444        }
445
446        ISOFieldValidator[] makeFieldValidatorArray ( Map<Integer,ISOFieldValidator> m ){
447            List<ISOFieldValidator> l = new ArrayList();
448            // Populate it
449            for (Entry<Integer,ISOFieldValidator> ent :m.entrySet() )
450                if ( ent.getKey() != VALIDATOR_INDEX )
451                    l.add(ent.getValue());
452            // Create the array
453            return l.toArray(new ISOFieldValidator[l.size()]);
454        }
455
456        ISOBaseValidator[] makeMsgValidatorArray ( Map m ){
457            // First find the count
458            List<ISOBaseValidator> l = (List)m.get(VALIDATOR_INDEX);
459            return l.toArray(new ISOBaseValidator[l.size()]);
460        }
461
462        // ErrorHandler Methods
463        @Override
464        public void error (SAXParseException ex) throws SAXException
465        {
466            throw ex;
467        }
468
469        @Override
470        public void fatalError (SAXParseException ex) throws SAXException
471        {
472            throw ex;
473        }
474
475        static final int VALIDATOR_INDEX = -3 ;
476        private Stack<Object> fieldStack, validatorStack;
477        private String fldID;
478
479    }
480}