How to read non repeating elements while implementing an Item Reader? - spring-batch

I have a scenario where I have to read an xml like this:
<MovieList language="English">
<Movie>..<Movie>
<Movie>..<Movie>
</MovieList>
I have to read the Movie Tag which is a complex object(tag) and insert the details into a movie table. I have set the fragmentRootElementName as Movie and able to read the Movie tag completely. How ever, I am not able to read the language attribute, which is not a repeating tag.
How should I be fetching the non repeating tag details? Should I parse the XML myself to read it? or Should I write one more fragmentRootElementName just to read the language attribute?
The configuration for item reader is as below:
<bean id="movieReader" class="org.springframework.batch.item.xml.StaxEventItemReader">
<property name="unmarshaller" ref="marshaller" />
<property name="fragmentRootElementName" value="Movie" />
<property name="resource" value="file:#{jobParameters['inputFile']}" />
</bean>

Ok, the simple way would be to define your fragmentRootElementName = MovieList but i guess this is not a good idea if your MovieList can contains millions of movies!!
I had a similar problem where i needed to knew the parent tag of my fragmentRootElementName.
So we created a CustomStaxEventItemReader that extends the original StaxEventItemReader.
We added a property parentElement that you can set in your config. and we override the method moveCursorToNextFragment() and doRead() to be able to deal with this problem!
Now the code i have don't do exactly what you need, but i modified it and it look like it works!!!
protected boolean moveCursorToNextFragment(XMLEventReader reader) {
try {
while (true) {
while (reader.peek() != null && !reader.peek().isStartElement()) {
reader.nextEvent();
}
if (reader.peek() == null) {
return false;
}
XMLEvent ev = reader.peek();
QName startElementName = ((StartElement) ev).getName();
// Take note of current parent element. Must be one of
// ParentTags
String tmp = startElementName.getLocalPart();
for (ParentTags aTag : ParentTags.values()) {
if (aTag.toString().equals(tmp)) {
currentParent = tmp;
Attribute attr = ((StartElement) ev)
.getAttributeByName(new QName("Language"));
if (null != attr) {
parentAttribute = attr.getValue();
}
break;
}
}
if (startElementName.getLocalPart().equals(
fragmentRootElementName)) {
if ((fragmentRootElementNameSpace == null && parentElement
.equals(currentParent))
|| startElementName.getNamespaceURI().equals(
fragmentRootElementNameSpace)) {
return true;
}
}
reader.nextEvent();
}
} catch (XMLStreamException e) {
throw new DataAccessResourceFailureException(
"Error while reading from event reader", e);
}
}
So basically, if you look at the code of the original StaxEventReader, you will see that it pass thru all the element of the xml file. Each time it get an element with the name = to your rootElement, it return true and the doRead unmarshall it and return the object associated.
Now, we only add some code to also find a given parent element. I have used an Enum called ParentTags because my XML was a bit more complexe, but you could only compare the name of the new parentElement defined in your config.
So if the actual element isEquals to your parentElement, you simply assign it to currentParent and try to get your attribute. If not null, assign it to parentAttribute property.
then in your doRead() method, you can access the parentAttribute property and set it in your domain object!
protected T doRead() throws Exception {
if (noInput) {
return null;
}
T item = null;
if (moveCursorToNextFragment(fragmentReader)) {
fragmentReader.markStartFragment();
#SuppressWarnings("unchecked")
T mappedFragment = (T) unmarshaller.unmarshal(StaxUtils
.createStaxSource(fragmentReader));
item = mappedFragment;
logger.info("Item read : " + item);
currentIndex = cIndex.getAndIncrement();
T p = (T) item;
if (p instanceof myDomainObj) {
myDomainObj pp = (myDomainObj) p;
logger.info(pp);
logger.info("attribute parent = " + parentAttribute);
pp.setLanguage(parentAttribute);
}
fragmentReader.markFragmentProcessed();
}
return item;
}
I hope this is clear enough!
Good luck and regards

Related

Extracting the values of elements (XML) which is in an array and put it in the message property in Camel

