Click here to Skip to main content
15,880,956 members
Articles / Programming Languages / XML

JAXB – A Newcomer’s Perspective, Part 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
26 Aug 2014CPOL15 min read 7K  
In Part 1 of this series, I discussed the basics of loading data from an XML file into a database using JAXB and JPA. (If JSON is called for instead of XML, then the same idea should translate to a tool like Jackson.) The approach is to use shared domain objects – i.e. a single […]

In Part 1 of this series, I discussed the basics of loading data from an XML file into a database using JAXB and JPA. (If JSON is called for instead of XML, then the same idea should translate to a tool like Jackson.) The approach is to use shared domain objects – i.e. a single set of POJOs with annotations that describe both the XML mapping and the relational mapping.

Letting one .java file describe all of the data’s representations makes it easy to write data loaders, unloaders, and translators. In theory it’s simple, but then I alluded to the difference between theory and practice. In theory, there is no difference.

Now in Part 2 ,we’ll look at a few gotchas you can expect to encounter when asking these two tools to work together over a realistic data model, and techniques you might employ to overcome those hurdles.

What’s In a Name?

This first point might be obvious, but I’ll mention it anyway: As with any tool that relies on the bean property conventions, JAXB is sensitive to your method names. You could avoid the issue by configuring direct field access, but as we’ll see shortly, there may be reasons you’d want to stick with property access.

The property name determines the default tag name of the corresponding element (though this can be overridden with annotations – such as @XmlElement in the simplest case). More importantly, your getter and setter names must match. The best advice, of course, is to let your IDE generate the getter and setter so that typos won’t be an issue.

Dealing with @EmbeddedId

Suppose you want to load some data representing orders. Each order might have multiple line items, with the line items for each order numbered sequentially from 1 so that the unique ID across all line items would be the combination of the order ID and the line item number. Assuming you use the @EmbeddedId approach to representing the key, your line items might be represented like this:

@Embeddable
public class LineItemKey {
	private Integer orderId;
	private Integer itemNumber;

	/* … getters and setters … */
}

@XmlRootElement
@Entity
@Table(name="ORDER_ITEM")
public class OrderLineItem {
	@EmbeddedId
	@AttributeOverrides(/*…*/)
	private LineItemKey lineItemKey;

	@Column(name="PART_NUM")
	private String partNumber;

	private Integer quantity;

	// … getters and setters …
};

The marshalling and unmarshalling code will look a lot like that from the Employee example in Part 1. Note that we don’t have to explicitly tell JAXBContext about the LineItemKey class because it is referenced by OrderLineItem.

LineItemKey liKey = new LineItemKey();
liKey.setOrderId(37042);
liKey.setItemNumber(1);

OrderLineItem lineItem = new OrderLineItem();
lineItem.setLineItemKey(liKey);
lineItem.setPartNumber("100-02");
lineItem.setQuantity(10);

JAXBContext jaxb = JAXBContext.newInstance(OrderLineItem.class);
Marshaller marshaller = jaxb.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(lineItem, System.out);

However, we may not be thrilled with the resulting XML structure:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<orderLineItem>
	<lineItemKey>
		<itemNumber>1</itemNumber>
		<orderId>37042</orderId>
	</lineItemKey>
	<partNumber>100-02</partNumber>
	<quantity>10</quantity>
</orderLineItem>

What if we don’t want the <lineItemKey> element? If we have JAXB using property access, then one option is to change our property definitions (i.e. our getters and setters), making OrderLineItem look like a flat object to JAXB (and potentially to the rest of our app; which could be a good thing).

@XmlRootElement
@Entity
@Table(name="ORDER_ITEM")
public class OrderLineItem {
	@EmbeddedId
	@AttributeOverrides(/*…*/)
	private LineItemKey lineItemKey;

	// … additional fields …

	@XmlTransient
	public LineItemKey getLineItemKey() {
		return lineItemKey;
	}

	public void setLineItemKey(LineItemKey lineItemKey) {
		this.lineItemKey = lineItemKey;
	}

	// "pass-thru" properties to lineItemKey
	public Integer getOrderId() {
		return lineItemKey.getOrderId();
	}

