Problem:
I am seeing that the Product "setProductOptions()" method has been deprecated, and that the "setProductOptionXrefs()" is preferred. The issue is that I can't seem to find any example of how to set the ProductOptionXrefs.
I've looked for examples within the Broadleaf "BroadleafCommerce-develop-5.2.x" and "DemoSite-broadleaf-5.2.2.1-GA" projects as well as combed the galaxy for an example. No luck.
The Goal (X):
I am creating an endpoint that will take in a JSON object and accept two parameters, (categoryName and price).
The endpoint will:
The endpoint itself looks like:
@RequestMapping(value = "/my_product", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ProductWrapper addCSDLProduct(HttpServletRequest request, @RequestBody ProductWrapper wrapper,
@RequestParam(value = "categoryName", required = true) String categoryName,
@RequestParam(value = "price", required = true) double price) {
Category category = null;
List<Category> categories = catalogService.findCategoriesByName( categoryName );
if ( categories != null && categories.size() > 0 ) {
category = categories.get(0);
}
Sku defaultSku = catalogService.createSku();
defaultSku.setRetailPrice(new Money( price ));
defaultSku.setInventoryType( InventoryType.ALWAYS_AVAILABLE );
defaultSku.setName( wrapper.getName() );
defaultSku.setLongDescription( wrapper.getLongDescription() );
defaultSku.setDescription( wrapper.getDescription() );
defaultSku.setUrlKey( wrapper.getUrl() );
defaultSku.setActiveStartDate( new Date() );
Product product = catalogService.createProduct(ProductType.PRODUCT);
product.setDefaultSku(defaultSku);
product.setUrl( wrapper.getUrl() );
product.setCategory(category);
List<ProductOptionXref> productOptionXrefs = new ArrayList<ProductOptionXref>();
List<ProductOption> allProductOptions = catalogService.readAllProductOptions();
if ( null != allProductOptions && allProductOptions.size() > 0 ) {
for ( ProductOption po : allProductOptions ) {
String current = po.getName();
if ( current.equalsIgnoreCase("Shirt Color") ) {
ProductOptionXref productOptionXref = new ProductOptionXrefImpl();
productOptionXref.setProductOption(po);
productOptionXrefs.add(productOptionXref);
}
}
}
product.setProductOptionXrefs(productOptionXrefs);
Product finalProduct = catalogService.saveProduct(product);
Long newId = finalProduct.getId();
ProductWrapper response;
response = (ProductWrapper) context.getBean(ProductWrapper.class.getName());
response.wrapDetails(product, request);
response.setId(newId);
return response;
}
The input object (an example) I use is:
{
"name": "This is the name of the product.",
"description": "This is the description of the product.",
"longDescription": "This is a long description of the product. Really long.",
"url": "/this/is/the/url/of/the/product",
"defaultSku": {
"name": "This is the name of the product.",
"active": true,
"available": true,
"inventoryType": "ALWAYS_AVAILABLE",
"retailPrice": {
"amount": 19.0,
"currency": "USD"
}
}
}
With a catgoryName of "Merchandise" and a price of 19.00.
This code above is currently returning a "Not-null" error:
Not-null property references a transient value - transient instance must be saved before current operation: org.broadleafcommerce.core.catalog.domain.ProductOptionXrefImpl.product -> org.broadleafcommerce.core.catalog.domain.ProductImpl; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: org.broadleafcommerce.core.catalog.domain.ProductOptionXrefImpl.product -> org.broadleafcommerce.core.catalog.domain.ProductImpl
which most likely has to do with my
if ( current.equalsIgnoreCase("Shirt Color") ) {
ProductOptionXref productOptionXref = new ProductOptionXrefImpl();
productOptionXref.setProductOption(po);
productOptionXrefs.add(productOptionXref);
}
and/or
Product finalProduct = catalogService.saveProduct(product);
lines just above.
I could (eventually) figure out the "Not-null" error, but the question I ask is if anyone has an example of adding a ProductOption or ProductOptionXref to a newly created Product?
Thanks
Jon
[UPDATE]
I wanted to update my question with the solution, much thanks to @phillipuniverse for the example/explanations.
@RequestMapping(value = "/my_product", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ProductWrapper addCSDLProduct(HttpServletRequest request, @RequestBody ProductWrapper wrapper,
@RequestParam(value = "categoryName", required = true) String categoryName,
@RequestParam(value = "price", required = true) double price) {
Category category = null;
List<Category> categories = catalogService.findCategoriesByName( categoryName );
if ( categories != null && categories.size() > 0 ) {
category = categories.get(0);
}
Sku defaultSku = catalogService.createSku();
defaultSku.setRetailPrice(new Money( price ));
defaultSku.setInventoryType( InventoryType.ALWAYS_AVAILABLE );
defaultSku.setName( wrapper.getName() );
defaultSku.setLongDescription( wrapper.getLongDescription() );
defaultSku.setDescription( wrapper.getDescription() );
defaultSku.setUrlKey( wrapper.getUrl() );
defaultSku.setActiveStartDate( new Date() );
Product product = catalogService.createProduct(ProductType.PRODUCT);
product.setDefaultSku(defaultSku);
product.setUrl( wrapper.getUrl() );
product.setCategory(category);
List<ProductOptionXref> productOptionXrefs = new ArrayList<ProductOptionXref>();
List<ProductOption> allProductOptions = catalogService.readAllProductOptions();
if ( null != allProductOptions && allProductOptions.size() > 0 ) {
for ( ProductOption po : allProductOptions ) {
String current = po.getName();
if ( current.equalsIgnoreCase("Shirt Color") ) {
ProductOptionXref productOptionXref = new ProductOptionXrefImpl();
productOptionXref.setProductOption(po);
productOptionXref.setProduct(product);
productOptionXrefs.add(productOptionXref);
}
}
}
product.setProductOptionXrefs(productOptionXrefs);
Product finalProduct = catalogService.saveProduct(product);
finalProduct.getDefaultSku().setDefaultProduct(finalProduct);
catalogService.saveSku(finalProduct.getDefaultSku());
Long newId = finalProduct.getId();
ProductWrapper response;
response = (ProductWrapper) context.getBean(ProductWrapper.class.getName());
response.wrapDetails(product, request);
response.setId(newId);
return response;
}
Jon
It looks like the majority of what you have there is correct. You are missing a back-reference with the default Sku though; admittedly it's a little weird. You've got this part right:
Product product = catalogService.createProduct(ProductType.PRODUCT);
product.setDefaultSku(defaultSku);
But then you also need to make sure that the default sku references back to the product; you have the save the product first and then set it:
...
Product finalProduct = catalogService.saveProduct(product);
finalProduct.getDefaultSku().setDefaultProduct(finalProduct);
catalogService.saveSku(finalProduct.getDefaultSku());
This code above is currently returning a "Not-null" error:
You are right, the problem is in this code, and it is because you are not setting the product
property on the ProductOptionXrefImpl
:
if ( current.equalsIgnoreCase("Shirt Color") ) {
ProductOptionXref productOptionXref = new ProductOptionXrefImpl();
productOptionXref.setProductOption(po);
productOptionXrefs.add(productOptionXref);
}
If you look at the product
property in ProductOptionXrefImpl
you will find this:
@ManyToOne(targetEntity = ProductImpl.class, optional=false, cascade = CascadeType.REFRESH)
@JoinColumn(name = "PRODUCT_ID")
protected Product product = new ProductImpl();
You are getting past the optional=false
part, but the new ProductImpl()
part makes it always be a 'transient' instance with Hibernate; it does not have an ID property and Hibernate doesn't know anything about it. Since the cascade
is set to only REFRESH
, Hibernate also doesn't try to persist it or anything (which it shouldn't).
The fix is to set the product
property on the option xref:
if ( current.equalsIgnoreCase("Shirt Color") ) {
ProductOptionXref productOptionXref = new ProductOptionXrefImpl();
productOptionXref.setProductOption(po);
productOptionXref.setProduct(product);
productOptionXrefs.add(productOptionXref);
}
I think this will work, but you might have to move the product option creation to below your first save of the Product, and set it to finalProduct
. But I think as-written all of the cascades will figure it out.