[jboss-jira] [JBoss JIRA] Created: (JBRULES-2402) Null Pointer exception while loading and executing two serialized rules which are in same package

Nagarajan Santhanam (JIRA) jira-events at lists.jboss.org
Mon Jan 11 01:20:30 EST 2010


Null Pointer exception while loading and executing two serialized rules which are in same package
-------------------------------------------------------------------------------------------------

                 Key: JBRULES-2402
                 URL: https://jira.jboss.org/jira/browse/JBRULES-2402
             Project: Drools
          Issue Type: Bug
      Security Level: Public (Everyone can see)
          Components: drools-core  (expert)
    Affects Versions: 5.0.1.FINAL
         Environment: OS : Ubuntu ( 2.6.28-13-server )
Sun Jdk 1.3
            Reporter: Nagarajan Santhanam
            Assignee: Mark Proctor
            Priority: Critical


Hi,

I'm trying to load drts in a serialized fashion and executing it. During execution, I get the error as mentioned below. 

But if I keep the package name different for both the drts, the same code works fine.

Find below the errors and code snippets  :

Files Used :

1. Main.java
2. SchemeEngine.java
3. PurchaseOrder.java
4. PurchaseOrderImpl.java
5. SchemeHelper.java
6. ON_PO_PROD_PACK_PER_DISC.drt
7. ON_PO_PROD_DIS.drt

Main.java

*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package test;

import app.PurchaseOrder;
import app.PurchaseOrderImpl;
import java.util.ArrayList;
import java.util.List;
import scheme.SchemeEngine;

/**
 *
 * @author msuser1
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void testCrazyBug() throws Exception {

//        String[] drts = new String[]{"/ON_PO_PROD_DIS.drt", "/ON_PO_PROD_PACK_PER_DISC.drt", "/ON_PO_PROD_PACK_PER_DISC_using_function.drt"};
//        String[] xls = new String[]{"/ON_PO_PROD_DISC_1.xls", "/ON_PO_PROD_PACK_PER_DISC.xls", "/ON_PO_PROD_PACK_PER_DISC_using_function.xls"};

        String[] drts = new String[]{"/ON_PO_PROD_DIS.drt", "/ON_PO_PROD_PACK_PER_DISC.drt"};
        String[] xls = new String[]{"/ON_PO_PROD_DISC_1.xls", "/ON_PO_PROD_PACK_PER_DISC.xls"};

        PurchaseOrder po = new PurchaseOrderImpl();

        System.out.println("length of drt " + drts.length);
        SchemeEngine engineDirect = new SchemeEngine(PurchaseOrderImpl.class.getClassLoader());
        for (int i = 0; i < drts.length; i++) {
            engineDirect.loadSchemeFromDRT(drts[i], xls[i]);
        }
        System.out.println("Applying Schemes in normal mode");
        engineDirect.applyOnPurchaseOrder(po);

        List serializedList = new ArrayList();
        for (int i = 0; i < drts.length; i++) {
            SchemeEngine engineLocal = new SchemeEngine(PurchaseOrderImpl.class.getClassLoader());
            engineLocal.loadSchemeFromDRT(drts[i], xls[i]);
            byte[] blobBytes = engineLocal.serializeDefinition();
            serializedList.add(blobBytes);
        }

        SchemeEngine engineOptimized = new SchemeEngine(PurchaseOrderImpl.class.getClassLoader());
        for (int i = 0; i < drts.length; i++) {
            engineOptimized.loadSerializedDefinition((byte[]) serializedList.get(i));
        }
        try {
            System.out.println("Applying Schemes in optmized mode");
            engineOptimized.applyOnPurchaseOrder(po);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void main(String args[]) throws Exception {
        testCrazyBug();
    }


}

SchemeEngine.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package scheme;

import app.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseConfiguration;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.DecisionTableConfiguration;
import org.drools.builder.DecisionTableInputType;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.common.DroolsObjectInputStream;
import org.drools.common.DroolsObjectOutputStream;
import org.drools.decisiontable.ExternalSpreadsheetCompiler;
import org.drools.decisiontable.InputType;
import org.drools.definition.KnowledgePackage;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatelessKnowledgeSession;
import org.drools.template.parser.DataListener;
import org.drools.template.parser.TemplateDataListener;

/**
 *
 * @author msuser1
 */
public class SchemeEngine {