	public void setOrderId(Integer orderId) {
		if (lineItemKey == null) {
			lineItemKey = new LineItemKey();
		}
		lineItemKey.setOrderId(orderId);
	}

	public Integer getItemNumber() {
		return lineItemKey.getItemNumber();
	}

	public void setItemNumber(Integer itemNumber) {
		if (lineItemKey == null) {
			lineItemKey = new LineItemKey();
		}
		lineItemKey.setItemNumber(itemNumber);
	}

	// … additional getters and setters …
};

Note the addition of @XmlTransient to the lineItemKey getter; this tells JAXB not to map this particular property. (If JPA is using field access, we might get by with removing the lineItemKey getter and setter entirely. On the other hand, if JPA is using property access, then we’d need to mark our “pass-thru” getters as @Transient to keep the JPA provider from inferring an incorrect mapping to the ORDER_ITEM table.)

With lineItemKey marked @XmlTransient, though, JAXB won’t know that it needs to create the embedded LineItemKey instance during unmarshalling. Here we’ve addressed that by making the “pass-thru” setters ensure that the instance exists. JPA should tolerate this at least if it’s using field access. If you want that approach to be thread safe, you’d have to synchronize the setters. As an alternative, you could create the LineItemKey in a default constructor (if you’re confident that your JPA provider won’t mind).

Another option that’s sure to only affect JAXB (without dedicated getters and setters) might be to use an ObjectFactory that injects the LineItemKey into the OrderLineItem before returning it. However, to the best of my knowledge, ObjectFactory has to cover all of the classes in a package, so if you have many simple domain objects and a few complex ones in the same package (and have no other reason to create an ObjectFactory) then you may want to avoid this approach.

You also might want to protect the pass-thru getters from null pointer exceptions by checking if LineITemKey exists before trying to fetch the return value.

In any event, our XML should now look like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<orderLineItem>
	<itemNumber>1</itemNumber>
	<orderId>37042</orderId>
	<partNumber>100-02</partNumber>
	<quantity>10</quantity>
</orderLineItem>

Related Objects: One to Many

Of course, your line items belong to orders, so you might have an ORDER table (and corresponding Order class).

@XmlRootElement
@Entity
@Table(name="ORDER")
public class Order {
	@Id
	@Column(name="ORDER_ID")
	private Integer orderId;

	@OneToMany(mappedBy="order")
	private List<OrderLineItem> lineItems;

	// … getters and setters …
}

We’ve set up a one-to-many relationship with OrderLineItem. Note we’re expecting OrderLineItem to own this relationship for JPA purposes.

For now we’ll take the @XmlRootElement annotation off of OrderLineItem. (We don’t have to; the annotation makes the class eligible to be a root element but does not preclude also using it as a nested element. However, if we want to continue writing XML that represents just the OrderLineItem, then we’ll have some additional decisions to make, so we’ll defer that for the moment.)

To keep the marshaller happy, we make the Order property of OrderLineItem @XmlTransient. This avoids a circular reference that could otherwise be interpreted as an infinitely deep XML tree. (You probably wouldn’t intend to embed the full order detail under the <orderLineItem> element anyway.)

With <orderLineItem> embedded under an <order> element, there’s no longer a reason to put a <orderId> element under <orderLineItem>. We remove the orderId property from OrderLineItem, knowing that code elsewhere in the app can still use lineItem.getOrder().getOrderId().

The new version of OrderLineItem looks like this:

@Entity
@Table(name="ORDER_ITEM")
public class OrderLineItem {
	@EmbeddedId
	@AttributeOverrides(/*…*/)
	private LineItemKey lineItemKey;

	@MapsId("orderId")
	@ManyToOne
	private Order order;

	@Column(name="PART_NUM")
	private String partNumber;

	private Integer quantity; 

	@XmlTransient
	public Order getOrder() {
		return order;
	}

	public void setOrder(Order order) {
		this.order = order;
	}

	public Integer getItemNumber() {
		return lineItemKey.getItemNumber();
	}

	public void setItemNumber(Integer itemNumber) {
		if (lineItemKey == null) {
			lineItemKey = new LineItemKey();
		}
		lineItemKey.setItemNumber(itemNumber);
	}

