CXF: add element with namespace from message to SOAP header - cxf

How can I get the name (including namespace prefix) of root element in message (1st element in SOAP body) and add this information to SOAP header.
Is it possible to do so with outbound interceptor and in which phase is the namespace prefix available? Or is there other way how to do so?
EDIT:
I am able to get the element with its namespace prefix via message.getContent(OutputStream.class) but I don't want to modify row XML. Is there way how can I get namespace (e.g. from JAXB object in message) and set its namespace prefix? Then I can use the element name and my prefix in header.

I created interceptor where I get message (as JAXB object) and then I use reflection to get its root element (because it is not root element) from ObjectFactory. Then I get its namespace and name and I use it for SOAP header and I set namespace prefix for the namespace. My handleMessage method in interceptor is like the following:
public void handleMessage(SoapMessage message) throws Fault {
String rootElementNamespace = null;
String rootElementName = null;
Set<Class<?>> formats = message.getContentFormats();
List<?> messageContent = message.getContent(List.class);
Object responseMessage = null;
for (Object o : messageContent){
if (o != null && o.getClass().getAnnotation(XmlType.class) != null){
responseMessage = o;
break;
}
}
if(responseMessage == null){
return;
}
Class<? extends Object> messageContentClass = messageContent.get(0).getClass();
String packageOfMessageContentClass = messageContent.get(0).getClass().getPackage().getName();
try {
Class<?> objectFactory = Class.forName(packageOfMessageContentClass + ".ObjectFactory");
Method[] objectFactoryMethods = objectFactory.getMethods();
Method createMessageMethod = null;
for (Method m : objectFactoryMethods){
if (m.getParameterTypes().length == 1 && m.getParameterTypes()[0].equals(messageContentClass)){
createMessageMethod = m;
break;
}
}
if(createMessageMethod == null){
return;
}
XmlElementDecl xmlTypeAnnotation = createMessageMethod.getAnnotation(XmlElementDecl.class);
rootElementNamespace = xmlTypeAnnotation.namespace();
rootElementName = xmlTypeAnnotation.name();
} catch (ClassNotFoundException e) {
//...
}
Map<String, String> hmap = new HashMap<String, String>();
hmap.put(this.ROOT_PREFIX, rootElementNamespace);
message.put("soap.env.ns.map", hmap);
message.put("disable.outputstream.optimization", true);
try {
Header header = getMyHeader(this.ROOT_PREFIX + ":" + rootElementName); //method creates header with required info
message.getHeaders().add(header);
} catch (JAXBException | DatatypeConfigurationException e) {
//...
}
}
Better solution is welcome...

Related

Facing problem to cover test class 75% coverage