    private KnowledgeBase kbase = null;
    private ClassLoader classLoader = null;

    public SchemeEngine(ClassLoader cl) {
        this.classLoader = cl;
        KnowledgeBaseConfiguration kbc = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(new Properties(), this.classLoader);
        kbase = KnowledgeBaseFactory.newKnowledgeBase(kbc);
    }

    public void loadSchemeFromDRL(String drlFileName) {
        DecisionTableConfiguration dtableconfiguration = KnowledgeBuilderFactory.newDecisionTableConfiguration();
        dtableconfiguration.setInputType(DecisionTableInputType.XLS);

        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        kbuilder.add(ResourceFactory.newClassPathResource(drlFileName, SchemeEngine.class),
                ResourceType.DRL);

        if (kbuilder.hasErrors()) {
            System.err.println(kbuilder.getErrors().toString());
        }
        kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
    }

    public void loadSchemeFromXLS(String xlsFileName) {
        DecisionTableConfiguration dtableconfiguration = KnowledgeBuilderFactory.newDecisionTableConfiguration();
        dtableconfiguration.setInputType(DecisionTableInputType.XLS);

        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        kbuilder.add(ResourceFactory.newClassPathResource(xlsFileName, SchemeEngine.class),
                ResourceType.DTABLE, dtableconfiguration);

        if (kbuilder.hasErrors()) {
            System.err.println(kbuilder.getErrors().toString());
        }
        kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
    }

    public void loadSchemeFromDRT(String drtFileName, String xlsFileName) throws Exception {
        loadSchemeFromDRT(drtFileName, xlsFileName, 1, 1);
    }

    public void loadSchemeFromDRT(String drtFileName, String xlsFileName, int startingRow, int startingColumn) throws Exception {
        InputStream drtInput = SchemeEngine.class.getResourceAsStream(drtFileName);
        ByteArrayOutputStream drtOut = new ByteArrayOutputStream();
        writeToStream(drtInput, drtOut);
        InputStream xlsInput = SchemeEngine.class.getResourceAsStream(xlsFileName);
        ByteArrayOutputStream xlsOut = new ByteArrayOutputStream();
        writeToStream(xlsInput, xlsOut);
        loadSchemeFromDRT(drtOut.toByteArray(), xlsOut.toByteArray(), startingRow, startingColumn);
    }

    private void writeToStream(InputStream in, OutputStream out) throws IOException {
        byte[] buf = new byte[4 * 1024];
        int numRead = 0;
        while ((numRead = in.read(buf)) >= 0) {
            out.write(buf, 0, numRead);
        }
    }

    public void loadSchemeFromDRT(byte[] drtContent, byte[] xlsContent) {
        loadSchemeFromDRT(drtContent, xlsContent, 1, 1);
    }

    public void loadSchemeFromDRT(byte[] drtContent, byte[] xlsContent, int startingRow, int startingColumn) {

        final ExternalSpreadsheetCompiler converter = new ExternalSpreadsheetCompiler();
        final List<DataListener> listeners = new ArrayList<DataListener>();
        InputStream in = new ByteArrayInputStream(drtContent);

        TemplateDataListener listener = new TemplateDataListener(3, 1, in);
        listeners.add(listener);
        converter.compile(new ByteArrayInputStream(xlsContent), InputType.XLS, listeners);

        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        kbuilder.add(ResourceFactory.newByteArrayResource(listener.renderDRL().getBytes()),
                ResourceType.DRL);

        if (kbuilder.hasErrors()) {
            System.err.println(kbuilder.getErrors().toString());
        }
        kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
    }