		// … more getters and setters …
};

Our JAXBContext needs to be told about the Order class. In this situation it doesn’t need to be told explicitly about OrderLineItem. So we can test marshalling like this:

JAXBContext jaxb = JAXBContext.newInstance(Order.class);

List<OrderLineItem> lineItems = new ArrayList<OrderLineItem>();

Order order = new Order();
order.setOrderId(37042);
order.setLineItems(lineItems);

OrderLineItem lineItem = new OrderLineItem();
lineItem.setOrder(order);
lineItem.setLineNumber(1);
lineItem.setPartNumber("100-02");
lineItem.setQuantity(10);
lineItems.add(lineItem);

lineItem = new OrderLineItem();
lineItem.setOrder(order);
lineItem.setLineNumber(2);
lineItem.setPartNumber("100-17");
lineItem.setQuantity(5);
lineItems.add(lineItem);

Marshaller marshaller = jaxb.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(order, System.out);

Note that we set the order property for each line item. JAXB won’t care about this when marshalling (because the property is @XmlTransient and no other property depends on the internal state it affects), but we want to keep our object relationships consistent. If we were to pass order to JPA, then failing to set the order property would become a problem – and we’ll come back to that point shortly.

We should get output like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<order>
    <orderId>37042</orderId>
    <lineItems>
        <lineNumber>1</lineNumber>
        <partNumber>100-02</partNumber>
        <quantity>10</quantity>
    </lineItems>
    <lineItems>
        <lineNumber>2</lineNumber>
        <partNumber>100-17</partNumber>
        <quantity>5</quantity>
    </lineItems>
</order>

The default element name mapping puts a <lineItems> tag around each line item (because that’s the property name), which is a little off. We can fix this by putting @XmlElement(name=”lineItem”) on the getLineItems() method of Order. (And if we then wanted the entire list of line item elements wrapped in a single <lineItems> element, we could do that with an @XmlElementWrapper(name=”lineItems”) annotation on the same method.)

At this point the marshalling test should look pretty good, but we’ll run into trouble if we unmarshal an order and ask JPA to persist the resulting order line item objects. The problem is that the unmarshaller isn’t setting the order property of OrderLineItem (which owns the Order-to-OrderLineItem relationship for JPA’s purposes).

We can solve this by having Order.setLineItems() iterate over the list of Employees and call setOrder() on each one. This relies on JAXB building the line item list first and then passing it to setLineItems(); it worked in my tests, but I don’t know if it will always work with all JAXB implementations.

Another option is to call setOrder() on each OrderLineItem after unmarshalling but before passing the objects to JPA. This is perhaps more foolproof, but it feels like a kludge. (Part of the point of encapsulation is that your setters supposedly can ensure your objects keep an internally consistent state, after all; so why pass that responsibility off to code outside the objects’ classes?)

Favoring simplicity, I’ll skip over some more elaborate ideas I toyed with while trying to sort this problem out. We will look at one more solution when we talk about @XmlID and @XmlIDREF shortly.

The Case for Property Access

I’ve leaned on modified setters to address the previous two problems. If you’re used to the idea that a setter should have one line (this.myField = myArgument), this may seem questionable. (Then again, if you won’t let your setters do any work for you, what is it you’re buying by encapsulating your fields?)

@XmlTransient
public List<OrderLineItem> getLineItems() {
return lineItems;
}

public void setLineItems(List<OrderLineItem> lineItems) {
    this.lineItems = lineItems;
}

// @Transient if JPA uses property access
@XmlElement(name="lineItem")
public List<OrderLineItem> getLineItemsForJAXB() {
    return getLineItems();
}

public void setLineItemsForJAXB(List<OrderLineItems> lineItems) {
    setLineItems(lineItems);
    // added logic, such as calls to setOrder()…
}

You can avoid using the “ForJAXB” properties anywhere else in your app if you want, so if you feel you’re having to add setter logic “just for JAXB” this approach will keep that added logic from getting in your way.

In my opinion, though, the types of setter logic I’ve described above merely hide the implementation details of the bean properties from outside code. I’d argue that JAXB is encouraging better abstraction in these instances.