This is my batch class. I am facing a problem in the test class.
So please give me some solution on it. How to write this condition into class or how to write a test class for this.
I have this batch class created:
public class processBatch implements database.Batchable<object>, Database.AllowsCallouts, Scheduable{
public string AccountURL;
public String ProcessURL;
Public String Key;
private class JsonUpsertResult
{
List<Database.Error> Error {get;set;}
string SFDCId{get;set;}
String visibleID{get;set;}
Boolean is Created {get;set;}
Boolean isSuccess {get;set;}
}
public ProcessBatch(){
SAP_Details__c SAP = SAP_Details__c. getOrgDefaults();
AccountURL =SAP.Account_API_URL__c;
ProcessURL =SAP.process_API_URL__c;
Key =SAP.API_Key__c;
system.debug('API:' + ProcessURL + '-' + Key);
}
public Iterable<Object> start (Database.BatchableContext BC) {
List<Object> results =new List <Object>();
http http = new http();
httpRequest request =new HttpRequest();
system.debug('API:' '+ ProcessURL+' -' + Key);
request.setEndpoint(ProcessURL);
request.setMethod('POST');
request.setHeader('Authorization', Key);
request.setHeader('Accept', '*/*');
request.setHeader ('content-type', 'application/json');
}';
request.setBody(filters);
HttpResponse response =http.send(request);
if(responce.getStatusCode() == 200){
SAPProcessJsonApex2 gt = {SAPProcessJSON2Apex2) JSON.deserialize(response.getBody(), SAPProcessJSON2Apex.class);
List<SAPProcessJson2Apex.Result> res =gt.result;
system.debug('Size of Result: ' + res.size());
results = res;
}
return results;
}
public void execute (Database. BatchableContext BC, List<object> scope){
List<JSON2Apex.Results) accountResults = new List<JSON2Apex.Results>;
List<Process__c> ProcessToUpsert = new List<Process__c> ();
List<Process_Agent__c> CollectivesToupsert = new List<Process_Agent__c> ();
List<Department__c> departmentToupdate = new List<Department__c> ();
List<JSON2Apex.Results) revisedAccRes = new List JSON2Apex.Results> ();
List<SAPprocessJSON2Apex2.Results res = (List<SAPprocessJSO2Apex2.Results>) scope();
List<SAPprocessJSON2Apex2.Results revisedRes = nen List<SAPprocessJSONApex2.Results> ();
for (SAPprocessJSON2Apex2.Results r: res) {
SAPprocessJSON2Apex2.Document doc = r. document: List<SAPprocessJSON2 Apex2. Companies) comp = doc.companies;
List<SAPprocessJSON2Apex2.Companies> comp = doc.companies;
List<String> processTypes = doc.processTypes;
Lisk<String> categories = doc.categories;
Process__c pr = new Process__c ();
pr.Visibleprocess_No__c = doc.processId;
pr.Visible_method__c = 'SAP';
pr.process_Description__c = doc.description;
pr.Amount__c = doc.Amount;
pr.Construction_Type__c = doc.constructionType;
if(doc.phase == 'First planning'){
pr.Division=='First planning!';
} else if(doc.phase == 'Quotation'){
pr.Division == 'Main Step Quotation;
} else if(doc.phase = 'Contract Get') {
pr. Division== ' Main Step Agricultural';
}
pr.process_Full Name = doc.title;
pr.Name = doc.title.left (80);
if (categories.size() > 0){
if(categories [0] == 'Factory' || categories [0] = 'Storage' ){
pr.Sector__c = 'Industrial';
}else if(categories [0] == 'Packaging/Storage') {
pr. Type_of_Sector_c = 'Warehouse';
}else if(categories[0] == 'Shops and Retail' || categories [0]=='Any Showrooms') {
pr.Sector__c = 'Commercial';
}
if(pr.Sector__c != null && doc.Amount >= 50000){
ProcessToUpsert.ada (p);
revisedRes.ada (r);
} } }
public void finish (Database.BatchableContext BC) {
}
public void execute (SchedulableContext sc) {
database.executeBatch (new ProcessBatch (), 30);
}
}
&& d.Amount__c>= 50000 Whenever I write this condition in my class then my test class covers only 35%, and when I remove this condition it covers 80%. This condition is correct according to the requirement.
Test Class
#isIest (SeeAllData = true)
public class ProcessBatchTest {
#isTest public static void processBatchTest () {
Test.setMock (HttpCalloutMock.class, new HttpMock());
ProcessBatch pb = new ProcessBatch();
Test.startTest();
database.executeBatch (pb,30);
Test.stopTest();
You control the HttpMock class that pretends it returns results from SAP callout in the test. Nothing stops you from going there and making it return 2 records (1 with amount below 50K, 1 with amount above), or multiple statuses or whatever else you need.

Get List Name from full url

I have hundreds of different random URLs coming in, all documents in libs, without any other parameters from different farms and different site collections and sites, goal is to download a file as a binary array from SharePoint.
So e.g. incoming url = http://a.b.c.d.e/f.g/h.i/j/k/l/m.docx .
So how to get the (a) correct site collection root url (b) site root url (c) library root url from this? The only way I now think of is slowly stripping off each part of the url until e.g. .Rootfolder no longer gives an exception... or the other way around slowly adding bits by the first part of the url until rootfolder nog longers gives an exception then query for subwebs etc..
The point is that ClientContext constructor accepts the url of web/site only.
But if the url will be specified in the following format:
http://site/web/documents/file.docx
then the exception System.Net.WebException will occur.
The following example demonstrates how to resolve ClientContext from request Url:
public static class ClientContextUtilities
{
/// <summary>
/// Resolve client context
/// </summary>
/// <param name="requestUri"></param>
/// <param name="context"></param>
/// <param name="credentials"></param>
/// <returns></returns>
public static bool TryResolveClientContext(Uri requestUri, out ClientContext context, ICredentials credentials)
{
context = null;
var baseUrl = requestUri.GetLeftPart(UriPartial.Authority);
for (int i = requestUri.Segments.Length; i >= 0; i--)
{
var path = string.Join(string.Empty, requestUri.Segments.Take(i));
string url = string.Format("{0}{1}", baseUrl, path);
try
{
context = new ClientContext(url);
if (credentials != null)
context.Credentials = credentials;
context.ExecuteQuery();
return true;
}
catch (Exception ex) {}
}
return false;
}
}
Usage
ClientContext context;
if (ClientContextUtilities.TryResolveClientContext(requestUri, out context, null))
{
using (context)
{
var baseUrl = requestUri.GetLeftPart(UriPartial.Authority);
var fileServerRelativeUrl = requestUri.ToString().Replace(baseUrl, string.Empty);
var file = context.Web.GetFileByServerRelativeUrl(fileServerRelativeUrl);
context.Load(file);
context.Load(context.Web);
context.Load(context.Site);
context.ExecuteQuery();
}
}
Since your goal is to download a file, there is pretty straightforward way to accomplish it without parsing url parts.
For example, using WebClient.DownloadFile Method:
private static void DownloadFile(Uri fileUri, ICredentials credentials, string localFileName)
{
using(var client = new WebClient())
{
client.Credentials = credentials;
client.DownloadFile(fileUri, localFileName);
}
}
I have made a working method but it seems elaborate, so any suggestions for improvement are welcome just to "download file if one of the specific columns has value "yes":
public void getDocument(Document doc)
{
// get the filename
Uri uri = new Uri(doc.uri);
doc.filename = "";
doc.filename = System.IO.Path.GetFileName(uri.LocalPath);
//string fullPathWithoutFileName = docUri.Replace(filename, "");
// would also include ?a&b so:
string[] splitDocUri = doc.uri.Split('/');
string fullPathWithoutFileName = "";
for (int i = 0; i < splitDocUri.Length -1; i++)
{
fullPathWithoutFileName += (splitDocUri[i] + '/');
}
// get via "_api/contextinfo" the context info
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(fullPathWithoutFileName + "_api/contextinfo");
req.Method = "POST";
req.Accept = "application/json; odata=verbose";
req.Credentials = new NetworkCredential(doc.username, doc.password, doc.domain);
req.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED","f");
req.ContentLength = 0;
BypassCertificateError();
HttpWebResponse rp = (HttpWebResponse)req.GetResponse();
Stream postStream = rp.GetResponseStream();
StreamReader postReader = new StreamReader(postStream);
string results = postReader.ReadToEnd();
// Now parse out some values needs system.web.extensions
JavaScriptSerializer jss = new JavaScriptSerializer();
var d = jss.Deserialize<dynamic>(results);
string formDigestValue = d["d"]["GetContextWebInformation"]["FormDigestValue"];
// the full url to the website e.g. "http://server:7777/level1/level 2"
string webFullUrl = d["d"]["GetContextWebInformation"]["WebFullUrl"];
// the full url to the site collection e.g. "http://server:7777"
string siteFullUrl = d["d"]["GetContextWebInformation"]["SiteFullUrl"];
// now we can create a context
ClientContext ctx = new ClientContext(webFullUrl);
ctx.ExecutingWebRequest +=
new EventHandler<WebRequestEventArgs>(ctx_MixedAuthRequest);
BypassCertificateError();
ctx.AuthenticationMode = ClientAuthenticationMode.Default;
ctx.Credentials = new NetworkCredential(doc.username, doc.password, doc.domain);
// Get the List
Microsoft.SharePoint.Client.File file = ctx.Web.GetFileByServerRelativeUrl(uri.AbsolutePath);
List list = file.ListItemAllFields.ParentList;
ctx.Load(list);
ctx.ExecuteQuery();
// execute a CAML query against it
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml =
"<View><Query><Where><Eq><FieldRef Name='FileLeafRef'/>" +
"<Value Type='Text'>" + doc.filename + "</Value></Eq></Where>" +
"<RowLimit>1</RowLimit></Query></View>";
ListItemCollection listItems = list.GetItems(camlQuery);
ctx.Load(listItems);
try {
ctx.ExecuteQuery();
}
catch
{
// e.g. : no access or the listname as incorrectly deduced
throw;
}
// and now retrieve the items needed
if (listItems.Count == 1)
{
ListItem item = listItems[0];
// some more checking from testColumn to decide if to download yes/no
string testColumn;
if (item.IsPropertyAvailable("testColumn")) {
testColumn = (string)item["testColumn"];
}
FileInformation fileInformation =
Microsoft.SharePoint.Client.File.OpenBinaryDirect(ctx,
(string)item["FileRef"]);
doc.bytes = ReadFully(fileInformation.Stream);
}
else
{
doc.errormessage = "Error: No document found";
}
}

Easy way to dynamically invoke web services (without JDK or proxy classes)

In Python I can consume a web service so easily:
from suds.client import Client
client = Client('http://www.example.org/MyService/wsdl/myservice.wsdl') #create client
result = client.service.myWSMethod("Bubi", 15) #invoke method
print result #print the result returned by the WS method
I'd like to reach such a simple usage with Java.
With Axis or CXF you have to create a web service client, i.e. a package which reproduces all web service methods so that we can invoke them as if they where normal methods. Let's call it proxy classes; usually they are generated by wsdl2java tool.
Useful and user-friendly. But any time I add/modify a web service method and I want to use it in a client program I need to regenerate proxy classes.
So I found CXF DynamicClientFactory, this technique avoids the use of proxy classes:
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.dynamic.DynamicClientFactory;
//...
//create client
DynamicClientFactory dcf = DynamicClientFactory.newInstance();
Client client = dcf.createClient("http://www.example.org/MyService/wsdl/myservice.wsdl");
//invoke method
Object[] res = client.invoke("myWSMethod", "Bubi");
//print the result
System.out.println("Response:\n" + res[0]);
But unfortunately it creates and compiles proxy classes runtime, hence requires JDK on the production machine. I have to avoid this, or at least I can't rely on it.
My question:
Is there another way to dinamically invoke any method of a web service in Java, without having a JDK at runtime and without generating "static" proxy classes? Maybe with a different library? Thanks!
I know this is a really old question but if you are still interested you could use soap-ws github project: https://github.com/reficio/soap-ws
Here you have a sample usage really simple:
Wsdl wsdl = Wsdl.parse("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL");
SoapBuilder builder = wsdl.binding()
.localPart("CurrencyConvertorSoap")
.find();
SoapOperation operation = builder.operation()
.soapAction("http://www.webserviceX.NET/ConversionRate")
.find();
Request request = builder.buildInputMessage(operation)
SoapClient client = SoapClient.builder()
.endpointUrl("http://www.webservicex.net/CurrencyConvertor.asmx")
.build();
String response = client.post(request);
As you can see it is really simple.
With CXF 3.x this could be possible with StaxDataBinding. Follow below steps to get the basics. Of course, this could be enhanced to your needs.
Create StaxDataBinding something like below. Note below code can be enhanced to your sophistication.
class StaxDataBinding extends AbstractInterceptorProvidingDataBinding {
private XMLStreamDataReader xsrReader;
private XMLStreamDataWriter xswWriter;
public StaxDataBinding() {
super();
this.xsrReader = new XMLStreamDataReader();
this.xswWriter = new XMLStreamDataWriter();
inInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
inFaultInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
inInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
inFaultInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
}
static class RemoveStaxInEndingInterceptor
extends AbstractPhaseInterceptor<Message> {
static final RemoveStaxInEndingInterceptor INSTANCE = new RemoveStaxInEndingInterceptor();
public RemoveStaxInEndingInterceptor() {
super(Phase.PRE_INVOKE);
addBefore(StaxInEndingInterceptor.class.getName());
}
public void handleMessage(Message message) throws Fault {
message.getInterceptorChain().remove(StaxInEndingInterceptor.INSTANCE);
}
}
public void initialize(Service service) {
for (ServiceInfo serviceInfo : service.getServiceInfos()) {
SchemaCollection schemaCollection = serviceInfo.getXmlSchemaCollection();
if (schemaCollection.getXmlSchemas().length > 1) {
// Schemas are already populated.
continue;
}
new ServiceModelVisitor(serviceInfo) {
public void begin(MessagePartInfo part) {
if (part.getTypeQName() != null
|| part.getElementQName() != null) {
return;
}
part.setTypeQName(Constants.XSD_ANYTYPE);
}
}.walk();
}
}
#SuppressWarnings("unchecked")
public <T> DataReader<T> createReader(Class<T> cls) {
if (cls == XMLStreamReader.class) {
return (DataReader<T>) xsrReader;
}
else {
throw new UnsupportedOperationException(
"The type " + cls.getName() + " is not supported.");
}
}
public Class<?>[] getSupportedReaderFormats() {
return new Class[] { XMLStreamReader.class };
}
#SuppressWarnings("unchecked")
public <T> DataWriter<T> createWriter(Class<T> cls) {
if (cls == XMLStreamWriter.class) {
return (DataWriter<T>) xswWriter;
}
else {
throw new UnsupportedOperationException(
"The type " + cls.getName() + " is not supported.");
}
}
public Class<?>[] getSupportedWriterFormats() {
return new Class[] { XMLStreamWriter.class, Node.class };
}
public static class XMLStreamDataReader implements DataReader<XMLStreamReader> {
public Object read(MessagePartInfo part, XMLStreamReader input) {
return read(null, input, part.getTypeClass());
}
public Object read(QName name, XMLStreamReader input, Class<?> type) {
return input;
}
public Object read(XMLStreamReader reader) {
return reader;
}
public void setSchema(Schema s) {
}
public void setAttachments(Collection<Attachment> attachments) {
}
public void setProperty(String prop, Object value) {
}
}
public static class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
private static final Logger LOG = LogUtils
.getL7dLogger(XMLStreamDataWriter.class);
public void write(Object obj, MessagePartInfo part, XMLStreamWriter writer) {
try {
if (!doWrite(obj, writer)) {
// WRITE YOUR LOGIC HOW you WANT TO HANDLE THE INPUT DATA
//BELOW CODE JUST CALLS toString() METHOD
if (part.isElement()) {
QName element = part.getElementQName();
writer.writeStartElement(element.getNamespaceURI(),
element.getLocalPart());
if (obj != null) {
writer.writeCharacters(obj.toString());
}
writer.writeEndElement();
}
}
}
catch (XMLStreamException e) {
throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
}
}
public void write(Object obj, XMLStreamWriter writer) {
try {
if (!doWrite(obj, writer)) {
throw new UnsupportedOperationException("Data types of "
+ obj.getClass() + " are not supported.");
}
}
catch (XMLStreamException e) {
throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
}
}
private boolean doWrite(Object obj, XMLStreamWriter writer)
throws XMLStreamException {
if (obj instanceof XMLStreamReader) {
XMLStreamReader xmlStreamReader = (XMLStreamReader) obj;
StaxUtils.copy(xmlStreamReader, writer);
xmlStreamReader.close();
return true;
}
else if (obj instanceof XMLStreamWriterCallback) {
((XMLStreamWriterCallback) obj).write(writer);
return true;
}
return false;
}
public void setSchema(Schema s) {
}
public void setAttachments(Collection<Attachment> attachments) {
}
public void setProperty(String key, Object value) {
}
}
}
Prepare your input to match the expected input, something like below
private Object[] prepareInput(BindingOperationInfo operInfo, String[] paramNames,
String[] paramValues) {
List<Object> inputs = new ArrayList<Object>();
List<MessagePartInfo> parts = operInfo.getInput().getMessageParts();
if (parts != null && parts.size() > 0) {
for (MessagePartInfo partInfo : parts) {
QName element = partInfo.getElementQName();
String localPart = element.getLocalPart();
// whatever your input data you need to match data value for given element
// below code assumes names are paramNames variable and value in paramValues
for (int i = 0; i < paramNames.length; i++) {
if (paramNames[i].equals(localPart)) {
inputs.add(findParamValue(paramNames, paramValues, localPart));
}
}
}
}
return inputs.toArray();
}
Now set the proper data binding and pass the data
Bus bus = CXFBusFactory.getThreadDefaultBus();
WSDLServiceFactory sf = new WSDLServiceFactory(bus, wsdl);
sf.setAllowElementRefs(false);
Service svc = sf.create();
Client client = new ClientImpl(bus, svc, null,
SimpleEndpointImplFactory.getSingleton());
StaxDataBinding databinding = new StaxDataBinding();
svc.setDataBinding(databinding);
bus.getFeatures().add(new StaxDataBindingFeature());
BindingOperationInfo operInfo = ...//find the operation you need (see below)
Object[] inputs = prepareInput(operInfo, paramNames, paramValues);
client.invoke("operationname", inputs);
If needed you can match operation name something like below
private BindingOperationInfo findBindingOperation(Service service,
String operationName) {
for (ServiceInfo serviceInfo : service.getServiceInfos()) {
Collection<BindingInfo> bindingInfos = serviceInfo.getBindings();
for (BindingInfo bindingInfo : bindingInfos) {
Collection<BindingOperationInfo> operInfos = bindingInfo.getOperations();
for (BindingOperationInfo operInfo : operInfos) {
if (operInfo.getName().getLocalPart().equals(operationName)) {
if (operInfo.isUnwrappedCapable()) {
return operInfo.getUnwrappedOperation();
}
return operInfo;
}
}
}
}
return null;
}