As you can see the Availability Flag, BeamId is getting repeated. How do I traverse and set the property for Availability Flag1 and so on, so that I can later fetch it with velocity template?
Payload:<ns2:TransportFeasibilityResponse>
<ns2:Parameters>
<ns2:AvailabilityFlag>true</ns2:AvailabilityFlag>
<ns2:SatellitedID>H1B</ns2:SatellitedID>
<ns2:BeamID>675</ns2:BeamID>
<ns2:TransportName>Earth</ns2:TransportName>
</ns2:FeasibilityParameters>
<ns2:Parameters>
<ns2:AvailabilityFlag>true</ns2:AvailabilityFlag>
<ns2:SatellitedID>J34</ns2:SatellitedID>
<ns2:BeamID>111</ns2:BeamID>
<ns2:TransportName>Jupiter</ns2:TransportName>
</ns2:Parameters>
</ns2:TransportFeasibilityResponse>
</ns2:TransportFeasibilityResponseMsg>
Code: (Its not complete)
public static HashMap<String,String> extractNameValueToProperties(String msgBody, selectedKeyList, namelist) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true);
Document doc = null;
try{
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(new InputSource(new StringReader(msgBody)));
} catch(Exception ex) {
Exception actException = new Exception( "Exception while extracting tagvalues", ex);
throw actException;
}
HashMap<String,String> tagNameValueMap = new HashMap<String,String>();
NodeList nodeList = doc.getElementsByTagName("*");
// Trying to enter the TransportFeasibilityResponse element
for (int i = 0; i < nodeList.getLength(); i++) {
Node indNode = nodeList.item(i);
if (indNode.indexOf(String name)>-1);
//checking for Availability flag and similar namelist
dataKey = indNode.getTextContent();
message.setProperty(selectedKeyList[k], dataKey);
k++;
j++;
else
{
continue;
}
}
}
Here,
I am setting these values in my route:
<setProperty propertyName="namelist">
<constant>AvailabilityFlag,SatellitedID,BeamID</constant>
</setProperty>
<setProperty propertyName="selectedKeyList">
<constant>AvailabilityFlag1,SatellitedID1,BeamID1,AvailabilityFlag2,SatellitedID2,BeamID2 </constant>
</setProperty>
<bean beanType="com.gdg.dgdgdg.javacodename" method="extractNameValueToProperties"/>
Question: Please tell me how I can parse through the repeating elements and assign it to the property?
Thanks
I'm not sure if I understand your question correctly, but I think you could use the Splitter pattern to split your xml per Parameters tag and process each other separately and aggregate it later.
Take for example this input:
<TransportFeasibilityResponse>
<Parameters>
<AvailabilityFlag>true</AvailabilityFlag>
<SatellitedID>H1B</SatellitedID>
<BeamID>675</BeamID>
<TransportName>Earth</TransportName>
</Parameters>
<Parameters>
<AvailabilityFlag>true</AvailabilityFlag>
<SatellitedID>J34</SatellitedID>
<BeamID>111</BeamID>
<TransportName>Jupiter</TransportName>
</Parameters>
</TransportFeasibilityResponse>
A route to process this input could be something like this:
from("direct:start")
.split(xpath("/TransportFeasibilityResponse/Parameters"), new AggregationStrategy() {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
List<String> beamIDs = null;
if (oldExchange == null) { // first
beamIDs = new ArrayList<String>();
} else {
beamIDs = oldExchange.getIn().getBody(List.class);
}
beamIDs.add(newExchange.getIn().getBody(String.class));
newExchange.getIn().setBody(beamIDs);
return newExchange;
}
})
.setBody(xpath("/Parameters/BeamID/text()"))
.end()
.log("The final body: ${body}");
First, we split the input per Parameters tag, and then extract the BeamID from it. After that, the AggregationStrategy aggregates each message into one, grouping by BeamID.
The final message should have the a body like this:
675,111
The data I put in the body just for an example, but you could set anywhere you want into the Exchange you are manipulating inside the AggregationStrategy implementation.

Custom properties are not updated for the word via openXML