    public byte[] serializeDefinition() {
        DroolsObjectOutputStream dos = null;
        ByteArrayOutputStream bos = null;
        try {
            bos = new ByteArrayOutputStream();
            dos = new DroolsObjectOutputStream(bos);
            dos.writeObject(kbase.getKnowledgePackages());
            return bos.toByteArray();
        } catch (IOException ex) {
            Logger.getLogger(SchemeEngine.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } finally {
            try {
                dos.close();
                bos.close();
            } catch (IOException ex) {
                Logger.getLogger(SchemeEngine.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void loadSerializedDefinition(byte[] serializedContent) {
        try {
            DroolsObjectInputStream dis = null;
            ByteArrayInputStream bis = null;
            bis = new ByteArrayInputStream(serializedContent);
            dis = new DroolsObjectInputStream(bis);
            Collection<KnowledgePackage> defn = (Collection<KnowledgePackage>)dis.readObject();
            kbase.addKnowledgePackages(defn);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(SchemeEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(SchemeEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void applyOnPurchaseOrder(PurchaseOrder purchaseOrder) {
        if (kbase.getKnowledgePackages().size() == 0) {
            throw new RuntimeException("No Schemes are loaded.");
        }
        List objects = new ArrayList();
        objects.add(purchaseOrder);
        if (purchaseOrder.orderDetails() != null && purchaseOrder.orderDetails().size() > 0) {
            Iterator iter = purchaseOrder.orderDetails().iterator();
            while (iter.hasNext()) {
                PurchaseOrderDetail detail = (PurchaseOrderDetail) iter.next();
                objects.add(detail);
            }
        }
        StatelessKnowledgeSession session = kbase.newStatelessKnowledgeSession();
        session.execute(Arrays.asList(objects.toArray()));
    }

    public void applyRule(Object[] objects) {
        if (kbase.getKnowledgePackages().size() == 0) {
            throw new RuntimeException("No Schemes are loaded.");
        }
        StatelessKnowledgeSession session = kbase.newStatelessKnowledgeSession();
        session.execute(Arrays.asList(objects));
    }

    public static void main(String[] args) {
    }
}

PurchaseOrder.java

package app;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */



import java.util.Date;
import java.util.List;

/**
 *
 * @author msuser1
 */
public interface PurchaseOrder {
    public Float orderAmount();
    public Date orderDate();
    public Integer orderDay();
    public List orderDetails();
    public boolean belongsToGeoHierarchy(String nodeName);
    public void addFreeItem(String schemeId, String skuId, int qty);
    public void addFlatDiscount(String SchemeId, Float discountAmt);
    public void addPercentageDiscount(String SchemeId, Float discountPercent);
    public void addFlatDiscountForItem(String SchemeId, String skuId, Float discountAmt);
    public void addPercentageDiscountForItem(String SchemeId, String skuId, Float discountPercent);
    public void addPercentageDiscountPerUomForItem(String SchemeId, String skuId, Float discountPercent);
    public void addInternalGiftVoucher(String SchemeId, Float voucherAmt);
    public void addExternalGiftVoucher(String SchemeId, Float voucherAmt, String externalVendorId);
    public void addInternalGiftVoucherForItem(String SchemeId, String skuId, Float voucherAmt);
    public void addExternalGiftVoucherForItem(String SchemeId, String skuId, Float voucherAmt, String externalVendorId);
    public void addRewardPoint(String SchemeId, int rewardPoint);
    public void addRewardPointForItem(String SchemeId, String skuId, int rewardPoint);
    public void addWeightBasedFlatDiscountPerUomForItem(String SchemeId, String skuId, Float discount_amt, String uomId);
    public Boolean immediatePayment();
    public Boolean regularPurchase();
    public Boolean consignmentPurchase();
    public Boolean cat2ConsignmentPurchase();
    public Boolean cat3ConsignmentPurchase();
    public Boolean intransitPurchase();
    public Boolean skuPresent(String skuId);
    public Integer materialQuantityForItem(String skuId);
    public Boolean schemeApplied(String schemeDefinitionId);
    public void addDetail(PurchaseOrderDetail prd);
}
=========================================================================================================================

PurchaseOrderImpl.java

package app;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author msuser1
 */
public class PurchaseOrderImpl implements PurchaseOrder {
    private List details = new ArrayList();

    public Date orderDate() {
        try {
            DateFormat fmt = new SimpleDateFormat("yyyy-dd-MM");
            Date dt = fmt.parse("2009-06-23");
            return dt;
        } catch (ParseException ex) {
            Logger.getLogger(PurchaseOrderImpl.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public Integer orderDay(){
        return 10;
    }

    public List orderDetails() {
        return details;
    }

    public boolean belongsToGeoHierarchy(String nodeName) {
        return true;
    }

    public void addFreeItem(String schemeId, String skuId, int qty) {
        System.out.println("Add Free Item");
    }

    public void addFlatDiscount(String SchemeId, Long discountAmt) {
        System.out.println("Add Flat Discount");
    }

    public void addPercentageDiscount(String SchemeId, int discountPercent) {
        System.out.println("Add Percentage Discount");
    }

    public void addFlatDiscountForItem(String SchemeId, String skuId, Long discountAmt) {
        System.out.println("Add Flat Discount For Item");
    }

    public void addPercentageDiscountForItem(String SchemeId, String skuId, int discountPercent) {
        System.out.println("Add Percentage Discount For Item");
    }

    public void addPercentageDiscountPerUomForItem(String SchemeId, String skuId, int discountPercent) {
        System.out.println("Add Percentage Discount For Uom For Item");
    }

    public void addInternalGiftVoucher(String SchemeId, Long voucherAmt) {
        System.out.println("Add Internal Gift Voucher");
    }

    public void addExternalGiftVoucher(String SchemeId, Long voucherAmt, String externalVendorId) {
        System.out.println("Add External Gift Voucher");
    }

    public void addInternalGiftVoucherForItem(String SchemeId, String skuId, Long voucherAmt) {
        System.out.println("Add Internal Gift Voucher For Item");
    }

    public void addExternalGiftVoucherForItem(String SchemeId, String skuId, Long voucherAmt, String externalVendorId) {
        System.out.println("Add External Gift Voucher For Item");
    }

    public void addRewardPoint(String SchemeId, int rewardPoint) {
        System.out.println("Add Reward Point");
    }

    public void addRewardPointForItem(String SchemeId, String skuId, int rewardPoint) {
        System.out.println("Add Reward Point For Item");
    }

    public Boolean immediatePayment(){
        return true;
    }

    public Boolean regularPurchase() {
        return true;
    }

    public Boolean consignmentPurchase() {
        return false;
    }

    public Boolean cat2ConsignmentPurchase() {
        return false;
    }

    public Boolean cat3ConsignmentPurchase() {
        return false;
    }

    public Boolean commissionPurchase() {
        return false;
    }

    public Boolean intransitPurchase() {
        return false;
    }

    public Boolean skuPresent(String skuId) {
        return false;
    }

    public Integer materialQuantityForItem(String skuId) {
        return 10;
    }

    public Boolean schemeApplied(String schemeDefinitionId) {
        return false;
    }

    public void addWeightBasedFlatDiscountPerUomForItem(String SchemeId, String skuId, Long discount_amt, String uomId) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    public void addDetail(PurchaseOrderDetail pod)
    {
        details.add(pod);
    }

    public void addFlatDiscount(String SchemeId, Float discountAmt) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addFlatDiscountForItem(String SchemeId, String skuId, Float discountAmt) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Float orderAmount() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addPercentageDiscount(String SchemeId, Float discountPercent) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addPercentageDiscountForItem(String SchemeId, String skuId, Float discountPercent) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addPercentageDiscountPerUomForItem(String SchemeId, String skuId, Float discountPercent) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addInternalGiftVoucher(String SchemeId, Float voucherAmt) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addExternalGiftVoucher(String SchemeId, Float voucherAmt, String externalVendorId) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addInternalGiftVoucherForItem(String SchemeId, String skuId, Float voucherAmt) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addExternalGiftVoucherForItem(String SchemeId, String skuId, Float voucherAmt, String externalVendorId) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addWeightBasedFlatDiscountPerUomForItem(String SchemeId, String skuId, Float discount_amt, String uomId) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}

SchemeHelper.java


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package helper;

import app.PurchaseOrder;
import app.PurchaseOrderDetail;
import java.lang.Float;

/**
 *
 * @author msuser1
 */
public class SchemeHelper {

    public static boolean belongsToProductHierarchy(String skuId, String nodeName) {
        return false; //Sku.belongsToProductHierarchy(nodeName);
    }

    public static Integer materialQuantityForItem(PurchaseOrder po, String skuId) {
        return po.materialQuantityForItem(skuId);
    }

    public static boolean productIdOrProductAncestorMatching(PurchaseOrderDetail poDetail, String productId) {
        System.out.println("Inside the method......................################################");
        boolean result = false;
        try {
            result = poDetail.productId().compareTo(productId) == 0 || poDetail.belongsToAncestorProduct(productId).booleanValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static boolean complexMethod(PurchaseOrderDetail poDetail, String productId, String packId) {
        boolean result = false;
        try {
            result = (poDetail.packId().compareTo(packId) == 0 || poDetail.belongsToAncestorPack(packId).booleanValue()) && (poDetail.productId().compareTo(productId) == 0 || poDetail.belongsToAncestorProduct(productId).booleanValue());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}

ON_PO_PROD_DIS.drt

template header
SchemeID
ProductID
Discount

package app;
import function helper.SchemeHelper.*

template "Scheme"
rule "po_@{SchemeID}_@{row.rowNumber}"
when
    $order : PurchaseOrder()
    $orderDetail : PurchaseOrderDetail($det : this,eval($det.productId().compareTo("@{ProductID}") == 0 || $det.belongsToAncestorProduct("@{ProductID}") == true), purchaseOrder == $order)
then
    float disAmt=0;
    disAmt = $orderDetail.saleableQty() * @{Discount}F;
    System.out.println("Discount amount = @{Discount}..XXXX..........."  + disAmt);
    $order.addFlatDiscountForItem("@{SchemeID}",$orderDetail.skuId(), disAmt);
end

end template

ON_PO_PROD_PACK_PER_DISC.drt

template header
SchemeID
ProductID
PackID
Discount


package app;
import app.*;
import function helper.SchemeHelper.*

template "Scheme"
rule "ON_PO_PROD_PACK_PER_DISC_@{row.rowNumber}"
when
    $order: PurchaseOrder()
    $orderDetail : PurchaseOrderDetail($det : this, eval(
($det.belongsToAncestorPack("@{PackID}") == true || $det.packId().compareTo("@{PackID}") == 0)
 && 
($det.productId().compareTo("@{ProductID}") == 0 || $det.belongsToAncestorProduct("@{ProductID}") == true)
) , purchaseOrder == $order)
then
    float disAmt=0;
    disAmt =  ($orderDetail.materialPrice() * $orderDetail.saleableQty()) * @{Discount}F/100;
    System.out.println("Discount amount = XXXX..........."  + disAmt);
    $order.addFlatDiscountForItem("@{SchemeID}",$orderDetail.skuId(), disAmt);
end

end template





Error :

java.lang.NullPointerException
        at org.drools.base.ClassFieldReader.getIndex(ClassFieldReader.java:78)
        at org.drools.util.LeftTupleIndexHashTable.<init>(LeftTupleIndexHashTable.java:48)
        at org.drools.util.LeftTupleIndexHashTable.<init>(LeftTupleIndexHashTable.java:35)
        at org.drools.common.SingleBetaConstraints.createBetaMemory(SingleBetaConstraints.java:172)
        at org.drools.reteoo.BetaNode.createMemory(BetaNode.java:340)
        at org.drools.common.ConcurrentNodeMemories.createNodeMemory(ConcurrentNodeMemories.java:96)
        at org.drools.common.ConcurrentNodeMemories.getNodeMemory(ConcurrentNodeMemories.java:75)
        at org.drools.common.AbstractWorkingMemory.getNodeMemory(AbstractWorkingMemory.java:1534)
        at org.drools.reteoo.JoinNode.assertLeftTuple(JoinNode.java:103)
        at org.drools.reteoo.CompositeLeftTupleSinkAdapter.doPropagateAssertLeftTuple(CompositeLeftTupleSinkAdapter.java:145)
        at org.drools.reteoo.CompositeLeftTupleSinkAdapter.createAndPropagateAssertLeftTuple(CompositeLeftTupleSinkAdapter.java:57)
        at org.drools.reteoo.LeftInputAdapterNode.assertObject(LeftInputAdapterNode.java:142)
        at org.drools.reteoo.SingleObjectSinkAdapter.propagateAssertObject(SingleObjectSinkAdapter.java:42)
        at org.drools.reteoo.ObjectTypeNode.assertObject(ObjectTypeNode.java:185)
        at org.drools.reteoo.EntryPointNode.assertObject(EntryPointNode.java:146)
        at org.drools.common.AbstractWorkingMemory.insert(AbstractWorkingMemory.java:1046)
        at org.drools.common.AbstractWorkingMemory.insert(AbstractWorkingMemory.java:1001)
        at org.drools.common.AbstractWorkingMemory.insert(AbstractWorkingMemory.java:788)
        at org.drools.impl.StatelessKnowledgeSessionImpl.execute(StatelessKnowledgeSessionImpl.java:259)
        at scheme.SchemeEngine.applyOnPurchaseOrder(SchemeEngine.java:178)
        at test.Main.testCrazyBug(Main.java:55)
        at test.Main.main(Main.java:64)

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: https://jira.jboss.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        



More information about the jboss-jira mailing list