StAX and namespaces

I am trying to convert some code from using DOM (via jDOM) to use StAX instead. At the same time I am migrating from DTD-based validation to XSD_based validation. Oh, and just for good measure I am introducing JAXB into the equation :)
Anyway, as an interim migration step I would like to allow users to still provide legacy documents (aka, using DTD and therefore no namespace). I will still validate the document using XSD, so the DTD is ignored. This works except that StAX (nor JAXB) seems to not like the non-namespaced document. I tried disabling namespace support (using javax.xml.stream.isNamespaceAware), but that did not have any effect. Explicitly adding xmlns to the document root fixed the problem, so I am fairly confident it is a namespacing issue.
Is there a way using StAX XMLEventReader to "introduce" a default namespace? Something along the lines of this approach (which is SAX specific), but for StAX...
Or any other ideas on how to achieve that?
An example document looks like:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.test.abstractembeddedcomponents.cid">
...
</hibernate-mapping>
The code I am currently using to read these documents is:
public JaxbRoot unmarshal(InputStream stream, Origin origin) {
try {
XMLEventReader staxReader = staxFactory().createXMLEventReader( stream );
try {
return unmarshal( staxReader, origin );
}
finally {
try {
staxReader.close();
}
catch ( Exception ignore ) {
}
}
}
catch ( XMLStreamException e ) {
throw new MappingException( "Unable to create stax reader", e, origin );
}
}
private XMLInputFactory staxFactory;
private XMLInputFactory staxFactory() {
if ( staxFactory == null ) {
staxFactory = buildStaxFactory();
}
return staxFactory;
}
#SuppressWarnings( { "UnnecessaryLocalVariable" })
private XMLInputFactory buildStaxFactory() {
XMLInputFactory staxFactory = XMLInputFactory.newInstance();
// tried with and without, no effect
//staxFactory.setProperty( "javax.xml.stream.isNamespaceAware", false );
return staxFactory;
}
#SuppressWarnings( { "unchecked" })
private JaxbRoot unmarshal(XMLEventReader staxEventReader, final Origin origin) {
XMLEvent event;
try {
event = staxEventReader.peek();
while ( event != null && !event.isStartElement() ) {
staxEventReader.nextEvent();
event = staxEventReader.peek();
}
}
catch ( Exception e ) {
throw new MappingException( "Error accessing stax stream", e, origin );
}
if ( event == null ) {
throw new MappingException( "Could not locate root element", origin );
}
final Schema validationSchema;
final Class jaxbTarget;
final String elementName = event.asStartElement().getName().getLocalPart();
if ( "entity-mappings".equals( elementName ) ) {
final Attribute attribute = event.asStartElement().getAttributeByName( ORM_VERSION_ATTRIBUTE_QNAME );
final String explicitVersion = attribute == null ? null : attribute.getValue();
validationSchema = validateXml ? resolveSupportedOrmXsd( explicitVersion ) : null;
jaxbTarget = JaxbEntityMappings.class;
}
else {
validationSchema = validateXml ? hbmSchema() : null;
jaxbTarget = JaxbHibernateMapping.class;
}
final Object target;
final ContextProvidingValidationEventHandler handler = new ContextProvidingValidationEventHandler();
try {
JAXBContext jaxbContext = JAXBContext.newInstance( jaxbTarget );
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema( validationSchema );
unmarshaller.setEventHandler( handler );
target = unmarshaller.unmarshal( staxEventReader );
}
catch ( JAXBException e ) {
throw new MappingException( ... );
}
return new JaxbRoot( target, origin );
}
In my testing the DTD being there or not has no effect. And like I said before, simply changing
<hibernate-mapping package="org.hibernate.test.abstractembeddedcomponents.cid">
to
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping" package="org.hibernate.test.abstractembeddedcomponents.cid">
fixes the failures I see, which are:
[org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'hibernate-mapping'.]
at ...
Caused by: org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'hibernate-mapping'.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1916)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:705)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.startElement(ValidatorHandlerImpl.java:550)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.ValidatingUnmarshaller.startElement(ValidatingUnmarshaller.java:78)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(InterningXmlVisitor.java:60)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXEventConnector.handleStartElement(StAXEventConnector.java:247)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXEventConnector.bridge(StAXEventConnector.java:116)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:394)
... 27 more
This can be done by implementing a filter which adds a default namespace declaration to the first (i.e. root) StartELement event. StAX already provides the EventReaderDelegate utility class, where the peek() and nextEvent() methods need to be overridden.
Here's the code:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.EventReaderDelegate;
/**
* Filter adding default namespace declaration to root element.
*/
public class NamespaceAddingEventReader extends EventReaderDelegate {
private final XMLEventFactory factory = XMLEventFactory.newInstance();
private final String namespaceURI;
private int startElementCount = 0;
public NamespaceAddingEventReader(XMLEventReader reader, String namespaceURI) {
super(reader);
this.namespaceURI = namespaceURI;
}
/**
* Duplicate event with additional namespace declaration.
* #param startElement
* #return event with namespace
*/
private StartElement withNamespace(StartElement startElement) {
List<Object> namespaces = new ArrayList<Object>();
namespaces.add(factory.createNamespace(namespaceURI));
Iterator<?> originalNamespaces = startElement.getNamespaces();
while (originalNamespaces.hasNext()) {
namespaces.add(originalNamespaces.next());
}
return factory.createStartElement(
new QName(namespaceURI, startElement.getName().getLocalPart()),
startElement.getAttributes(),
namespaces.iterator());
}
#Override
public XMLEvent nextEvent() throws XMLStreamException {
XMLEvent event = super.nextEvent();
if (event.isStartElement()) {
if (++startElementCount == 1) {
return withNamespace(event.asStartElement());
}
}
return event;
}
#Override
public XMLEvent peek() throws XMLStreamException {
XMLEvent event = super.peek();
if (startElementCount == 0 && event.isStartElement()) {
return withNamespace(event.asStartElement());
} else {
return event;
}
}
}
To see how this is used, let's copy some XML without namespace declaration to System.out using the event API:
StringReader xml = new StringReader("<?xml version='1.0'?><alice>bob</alice>");
XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(xml);
reader = new NamespaceAddingEventReader(reader, "http://foo");
XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(System.out);
writer.add(reader);
writer.flush();
Running the code will print
<?xml version='1.0' encoding='UTF-8'?><alice xmlns="http://foo">bob</alice>