I am trying to update custom properties of word document thru Open XML programming but it seems the updated properties are not getting saved properly for the word document. So when I opening document after successful execution of the update custom property code, I am getting the message box which is "This document contains field that may refer to other files; Do you want to update the fields in the Document?" If I am pressing 'NO' button then all the update properties would not be saved to the document. If we are going for yes option then it will update properties but I need to save the properties explicitly. Please suggest to save properties to the document without getting confirmation message or corrupting the document. :)
the code snippet is given as below,
public void SetCustomValue(
WordprocessingDocument document, string propname, string aValue)
{
CustomFilePropertiesPart oDocCustomProps = document.CustomFilePropertiesPart;
Properties props = oDocCustomProps.Properties;
if (props != null)
{
//logger.Debug("props is not null");
foreach (var prop in props.Elements<CustomDocumentProperty>())
{
if (prop != null && prop.Name == propname)
{
//logger.Debug("Setting Property: " + prop.Name + " to value: " + aValue);
prop.Remove();
var newProp = new CustomDocumentProperty();
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = prop.Name;
VTLPWSTR vTLPWSTR1 = new VTLPWSTR();
vTLPWSTR1.Text = aValue;
newProp.Append(vTLPWSTR1);
props.AppendChild(newProp);
props.Save();
}
}
int pid = 2;
foreach (CustomDocumentProperty item in props)
{
item.PropertyId = pid++;
}
props.Save();
}
}
I am using .Net framework 3.5 with Open XML SDK 2.0 and Office 2013.
Try this one
var CustomeProperties = xmlDOc.CustomFilePropertiesPart.Properties;
foreach (CustomDocumentProperty customeProperty in CustomeProperties)
{
if (customeProperty.Name == "DocumentName")
{
customeProperty.VTLPWSTR = new VTLPWSTR("My Custom Name");
}
else if (customeProperty.Name == "DocumentID")
{
customeProperty.VTLPWSTR = new VTLPWSTR("FNP.SMS.IQC");
}
else if (customeProperty.Name == "DocumentLastUpdate")
{
customeProperty.VTLPWSTR = new VTLPWSTR(DateTime.Now.ToShortDateString());
}
}
//Open Word Setting File
DocumentSettingsPart settingsPart = xmlDOc.MainDocumentPart.GetPartsOfType<DocumentSettingsPart>().First();
//Update Fields
UpdateFieldsOnOpen updateFields = new UpdateFieldsOnOpen();
updateFields.Val = new OnOffValue(true);
settingsPart.Settings.PrependChild<UpdateFieldsOnOpen>(updateFields);
settingsPart.Settings.Save();
you have to update your document fields on open.

CRM 2011 : Plugin to create duplicate records