If you think of JAXB as a way to serialize the internal state of an object, then field access may seem preferable. (I’ve heard that argument for using field access with JPA, at any rate.) At the end of the day, though, you want the tool to do a job for you. Treating JAXB as an external mechanism for building (or recording) your objects may just be more pragmatic.

Related Objects: One to One, Many to Many

With one-to-many relationships working, it may seem like one-to-one relationships should be a snap. However, while a one-to-many relationship will often lend itself to the hierarchical nature of XML (with the “many” being children of the “one”), the objects in a one-to-one relationship are often just peers; so at best the choice to embed one element within the other in the XML representation would be arbitrary.

Many-to-many relationships pose a bigger challenge to the hierarchical model. And if you have a more complex network of relationships (regardless of their cardinalities), there may not be a straightforward way to arrange the objects into a tree.

Before exploring a general solution, it might be good to pause at this point and ask yourself if you need a general solution. Our project needed to load two types of object that conformed to a parent-child relationship, so the techniques I’ve described previously were sufficient. It may be that you simply don’t need to persist your entire object model in XML.

But if you do find you need a way to model relationships that don’t fit the parent-child mold, you can do it with @XmlID and @XmlIDREF.

As you learn the rules for using @XmlID, you might ask yourself if it wouldn’t be easier to just store the raw foreign key elements under the referencing element (analogous to the way an RDBMS typically represents a foreign key). You could, and the marshaller would have no problem producing nice-looking XML. But then during or after unmarshalling you’d be responsible for reassembling the relationship graph on your own. The rules for @XmlID are annoying, but I don’t find them so hard to accommodate that avoiding them would justify that kind of effort.

The ID values must be Strings, and they must be unique across all elements in your XML document (not just across all elements of a given type). This is because conceptually an ID reference is untyped; in fact, if you let JAXB build your domain objects from a schema, it would map your @XmlIDREF elements (or attributes) to properties of type Object. (When you annotate your own domain classes, though, you’re allowed to use @XmlIDREF with typed fields and properties as long as the referenced type has a field or property annotated with @XmlID. I prefer to do this as it avoids unwanted casts in my code.) The keys for your relationships may not follow those rules; but that’s okay, because you can create a property (named xmlId, say) that will.

Suppose each of our orders has a Customer and a “ship to” Address. Also, each Customer has a list of billing Addresses. Both tables in the database (CUSTOMER and ADDRESS) use Integer surrogate keys with sequences starting at 1.

In our XML, the Customer and “ship to” Address could be represented as child elements under Order; but maybe we need to keep track of Customers who don’t currently have any orders. Likewise, the billing Address list could be represented as a list of child elements under Customer, but this will inevitably lead to duplication of data as customers have orders shipped to their billing addresses. So instead we’ll use @XmlID.

We can define Address as follows:

	@Entity
	@Table(name="ADDRESS")
	public class Address {
		@Id
		@Column(name="ADDRESS_ID")
		private Integer addressId;

		// other fields…

		@XmlTransient
		public Integer getAddressId() {
			return addressId;
		}

		public void setAddressId(Integer addressId) {
			this.addressId = addressId;
		}

		// @Transient if JPA uses property access
		@XmlID
		@XmlElement(name="addressId")
		public String getXmlId() {
			return getClass().getName() + getAddressId();
		}

		public void setXmlId(String xmlId) {
			//TODO: validate xmlId is of the form <className><Integer>
			setAddressId(Integer.parseInt(
				xmlId.substring( getClass().getName().length() )));
		}

		// … more getters and setters …
}

Here the xmlId property provides JAXB’s view of the addressId. Prepending the class name provides uniqueness across types whose keys might otherwise clash. If we had a more complex natural key for the table, we’d have to convert each element of the key to a string, possibly with some sort of delimiter, and concatenate it all together.

A variation on this idea is to use @XmlAttribute instead of @XmlElement. I generally prefer to use elements for data values (since they’re logically the content of the document), but the XmlId could arguably be seen as describing the <Address> XML element rather than the address itself, so it might make sense to record it as an attribute.

