alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Axis 2 example source code file (XMLFaultUtils.java)

This example Axis 2 source code file (XMLFaultUtils.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Axis 2 tags/keywords

arraylist, block, block, iterator, qname, qname, soap, soapfaulttext, string, string, util, webserviceexception, xml, xmlfault, xmlfaultreason, xmlfaultreason

The Axis 2 XMLFaultUtils.java source code

/*
 * 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.axis2.jaxws.message.util;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.impl.llom.OMSourcedElementImpl;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPFault;
import org.apache.axiom.soap.SOAPFaultCode;
import org.apache.axiom.soap.SOAPFaultDetail;
import org.apache.axiom.soap.SOAPFaultNode;
import org.apache.axiom.soap.SOAPFaultReason;
import org.apache.axiom.soap.SOAPFaultRole;
import org.apache.axiom.soap.SOAPFaultSubCode;
import org.apache.axiom.soap.SOAPFaultText;
import org.apache.axiom.soap.SOAPFaultValue;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.message.Block;
import org.apache.axis2.jaxws.message.XMLFault;
import org.apache.axis2.jaxws.message.XMLFaultCode;
import org.apache.axis2.jaxws.message.XMLFaultReason;
import org.apache.axis2.jaxws.message.factory.OMBlockFactory;
import org.apache.axis2.jaxws.message.factory.SAAJConverterFactory;
import org.apache.axis2.jaxws.registry.FactoryRegistry;

import javax.xml.namespace.QName;
import javax.xml.soap.Detail;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPException;
import javax.xml.stream.XMLStreamException;
import javax.xml.ws.WebServiceException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
 * Collection of utilities used by the Message implementation to process XMLFaults.
 *
 * @see XMLFault
 */
public class XMLFaultUtils {


    /**
     * @param envelope javax.xml.soap.SOAPEnvelope
     * @return true if the SOAPEnvelope contains a SOAPFault
     */
    public static boolean isFault(javax.xml.soap.SOAPEnvelope envelope) throws SOAPException {
        javax.xml.soap.SOAPBody body = envelope.getBody();
        if (body != null) {
            return (body.getFault() != null);
        }
        return false;
    }

    /**
     * @param envelope org.apache.axiom.soap.SOAPEnvelope
     * @return true if the SOAPEnvelope contains a SOAPFault
     */
    public static boolean isFault(SOAPEnvelope envelope) {
        SOAPBody body = envelope.getBody();
        if (body != null) {
            return (body.hasFault() || body.getFault() != null);
        }
        return false;
    }


    /**
     * Create an XMLFault object from a SOAPFault and detail Blocks
     *
     * @param soapFault
     * @param detailBlocks
     * @return
     */
    public static XMLFault createXMLFault(SOAPFault soapFault, Block[] detailBlocks)
            throws WebServiceException {

        // Here is a sample comprehensive SOAP 1.2 fault which will help you 
        // understand the structure.
        // <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
        //               xmlns:m="http://www.example.org/timeouts"
        //               xmlns:xml="http://www.w3.org/XML/1998/namespace">
        //   <env:Body>
        //      <env:Fault>
        //        <env:Code>
        //          <env:Value>env:Sender
        //          <env:Subcode>
        //            <env:Value>m:MessageTimeout
        //          </env:Subcode>
        //        </env:Code>
        //        <env:Reason>
        //          <env:Text xml:lang="en">Sender Timeout
        //          <env:Text xml:lang="de">Sender Timeout
        //        </env:Reason>
        //        <env:Node>http://my.example.org/Node
        //        <env:Role>http://my.example.org/Role
        //        <env:Detail>
        //          <m:MaxTime>P5M
        //        </env:Detail>    
        //      </env:Fault>
        //   </env:Body>
        // </env:Envelope>

        // Get the code
        // TODO what if this fails ?  Log a message and treat like a RECEIVER fault ?

        //figureout the soap version
        boolean isSoap11 = soapFault.getNamespace().getNamespaceURI().equals(
                SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);

        SOAPFaultCode soapCode = soapFault.getCode();
        QName codeQName = null;
        if (isSoap11) {
            codeQName = soapCode.getTextAsQName();
        } else {
            codeQName = soapCode.getValue().getTextAsQName();
        }
        XMLFaultCode code = XMLFaultCode.fromQName(codeQName);

        // Get the primary reason text
        // TODO what if this fails
        SOAPFaultReason soapReason = soapFault.getReason();
        String text = null;
        String lang = null;
        List soapTexts = null;
        if (isSoap11) {
            text = soapReason.getText();
        } else {
            soapTexts = soapReason.getAllSoapTexts();
            SOAPFaultText reasonText = (SOAPFaultText)soapTexts.get(0);
            text = reasonText.getText();
            lang = reasonText.getLang();
        }
        XMLFaultReason reason = new XMLFaultReason(text, lang);

        // Construct the XMLFault from the required information (code, reason, detail blocks)
        XMLFault xmlFault = new XMLFault(code, reason, detailBlocks);

        // Add the secondary fault information

        // Get the SubCodes
        SOAPFaultSubCode soapSubCode = soapCode.getSubCode();
        if (soapSubCode != null) {
            List<QName> list = new ArrayList();

            // Walk the nested sub codes and collect the qnames
            while (soapSubCode != null) {
                SOAPFaultValue soapSubCodeValue = soapSubCode.getValue();
                QName qName = soapSubCodeValue.getTextAsQName();
                list.add(qName);
                soapSubCode = soapSubCode.getSubCode();
            }

            // Put the collected sub code qnames onto the xmlFault
            QName[] qNames = new QName[list.size()];
            xmlFault.setSubCodes(list.toArray(qNames));
        }

        // Get the secondary Reasons...the first reason was already saved as the primary reason
        if (soapTexts != null && soapTexts.size() > 1) {
            XMLFaultReason[] secondaryReasons = new XMLFaultReason[soapTexts.size() - 1];
            for (int i = 1; i < soapTexts.size(); i++) {
                SOAPFaultText soapReasonText = (SOAPFaultText)soapTexts.get(i);
                secondaryReasons[i - 1] = new XMLFaultReason(soapReasonText.getText(),
                                                             soapReasonText.getLang());
            }
            xmlFault.setSecondaryReasons(secondaryReasons);
        }

        // Get the Node
        SOAPFaultNode soapNode = soapFault.getNode();
        if (soapNode != null) {
            xmlFault.setNode(soapNode.getText());
        }

        // Get the Role
        SOAPFaultRole soapRole = soapFault.getRole();
        if (soapRole != null) {
            xmlFault.setRole(soapRole.getText());
        }
        return xmlFault;
    }

    /**
     * Create XMLFault
     *
     * @param soapFault
     * @return xmlFault
     * @throws WebServiceException
     */
    public static XMLFault createXMLFault(javax.xml.soap.SOAPFault soapFault)
            throws WebServiceException {
        Block[] detailBlocks = getDetailBlocks(soapFault);
        return createXMLFault(soapFault, detailBlocks);
    }

    /**
     * Create an XMLFault object from a SOAPFault and detail Blocks
     *
     * @param soapFault
     * @param detailBlocks
     * @return
     */
    public static XMLFault createXMLFault(javax.xml.soap.SOAPFault soapFault, Block[] detailBlocks)
            throws WebServiceException {

        // The SOAPFault structure is modeled after SOAP 1.2.  
        // Here is a sample comprehensive SOAP 1.2 fault which will help you understand the
        // structure.
        // <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
        //               xmlns:m="http://www.example.org/timeouts"
        //               xmlns:xml="http://www.w3.org/XML/1998/namespace">
        //   <env:Body>
        //      <env:Fault>
        //        <env:Code>
        //          <env:Value>env:Sender
        //          <env:Subcode>
        //            <env:Value>m:MessageTimeout
        //          </env:Subcode>
        //        </env:Code>
        //        <env:Reason>
        //          <env:Text xml:lang="en">Sender Timeout
        //          <env:Text xml:lang="de">Sender Timeout
        //        </env:Reason>
        //        <env:Node>http://my.example.org/Node
        //        <env:Role>http://my.example.org/Role
        //        <env:Detail>
        //          <m:MaxTime>P5M
        //        </env:Detail>    
        //      </env:Fault>
        //   </env:Body>
        // </env:Envelope>

        // Get the code or default code
        QName codeQName = soapFault.getFaultCodeAsQName();
        XMLFaultCode code = XMLFaultCode.fromQName(codeQName);

        // Get the primary reason text
        // TODO what if this fails
        String text = soapFault.getFaultString();
        Locale locale = soapFault.getFaultStringLocale();
        XMLFaultReason reason = new XMLFaultReason(text, localeToXmlLang(locale));

        // Construct the XMLFault from the required information (code, reason, detail blocks)
        XMLFault xmlFault = new XMLFault(code, reason, detailBlocks);


        boolean isSOAP12 =
                soapFault.getNamespaceURI().equals(SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE);

        // Add the secondary fault information

        // Get the SubCodes
        if (isSOAP12) {
            Iterator it = soapFault.getFaultSubcodes();
            List<QName> list = new ArrayList();
            while (it.hasNext()) {
                QName qName = (QName)it.next();
                list.add(qName);
            }
            if (list.size() > 0) {
                QName[] subCodes = new QName[list.size()];
                subCodes = list.toArray(subCodes);
                xmlFault.setSubCodes(subCodes);
            }
        }

        // Get the secondary Reasons...the first reason was already saved as the primary reason
        if (isSOAP12) {
            try {
                Iterator it = soapFault.getFaultReasonLocales();
                boolean first = true;
                List<XMLFaultReason> list = new ArrayList();
                while (it.hasNext()) {
                    locale = (Locale)it.next();
                    if (first) {
                        first = false;
                    } else {
                        text = soapFault.getFaultReasonText(locale);
                        list.add(new XMLFaultReason(text, localeToXmlLang(locale)));
                    }
                }
                if (list.size() > 0) {
                    XMLFaultReason[] secondaryReasons = new XMLFaultReason[list.size()];
                    secondaryReasons = list.toArray(secondaryReasons);
                    xmlFault.setSecondaryReasons(secondaryReasons);
                }
            } catch (SOAPException se) {
                throw ExceptionFactory.makeWebServiceException(se);
            }
        }

        // Get the Node
        if (isSOAP12) {
            String soapNode = soapFault.getFaultNode();
            if (soapNode != null) {
                xmlFault.setNode(soapNode);
            }
        }

        // Get the Role
        String soapRole = soapFault
                .getFaultActor();  // getFaultActor works for both SOAP 1.1 and SOAP 1.2 per spec
        if (soapRole != null) {
            xmlFault.setRole(soapRole);
        }

        return xmlFault;
    }


    private static Block[] getDetailBlocks(SOAPFault soapFault) throws WebServiceException {
        try {
            Block[] blocks = null;
            SOAPFaultDetail detail = soapFault.getDetail();
            if (detail != null) {
                // Create a block for each element
                OMBlockFactory bf =
                        (OMBlockFactory)FactoryRegistry.getFactory(OMBlockFactory.class);
                ArrayList<Block> list = new ArrayList();
                Iterator it = detail.getChildElements();
                while (it.hasNext()) {
                    OMElement om = (OMElement)it.next();
                    Block b = bf.createFrom(om, null, om.getQName());
                    list.add(b);
                }
                blocks = new Block[list.size()];
                blocks = list.toArray(blocks);
            }
            return blocks;
        } catch (Exception e) {
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }

    private static Block[] getDetailBlocks(javax.xml.soap.SOAPFault soapFault)
            throws WebServiceException {
        try {
            Block[] blocks = null;
            Detail detail = soapFault.getDetail();
            if (detail != null) {
                // Get a SAAJ->OM converter
                SAAJConverterFactory converterFactory = (SAAJConverterFactory)FactoryRegistry
                        .getFactory(SAAJConverterFactory.class);
                SAAJConverter converter = converterFactory.getSAAJConverter();

                // Create a block for each element
                OMBlockFactory bf =
                        (OMBlockFactory)FactoryRegistry.getFactory(OMBlockFactory.class);
                ArrayList<Block> list = new ArrayList();
                Iterator it = detail.getChildElements();
                while (it.hasNext()) {
                    DetailEntry de = (DetailEntry)it.next();
                    OMElement om = converter.toOM(de);
                    Block b = bf.createFrom(om, null, om.getQName());
                    list.add(b);
                }
                blocks = new Block[list.size()];
                blocks = list.toArray(blocks);
            }
            return blocks;
        } catch (Exception e) {
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }

    /**
     * Create a SOAPFault representing the XMLFault and attach it to body. If there are 1 or more
     * detail Blocks on the XMLFault, a SOAPFaultDetail is attached. If ignoreDetailBlocks=false,
     * then OMElements are added to the SOAPFaultDetail. If ignoreDetailBlocks=true, then the Detail
     * Blocks are ignored (this is necessary for XMLSpine processing)
     *
     * @param xmlFault
     * @param body               - Assumes that the body is empty
     * @param ignoreDetailBlocks true or fals
     * @return SOAPFault (which is attached to body)
     */
    public static SOAPFault createSOAPFault(XMLFault xmlFault,
                                            SOAPBody body,
                                            boolean ignoreDetailBlocks) throws WebServiceException {

        // Get the factory and create the soapFault
        SOAPFactory factory = MessageUtils.getSOAPFactory(body);
        SOAPFault soapFault = factory.createSOAPFault(body);
        OMNamespace ns = body.getNamespace();

        // The SOAPFault structure is modeled after SOAP 1.2.  
        // Here is a sample comprehensive SOAP 1.2 fault which will help you understand the
        // structure.
        // <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
        //               xmlns:m="http://www.example.org/timeouts"
        //               xmlns:xml="http://www.w3.org/XML/1998/namespace">
        //   <env:Body>
        //      <env:Fault>
        //        <env:Code>
        //          <env:Value>env:Sender
        //          <env:Subcode>
        //            <env:Value>m:MessageTimeout
        //          </env:Subcode>
        //        </env:Code>
        //        <env:Reason>
        //          <env:Text xml:lang="en">Sender Timeout
        //          <env:Text xml:lang="de">Sender Timeout
        //        </env:Reason>
        //        <env:Node>http://my.example.org/Node
        //        <env:Role>http://my.example.org/Role
        //        <env:Detail>
        //          <m:MaxTime>P5M
        //        </env:Detail>    
        //      </env:Fault>
        //   </env:Body>
        // </env:Envelope>

        boolean isSoap11 = soapFault.getNamespace().getNamespaceURI()
                .equals(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);

        // Set the primary Code Value
        SOAPFaultCode soapCode = factory.createSOAPFaultCode(soapFault);
        QName soapValueQName = xmlFault.getCode().toQName(ns.getNamespaceURI());
        if (isSoap11) {
            soapCode.setText(soapValueQName);
        } else {
            SOAPFaultValue soapValue = factory.createSOAPFaultValue(soapCode);
            soapValue.setText(soapValueQName);
        }

        // Set the primary Reason Text
        SOAPFaultReason soapReason = factory.createSOAPFaultReason(soapFault);
        if (isSoap11) {
            soapReason.setText(xmlFault.getReason().getText());
        } else {
            SOAPFaultText soapText = factory.createSOAPFaultText(soapReason);
            soapText.setText(xmlFault.getReason().getText());
            soapText.setLang(xmlFault.getReason().getLang());
        }

        // Set the Detail and contents of Detail
        Block[] blocks = xmlFault.getDetailBlocks();
        if (blocks != null && blocks.length > 0) {
            SOAPFaultDetail detail = factory.createSOAPFaultDetail(soapFault);
            if (!ignoreDetailBlocks) {
                for (int i = 0; i < blocks.length; i++) {
                    // A Block implements OMDataSource.  So create OMSourcedElements
                    // for each of the Blocks.
                    OMSourcedElementImpl element =
                            new OMSourcedElementImpl(blocks[i].getQName(), factory, blocks[i]);
                    detail.addChild(element);
                }
            }
        }

        // Now set all of the secondary fault information
        // Set the SubCodes
        QName[] subCodes = xmlFault.getSubCodes();
        if (subCodes != null && subCodes.length > 0) {
            OMElement curr = soapCode;
            for (int i = 0; i < subCodes.length; i++) {
                SOAPFaultSubCode subCode = (i == 0) ?
                        factory.createSOAPFaultSubCode((SOAPFaultCode)curr) :
                        factory.createSOAPFaultSubCode((SOAPFaultSubCode)curr);
                SOAPFaultValue soapSubCodeValue = factory.createSOAPFaultValue(subCode);
                soapSubCodeValue.setText(subCodes[i]);
                curr = subCode;
            }
        }

        // Set the secondary reasons and languages
        XMLFaultReason reasons[] = xmlFault.getSecondaryReasons();
        if (reasons != null && reasons.length > 0) {
            for (int i = 0; i < reasons.length; i++) {
                SOAPFaultText soapReasonText = factory.createSOAPFaultText(soapReason);
                soapReasonText.setText(reasons[i].getText());
                soapReasonText.setLang(reasons[i].getLang());
            }
        }

        // Set the Role
        if (xmlFault.getRole() != null) {
            SOAPFaultRole soapRole = factory.createSOAPFaultRole();
            soapRole.setText(xmlFault.getRole());
            soapFault.setRole(soapRole);
        }

        // Set the Node
        if (xmlFault.getNode() != null) {
            SOAPFaultNode soapNode = factory.createSOAPFaultNode();
            soapNode.setText(xmlFault.getNode());
            soapFault.setNode(soapNode);
        }

        return soapFault;

    }


    /**
     * Create a SOAPFault representing the XMLFault. If there are 1 or more detail Blocks on the
     * XMLFault, a SOAPFaultDetail is attached.
     *
     * @param xmlFault
     * @param body
     * @return SOAPFault (which is attached to body)
     */
    public static javax.xml.soap.SOAPFault createSAAJFault(XMLFault xmlFault,
                                                           javax.xml.soap.SOAPBody body)
            throws SOAPException, WebServiceException {

        // Get the factory and create the soapFault
        String protocolNS = body.getNamespaceURI();

        javax.xml.soap.SOAPFault soapFault = body.addFault();

        // The SOAPFault structure is modeled after SOAP 1.2.  
        // Here is a sample comprehensive SOAP 1.2 fault which will help you understand the
        // structure.
        // <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
        //               xmlns:m="http://www.example.org/timeouts"
        //               xmlns:xml="http://www.w3.org/XML/1998/namespace">
        //   <env:Body>
        //      <env:Fault>
        //        <env:Code>
        //          <env:Value>env:Sender
        //          <env:Subcode>
        //            <env:Value>m:MessageTimeout
        //          </env:Subcode>
        //        </env:Code>
        //        <env:Reason>
        //          <env:Text xml:lang="en">Sender Timeout
        //          <env:Text xml:lang="de">Sender Timeout
        //        </env:Reason>
        //        <env:Node>http://my.example.org/Node
        //        <env:Role>http://my.example.org/Role
        //        <env:Detail>
        //          <m:MaxTime>P5M
        //        </env:Detail>    
        //      </env:Fault>
        //   </env:Body>
        // </env:Envelope>

        // Set the primary Code Value
        QName soapValueQName = xmlFault.getCode().toQName(protocolNS);
        String prefix = soapFault.getPrefix();
        String soapValue = null;
        if (prefix == null || prefix.length() == 0) {
            soapValue = soapValueQName.getLocalPart();
        } else {
            soapValue = prefix + ":" + soapValueQName.getLocalPart();
        }
        soapFault.setFaultCode(soapValue);

        // Set the primary Reason Text
        String reasonText = xmlFault.getReason().getText();
        String reasonLang = xmlFault.getReason().getLang();
        Locale locale = (reasonLang != null && reasonLang.length() > 0) ?
                new Locale(reasonLang) :
                Locale.getDefault();
        soapFault.setFaultString(reasonText, locale);

        // Set the Detail and contents of Detail
        Block[] blocks = xmlFault.getDetailBlocks();
        if (blocks != null && blocks.length > 0) {
            Detail detail = soapFault.addDetail();
            // Get a OM->SAAJ converter
            SAAJConverterFactory converterFactory =
                    (SAAJConverterFactory)FactoryRegistry.getFactory(SAAJConverterFactory.class);
            SAAJConverter converter = converterFactory.getSAAJConverter();
            for (int i = 0; i < blocks.length; i++) {
                try {
                    converter.toSAAJ(blocks[i].getOMElement(), detail);
                } catch (XMLStreamException xse) {
                    ExceptionFactory.makeWebServiceException(xse);
                }
            }

        }

        // Now set all of the secondary fault information
        // Set the SubCodes
        QName[] subCodes = xmlFault.getSubCodes();
        if (subCodes != null && subCodes.length > 0 &&
                protocolNS.equals(SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE)) {
            for (int i = 0; i < subCodes.length; i++) {
                soapFault.appendFaultSubcode(subCodes[i]);
            }
        }

        // Set the secondary reasons and languages
        XMLFaultReason reasons[] = xmlFault.getSecondaryReasons();
        if (reasons != null && reasons.length > 0 &&
                protocolNS.equals(SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE)) {
            for (int i = 0; i < reasons.length; i++) {
                if (reasons[i].getLang() == null || reasons[i].getLang().length() == 0) {
                    locale = Locale.getDefault();
                } else {
                    locale = new Locale(reasons[i].getLang());
                }
                soapFault.addFaultReasonText(reasons[i].getText(), locale);
            }
        }

        // Set the Role
        if (xmlFault.getRole() != null) {
            soapFault.setFaultActor(
                    xmlFault.getRole());  // Use Fault actor because it is applicable for SOAP 1.1 and SOAP 1.2
        }

        // Set the Node...only applicable for SOAP 1.2
        if (xmlFault.getNode() != null && protocolNS.equals(SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE))
        {
            soapFault.setFaultRole(xmlFault.getNode());
        }

        return soapFault;

    }
    
    /**
     * Converte a Locale object to an xmlLang String
     * @param locale
     * @return String of the form <locale.getLanguage()>-
     */
    private static String localeToXmlLang(Locale locale) {
        if (locale == null) {
            return Locale.getDefault().getLanguage() + "-" + Locale.getDefault().getCountry();
        }
        String lang = locale.getLanguage();
        String countryCode = locale.getCountry();
        if (countryCode == null || countryCode.length() == 0) {
            return lang;
        } else {
            return new String(lang + "-" + countryCode);
        }
        
    }
}

Other Axis 2 examples (source code examples)

Here is a short list of links related to this Axis 2 XMLFaultUtils.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.