i created a simple plugin to create a duplicate record that refers to the parent record.
Here is my code
var pluginExecutionContext = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
abc= pluginExecutionContext.InputParameters["Target"] as Entity;
if (pluginExecutionContext.Depth == 1)
{
Guid abcId = abc.Id;
Entity abcCopy = new Entity("mcg_abc");
if (abc.Attributes.Contains("mcg_abccategoryoptioncode"))
{
abcCopy.Attributes["mcg_abccategoryoptioncode"] = abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode");
}
if (abc.Attributes.Contains("mcg_effectivedate"))
{
abcCopy.Attributes["mcg_effectivedate"] = isp.GetAttributeValue<DateTime>("mcg_effectivedate");
}
if (abc.Attributes.Contains("mcg_startdate"))
{
abcCopy.Attributes["mcg_startdate"] = isp.GetAttributeValue<DateTime>("mcg_startdate");
}
if (abc.Attributes.Contains("mcg_enddate"))
{
abcCopy.Attributes["mcg_enddate"] = isp.GetAttributeValue<DateTime>("mcg_enddate");
}
if (abc.Attributes.Contains("mcg_amendeddate"))
{
abcCopy.Attributes["mcg_amendeddate"] = isp.GetAttributeValue<DateTime>("mcg_amendeddate");
}
if ((abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode").Value) == 803870001)
{
//Some more fields;
}
else
{
//Some more fields;
}
// SOme more fields;
abcCopy.Attributes["mcg_parentabc"] = new EntityReference("mcg_abc", abc.Id);
service.Create(abcCopy);
}
Now the problem is all the fields before the below check are getting copied
if ((abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode").Value) == 803870001)
However fields after this check are not getting copied.
Please if anybody could suggest what mistake i have made.
In case you take field from Target - this field was updated on a client side. In case field was not updated - it would not be in Target. You should use Images to get values of unchanged fields.
The field must be empty so a exception may arise. Try to use the plugin image or change your code to this way:
if (abc.Attributes.Contains("mcg_abccategoryoptioncode")){
if ((abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode").Value) == 803870001)
....

tinymce.dom.replace throws an exception concerning parentNode

I'm writing a tinyMce plugin which contains a section of code, replacing one element for another. I'm using the editor's dom instance to create the node I want to insert, and I'm using the same instance to do the replacement.
My code is as follows:
var nodeData =
{
"data-widgetId": data.widget.widgetKey(),
"data-instanceKey": "instance1",
src: "/content/images/icon48/cog.png",
class: "widgetPlaceholder",
title: data.widget.getInfo().name
};
var nodeToInsert = ed.dom.create("img", nodeData);
// Insert this content into the editor window
if (data.mode == 'add') {
tinymce.DOM.add(ed.getBody(), nodeToInsert);
}
else if (data.mode == 'edit' && data.selected != null) {
var instanceKey = $(data.selected).attr("data-instancekey");
var elementToReplace = tinymce.DOM.select("[data-instancekey=" + instanceKey + "]");
if (elementToReplace.length === 1) {
ed.dom.replace(elementToReplace[0], nodeToInsert);
}
else {
throw new "No element to replace with that instance key";
}
}
TinyMCE breaks during the replace, here:
replace : function(n, o, k) {
var t = this;
if (is(o, 'array'))
n = n.cloneNode(true);
return t.run(o, function(o) {
if (k) {
each(tinymce.grep(o.childNodes), function(c) {
n.appendChild(c);
});
}
return o.parentNode.replaceChild(n, o);
});
},
..with the error Cannot call method 'replaceChild' of null.
I've verified that the two argument's being passed into replace() are not null and that their parentNode fields are instantiated. I've also taken care to make sure that the elements are being created and replace using the same document instance (I understand I.E has an issue with this).
I've done all this development in Google Chrome, but I receive the same errors in Firefox 4 and IE8 also. Has anyone else come across this?
Thanks in advance
As it turns out, I was simply passing in the arguments in the wrong order. I should have been passing the node I wanted to insert first, and the node I wanted to replace second.

CommandBars.FindControl throwing an exception

I am trying to use the FindControl Method of the CommandBars object in a VSTO Word addin to get what else a command bar object
Code is as follows
private void WireContextMenu(string MenuID,string Tag, string ID, ref Office.CommandBarButton Control)
{
try
{
object missing = System.Type.Missing;
Control = (Office.CommandBarButton)this.Application.CommandBars[MenuID].FindControl((object)Office.MsoControlType.msoControlButton, ID, Tag, missing, missing);
if (Control == null)
{
Control = (Office.CommandBarButton)this.Application.CommandBars[MenuID].Controls.Add(Office.MsoControlType.msoControlButton, ID, missing, missing, missing);
Control.Caption = "Biolit Markup Selection";
Control.Tag = Tag;
}
Control.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(this.cb_Click);
}
catch (Exception Ex)
{
}
}
The FindControl method is throwing a Type Mismatch Exception (-2147352571)
Any ideas
is this the right way anyhow to add a item to the right click menu of word and then make sure you dont add it if it already exists
Thanks
you are using Missing where Missing is not allowed as parameter
ref: link text
http://msdn.microsoft.com/en-us/library/system.type.missing.aspx
use code like this:
object type = MsoControlType.msoControlPopup;
object id = 1;
object tag = null;
object visible = 1;
object recusive = false;
//object missing = System.Type.Missing;
CommandBarControl barControl = popParent.FindControl(type, id, tag, visible, recusive);