For unmarshalling to work, we also have to parse the addressId value back out of the xmlId in the setter. We could avoid this if we persist both the xmlId property and the addressId property; in that case, the xmlId setter could just throw its value away; but I don’t like that option because it saves relatively little effort and creates the possibility of encountering an XML document with inconsistent values for xmlId and addressId. (Sometimes you might have to admit the possibility of an inconsistent document – such as if you persist both sides of a relationship, which I’ll talk about later.)

Next we’ll create our Customer mapping:

@Entity
@Table(name="CUSTOMER")
public class Customer {
    @Id
    @Column(name="CUSTOMER_ID")
    private Integer customerId;

    @ManyToMany
    @JoinTable(name = "CUST_ADDR")
    private List<Address> billingAddresses;

    // other fields…

    @XmlTransient
    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }

    @XmlIDREF
    @XmlElement(name = "billingAddress")
    public List<Address> getBillingAddresses() {
        return billingAddresses;
    }

    public void setBillingAddresses(List<Address> billingAddresses) {
        this.billingAddresses = billingAddresses;
    }

    // @Transient if JPA uses property access
    @XmlID
    @XmlElement(name="customerId")
    public String getXmlId() {
        return getClass().getName() + getCustomerId();
    }

    public void setXmlId(String xmlId) {
        //TODO: validate xmlId is of the form <className><Integer>
        setCustomerId(Integer.parseInt(
            xmlId.substring( getClass().getName().length() )));
    }

    // … more getters and setters …
}

The handling of Customer’s xmlId is the same as that for Address. We’ve marked the billingAddresses property with the @XmlIDREF annotation, telling JAXB that each <billingAddress> element should contain an ID value referencing an Address rather than the actual Address element structure. In the same way, we would add customer and shipToAddress properties to Order, annotated with @XmlIDREF.

At this point, every reference to a Customer or an Address is marked as an @XmlIDREF. This means that while we can marshal our data into XML, the result won’t actually contain any Customer or Address data. If an @XmlIDREF doesn’t correspond to an @XmlID in a document when you unmarshal it, then the corresponding property on the unmarshalled object will be null. So if we really want this to work, we have to create a new @XmlRootElement that can contain all of our data.

@XmlRootElement
public class OrderData {
    private List<Order> orders;
    private List<Address> addresses;
    private List<Customer> customers;

    // getters and setters
}

This class doesn’t correspond to any table in our database, so it doesn’t have JPA annotations. Our getters can have @XmlElement and @XmlElementWrapper annotations as on previous List-type properties. If we assemble and marshal an OrderData object, we might get something like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<orderData>
    <addresses>
        <address>
            <addressId>Address1010</addressId>
            <!-- … other elements … -->
        </address>
        <address>
            <addressId>Address1011</addressId>
            <!-- … -->
        </address>
    </addresses>
    <customers>
        <customer>
            <billingAddress>Address1010</billingAddress>
            <billingAddress>Address1011</billingAddress>
            <customerId>Customer100</customerId>
        </customer>
    </customers>
    <orders>
        <order>
            <customer>Customer100</customer>
            <lineItem>
                <itemNumber>1</itemNumber>
                <partNumber>100-02</partNumber>
                <quantity>10</quantity>
            </lineItem>
            <lineItem>
                <lineNumber>2</lineNumber>
                <partNumber>100-17</partNumber>
                <quantity>5</quantity>
            </lineItem>
            <orderId>37042</orderId>
            <shipToAddress>Address1011</shipToAddress>
        </order>
    </orders>
</orderData>

So far we’ve only mapped one side of each relationship. If our domain objects need to support navigation in both directions, then we have a choice to make: We can mark the property on one side of the relationship as @XmlTransient; this puts us in the same situation we were in with a hierarchically represented one-to-many relationship, in that unmarshalling will not automatically set the @XmlTransient property. Or, we can make both properties @XmlIDREF, recognizing that someone could write an inconsistent XML document.

Revisiting Related Objects: One to Many

Earlier when we looked at one-to-many relationships, we relied solely on containment – child elements embedded within a parent element. One of the limitations of containment is that it only allows us to map one side of a relationship. This caused us to jump through some hoops during unmarshalling since our domain objects would need the reverse relationship to work well with JPA.

