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.iso.*;
022import org.jpos.util.LogEvent;
023import org.jpos.util.LogSource;
024import org.jpos.util.Logger;
025import org.xml.sax.Attributes;
026import org.xml.sax.InputSource;
027import org.xml.sax.SAXException;
028import org.xml.sax.XMLReader;
029import org.xml.sax.helpers.DefaultHandler;
030import org.xml.sax.helpers.XMLReaderFactory;
031
032import java.io.*;
033import java.util.Stack;
034import java.util.concurrent.locks.Lock;
035import java.util.concurrent.locks.ReentrantLock;
036
037/**
038 * packs/unpacks ISOMsgs from jPOS logs
039 *
040 * @author apr@cs.com.uy
041 * @version $Id$
042 * @see ISOPackager
043 */
044@SuppressWarnings("unchecked")
045public class LogPackager extends DefaultHandler
046                         implements ISOPackager, LogSource
047{
048    /** Logger receiving pack/unpack diagnostic events. */
049    protected Logger logger = null;
050    /** Logger realm associated with this packager. */
051    protected String realm = null;
052    private ByteArrayOutputStream out;
053    private PrintStream p;
054    private XMLReader reader = null;
055    private Stack stk;
056
057    private Lock lock = new ReentrantLock();
058
059    /** XML element name for the outer log wrapper. */
060    public static final String LOG_TAG       = "log";
061    /** XML element name for an ISO message. */
062    public static final String ISOMSG_TAG    = "isomsg";
063    /** XML element name for an ISO field. */
064    public static final String ISOFIELD_TAG  = "field";
065    /** XML attribute carrying the field number. */
066    public static final String ID_ATTR       = "id";
067    /** XML attribute carrying the field value. */
068    public static final String VALUE_ATTR    = "value";
069    /** XML attribute identifying the field type ({@link #TYPE_BINARY}, {@link #TYPE_BITMAP}). */
070    public static final String TYPE_ATTR     = "type";
071    /** Field-type marker: hex-encoded binary value. */
072    public static final String TYPE_BINARY   = "binary";
073    /** Field-type marker: bitmap value. */
074    public static final String TYPE_BITMAP   = "bitmap";
075
076    /**
077     * Constructs the packager and prepares its underlying SAX parser.
078     *
079     * @throws ISOException if the configured SAX parser cannot be instantiated
080     */
081    public LogPackager() throws ISOException {
082        super();
083        out = new ByteArrayOutputStream();
084        p   = new PrintStream(out);
085        stk = new Stack();
086        try {
087            reader = XMLReaderFactory.createXMLReader(
088                System.getProperty( "sax.parser",
089                                    "org.apache.crimson.parser.XMLReaderImpl")
090            );
091            reader.setFeature ("http://xml.org/sax/features/validation",false);
092            reader.setContentHandler(this);
093            reader.setErrorHandler(this);
094        } catch (Exception e) {
095            throw new ISOException (e.toString());
096        }
097    }
098    public byte[] pack (ISOComponent c) throws ISOException {
099        LogEvent evt = new LogEvent (this, "pack");
100        lock.lock();
101        try {
102            if (!(c instanceof ISOMsg))
103                throw new ISOException ("cannot pack "+c.getClass());
104            ISOMsg m = (ISOMsg) c;
105            byte[] b;
106            p.println ("<log>");
107            c.dump (p, " ");
108            p.println ("</log>");
109            b = out.toByteArray();
110            out.reset();
111            if (logger != null)
112                evt.addMessage (m);
113            return b;
114        } catch (ISOException e) {
115            evt.addMessage (e);
116            throw e;
117        } finally {
118            Logger.log(evt);
119            lock.unlock();
120        }
121    }
122
123    public int unpack (ISOComponent c, byte[] b)
124        throws ISOException
125    {
126        LogEvent evt = new LogEvent (this, "unpack");
127        lock.lock();
128        try {
129            if (!(c instanceof ISOMsg))
130                throw new ISOException 
131                    ("Can't call packager on non Composite");
132
133            while (!stk.empty())    // purge from possible previous error
134                stk.pop();
135
136            InputSource src = new InputSource (new ByteArrayInputStream(b));
137            reader.parse (src);
138            if (!stk.empty()) {
139                ISOMsg m = (ISOMsg) c;
140                m.merge ((ISOMsg) stk.pop());
141                if (logger != null)     
142                    evt.addMessage (m);
143            }
144        } catch (ISOException e) {
145            evt.addMessage (e);
146            // throw e;
147        } catch (IOException e) {
148            evt.addMessage (e);
149            // throw new ISOException (e.toString());
150        } catch (SAXException e) {
151            evt.addMessage (e);
152            // throw new ISOException (e.toString());
153        } finally {
154            Logger.log (evt);
155            lock.unlock();
156        }
157        return b.length;
158    }
159
160    public void unpack (ISOComponent c, InputStream in)
161        throws ISOException, IOException
162    {
163        LogEvent evt = new LogEvent (this, "unpack");
164        lock.lock();
165        try {
166            if (!(c instanceof ISOMsg))
167                throw new ISOException 
168                    ("Can't call packager on non Composite");
169
170            while (!stk.empty())    // purge from possible previous error
171                stk.pop();
172
173            reader.parse (new InputSource (in));
174            if (!stk.empty()) {
175                ISOMsg m = (ISOMsg) c;
176                m.merge ((ISOMsg) stk.pop());
177                if (logger != null)     
178                    evt.addMessage (m);
179            }
180        } catch (ISOException e) {
181            evt.addMessage (e);
182            // throw e;
183        } catch (IOException e) {
184            evt.addMessage (e);
185            // throw new ISOException (e.toString());
186        } catch (SAXException e) {
187            evt.addMessage (e);
188            // throw new ISOException (e.toString());
189        } finally {
190            Logger.log (evt);
191            lock.unlock();
192        }
193    }
194
195    public void startElement 
196        (String ns, String name, String qName, Attributes atts)
197        throws SAXException
198    {
199        int fieldNumber = -1;
200        try {
201            String id       = atts.getValue(ID_ATTR);
202            if (id != null) {
203                try {
204                    fieldNumber = Integer.parseInt (id);
205                } catch (NumberFormatException ex) { }
206            }
207            if (name.equals (ISOMSG_TAG)) {
208                if (fieldNumber >= 0) {
209                    if (stk.empty())
210                        throw new SAXException ("inner without outter");
211
212                    ISOMsg inner = new ISOMsg(fieldNumber);
213                    ((ISOMsg)stk.peek()).set (inner);
214                    stk.push (inner);
215                } else {
216                    stk.push (new ISOMsg(0));
217                }
218            } else if (name.equals (ISOFIELD_TAG)) {
219                ISOMsg m     = (ISOMsg) stk.peek();
220                String value = atts.getValue(VALUE_ATTR);
221                String type  = atts.getValue(TYPE_ATTR);
222                if (id == null || value == null)
223                    throw new SAXException ("invalid field");   
224                if (TYPE_BINARY.equals (type)) {
225                    m.set (new ISOBinaryField (
226                        fieldNumber, 
227                            ISOUtil.hex2byte (
228                                value.getBytes(), 0, value.length()/2
229                            )
230                        )
231                    );
232                }
233                else {
234                    m.set (new ISOField (fieldNumber, value));
235                }
236            }
237        } catch (ISOException e) {
238            throw new SAXException 
239                ("ISOException unpacking "+fieldNumber);
240        }
241    }
242
243    public void endElement (String ns, String name, String qname) 
244        throws SAXException
245    {
246        if (name.equals (ISOMSG_TAG)) {
247            ISOMsg m = (ISOMsg) stk.pop();
248            if (stk.empty())
249                stk.push (m); // push outter message
250        }
251    }
252
253    public String getFieldDescription(ISOComponent m, int fldNumber) {
254        return "<notavailable/>";
255    }
256    public String getDescription () {
257        return getClass().getName();
258    }    
259    public void setLogger (Logger logger, String realm) {
260        this.logger = logger;
261        this.realm  = realm;
262    }
263    public String getRealm () {
264        return realm;
265    }
266    public Logger getLogger() {
267        return logger;
268    }
269    public ISOMsg createISOMsg() {
270        return new ISOMsg();
271    }
272}
273