Copy T4 template output to new file

I'm trying to use T4 templates to make generating migrations for our system slightly easier. The one thing that I can't quite figure out (and this makes me wonder if I'm using T4 templates for the wrong thing) is how to copy the rendered output to a new file. I can manually create a file and copy the contents of the generated file, but that kind of goes against my whole "make things easier" ethos here.
Here's the template I have. Upon rendering, it would ideally get copied to "62-CreateWidgetsTable.cs" in the same directory. The goal is to have a file that I can now edit (I am generating a template, in other words, not generating the complete file.) If I could rename the generated file in VS (and then have the t4 generate a new template that would just sit there until someone came along and used it), that would be good enough.
<## template debug="false" hostspecific="false" language="C#" #>
<## output extension=".cs" #>
<#
var migrationNumber = "62";
var migrationName = "CreateWidgetsTable";
#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Migrator.Framework;
namespace WidgetsIntl.Console.Migrations
{
[Migration(<#= DateTime.UtcNow.ToString("yyyyMMddhhmmss") #>)]
public class _<#= migrationNumber #>_<#= migrationName #> : Migration
{
public override void Up()
{
}
public override void Down()
{
}
}
}
Alright, I figured out a couple ways to do this. The simplest way to do it (which I found only after doing it the way I'm about to show you) is here: t4 append output to existing file. The key information is GenerationEnvironment is a StringBuilder which contains the result of running the template, so you can just write that result to any old file you want!
The other way to do it is to use T4 Toolbox. Go download it!
Then, you can make a template that create a class that extends Template (defined by T4 toolbox) that overrides some default behavior:
<## template language="C#" hostspecific="True" debug="True" #>
<## include file="T4Toolbox.tt" #>
<#
// make an instance of the class we define below, set some variables, and render it
var tpl = new MyT4();
tpl.MyVar = "Do those things you do";
tpl.Render();
#>
<#+
public class MyT4 : Template
{
public MyVar = "some stuff";
public override string TransformText()
{
Output.PreserveExistingFile = true; // tells T4 that you want to manually edit this file afterward (for scaffoling, which was my use case)
Output.File = MyVar + ".cs"; // output will go in "some stuff.cs"
/******************
Template is defined here!
*******************/
#>
public class <#=myVar.Replace(" ", "_") #>
{
public void Method()
{
return "Hi, I am <#= myvar #>";
}
}
<#+
/*************************
now finishing up the TransformText() method
*************************/
return GenerationEnvironment.ToString();
}
}
#>
In some Projects I use allready the FileManager class below. Its a custominated implementation based on this blog post: http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited
<## assembly name="System.Core"
#><## assembly name="System.Data.Linq"
#><## assembly name="EnvDTE"
#><## assembly name="System.Xml"
#><## assembly name="System.Xml.Linq"
#><## import namespace="System"
#><## import namespace="System.CodeDom"
#><## import namespace="System.CodeDom.Compiler"
#><## import namespace="System.Collections.Generic"
#><## import namespace="System.Data.Linq"
#><## import namespace="System.Data.Linq.Mapping"
#><## import namespace="System.IO"
#><## import namespace="System.Linq"
#><## import namespace="System.Reflection"
#><## import namespace="System.Text"
#><## import namespace="System.Xml.Linq"
#><## import namespace="Microsoft.VisualStudio.TextTemplating"
#><#+
// Manager class records the various blocks so it can split them up
protected abstract class FileManager {
protected FileManager(ITextTemplatingEngineHost host, StringBuilder template)
{
this.host = host;
this.template = template;
}
protected abstract void CreateFile(String fileName, String content);
public abstract String GetCustomToolNamespace(String fileName);
public abstract String DefaultProjectNamespace { get; }
public abstract void Process();
public static FileManager Create(ITextTemplatingEngineHost host, StringBuilder template)
{
return new VSManager(host, template);
}
protected class Block
{
public String Name;
public int Start, Length;
}
protected Block currentBlock;
protected List<Block> files = new List<Block>();
protected Block footer = new Block();
protected Block header = new Block();
protected ITextTemplatingEngineHost host;
protected StringBuilder template;
public void StartNewFile(String name)
{
if (name == null)
throw new ArgumentNullException("name");
CurrentBlock = new Block { Name = name };
}
public void StartFooter() {
CurrentBlock = footer;
}
public void StartHeader() {
CurrentBlock = header;
}
public void EndBlock() {
if (CurrentBlock == null)
return;
CurrentBlock.Length = template.Length - CurrentBlock.Start;
if (CurrentBlock != header && CurrentBlock != footer)
files.Add(CurrentBlock);
currentBlock = null;
}
protected bool IsFileContentDifferent(String fileName, String newContent)
{
return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
}
protected Block CurrentBlock
{
get { return currentBlock; }
set {
if (CurrentBlock != null)
EndBlock();
if (value != null)
value.Start = template.Length;
currentBlock = value;
}
}
// VS Manager
private class VSManager: FileManager
{
private EnvDTE.ProjectItem templateProjectItem;
private EnvDTE.DTE dte;
private List<string> generatedFileNames = new List<string>();
public override String DefaultProjectNamespace
{
get
{
return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
}
}
public override String GetCustomToolNamespace(string fileName)
{
return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
}
public override void Process()
{
EndBlock();
String headerText = template.ToString(header.Start, header.Length);
String footerText = template.ToString(footer.Start, footer.Length);
Directory.SetCurrentDirectory(Path.GetDirectoryName(host.TemplateFile));
files.Reverse();
foreach(Block block in files)
{
String fileName = Path.GetFullPath(block.Name);
String content = headerText + template.ToString(block.Start, block.Length) + footerText;
generatedFileNames.Add(fileName);
CreateFile(fileName, content);
template.Remove(block.Start, block.Length);
}
this.ProjectSync(generatedFileNames);
this.files = new List<Block>();
this.footer = new Block();
this.header = new Block();
this.generatedFileNames = new List<string>();
}
protected override void CreateFile(String fileName, String content)
{
if (IsFileContentDifferent(fileName, content))
{
CheckoutFileIfRequired(fileName);
File.WriteAllText(fileName, content);
}
}
internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template)
{
var hostServiceProvider = host as IServiceProvider;
if (hostServiceProvider == null)
{
throw new ArgumentNullException("Could not obtain IServiceProvider");
}
this.dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
if (this.dte == null)
{
throw new ArgumentNullException("Could not obtain DTE from host");
}
}
private void ProjectSync(IEnumerable<string> keepFileNames) {
var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();
foreach (string keepFileName in keepFileNames)
{
var item = this.dte.Solution.FindProjectItem(keepFileName);
if (item != null)
{
projectFiles.Add(keepFileName, item);
}
}
// Remove unused items from the project
/* foreach(var pair in projectFiles) // NEW
{
if (keepFileNames.Contains(pair.Key))
{
pair.Value.Delete();
}
} */
// Add missing files to the project
foreach(string fileName in keepFileNames)
{
if (!projectFiles.ContainsKey(fileName))
{
EnvDTE.Project targetProj = null;
foreach (EnvDTE.Project proj in this.dte.Solution.Projects)
{
if (string.IsNullOrEmpty(proj.FullName))
{
continue;
}
if (fileName.Contains(Path.GetDirectoryName(proj.FullName) + #"\"))
{
targetProj = proj;
break;
}
}
var targetDir = NavigateTo(targetProj, fileName);
if (targetDir == null)
{
targetProj.ProjectItems.AddFromFile(fileName);
continue;
}
targetDir.ProjectItems.AddFromFile(fileName);
}
}
}
private void CheckoutFileIfRequired(String fileName)
{
var sc = dte.SourceControl;
if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
{
dte.SourceControl.CheckOutItem(fileName);
}
}
public EnvDTE.ProjectItem NavigateTo(EnvDTE.Project project, string path)
{
if (string.IsNullOrEmpty(project.FullName))
{
return null;
}
var projBase = Path.GetDirectoryName(project.FullName);
var fileBase = Path.GetDirectoryName(path);
var naviBase = fileBase.Replace(projBase + #"\", "");
if (string.IsNullOrEmpty(fileBase.Replace(projBase, "")))
{
return null;
}
var naviPoints = naviBase.Split('\\');
EnvDTE.ProjectItem item = null;
EnvDTE.ProjectItems items = project.ProjectItems;
foreach (var folder in naviPoints)
{
item = items.Item(folder);
items = item.ProjectItems;
}
return item;
}
}
} #>
The easiest way I have found to do this without plugins is to right-click your target project and go to Add -> Existing Item and select your generated file. This makes a copy of the file for you at the root of the project, which you can then move as needed. This allows you to easily transfer generated files to projects besides the one that they were generated in.
I haven't tested what happens when the .tt file is itself in the root of a project, but this definitely works so long as the .tt is in a subfolder (which is probably good practice anyhow).

Resources