We’ve seen that @XmlID and @XmlIDREF provide a more general representation of relationships. Mixing the two techniques, we can represent both sides of a parent-child relationship (with the caveat, as with any case where we show both sides of a relationship in XML, that you could hand-write an XML document with inconsistent relationships).

We can modify our previous one-to-many example to look like this:

@XmlRootElement
@Entity
@Table(name="ORDER")
public class Order {
	@Id
	@Column(name="ORDER_ID")
	private Integer orderId;

	@OneToMany(mappedBy="order")
	private List<OrderLineItem> lineItems;

	@XmlTransient
	public Integer getOrderId() {
		return orderId;
	}

	public void setOrderId(Integer orderId) {
		this.orderId = orderId;
	}

	@XmlID
	@XmlElement(name="orderId")
	public String getXmlId() {
		return getClass().getName() + getOrderId;
	}

		public void setXmlId(String xmlId) {
			//TODO: validate xmlId is of the form <className><Integer>
			setOrderId(Integer.parseInt(
				xmlId.substring( getClass().getName().length() )));
		}

	@XmlElement("lineItem")
	public List<OrderLineItem> getLineItems() {
		return lineItems;
	}

	public void setLineItems(List<OrderLineItem> lineItems) {
		this.lineItems = lineItems;
	}
	}

@Entity
	@Table(name="ORDER_ITEM")
	public class OrderLineItem {
		@EmbeddedId
		@AttributeOverrides(/*…*/)
		private LineItemKey lineItemKey;

		@MapsId("orderId")
		@ManyToOne
		private Order order;

		@Column(name="PART_NUM")
		private String partNumber;

		private Integer quantity; 

		@XmlIDREF
		public Order getOrder() {
			return order;
		}

		public void setOrder(Order order) {
			this.order = order;
		}

		public Integer getItemNumber() {
			return lineItemKey.getItemNumber();
		}

		public void setItemNumber(Integer itemNumber) {
			if (lineItemKey == null) {
				lineItemKey = new LineItemKey();
			}
			lineItemKey.setItemNumber(itemNumber);
		}

		// … more getters and setters …
}

When we marshal Order, we now write out the orderId as an XML ID. Instead of making the order property of OrderLineItem @XmlTransient, we avoid the infinite recursion by having it write the @XmlIDREF instead of the full Order structure; so both sides of the relationship are preserved in a way we can understand at unmarshalling time.

The resulting XML would look like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<order>
    <orderId>Order37042</orderId>
    <lineItem>
        <lineNumber>1</lineNumber>
        <order>Order37042</order>
        <partNumber>100-02</partNumber>
        <quantity>10</quantity>
    </lineItem>
    <lineItem>
        <lineNumber>2</lineNumber>
        <order>Order37042</order>
        <partNumber>100-17</partNumber>
        <quantity>5</quantity>
    </lineItem>
</order>

And both marshalling and unmarshalling work as we would want. The repetition of the containing order ID value is the only complaint we might have with the output. We could reduce the visual impact by making in an @XmlAttribute rather than an @XmlElement; and this is another case where we could possibly make the argument that the value isn’t “real content,” since we’re just putting it in to help JAXB with unmarshalling.

Closing Thoughts

As the title says, I walked through this exercise as a newcomer to JAXB. This is by no means a thorough discussion of what JAXB can do, and from the documentation I’ve read I’d even say that I’ve ignored some of its most sophisticated functionality.

However, I hope this might serve as a useful primer and might illustrate the power that comes from the bean conventions and from tools and frameworks that unobtrusively interact with POJOs.

I’d also reiterate the point that you can make a technique like this as complicated as you care to; so knowing how much complexity your requirements really warrant is key.

– Mark Adelsberger, asktheteam@keyholesoftware.com

This article was originally posted at http://keyholesoftware.com/2014/08/25/jaxb-part-2

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Keyhole Software
United States United States
Keyhole is a software development and consulting firm with a tight-knit technical team. We work primarily with Java, .NET, and Mobile technologies, specializing in application development. We love the challenge that comes in consulting and blog often regarding some of the technical situations and technologies we face. Kansas City, St. Louis and Chicago.
This is a Organisation

3 members

Comments and Discussions

 
-- There are no messages in this forum --