download XSD with all imports - xsd

I use gml (3.1.1) XSDs in XSD for my application. I want to download all gml XSDs in version 3.1.1 in for example zip file. In other words: base xsd is here and I want to download this XSD with all imports in zip file or something like zip file. Is there any application which supports that?
I've found this downloader but it doesn't works for me (I think that this application is not supporting relative paths in imports which occurs in gml.xsd 3.1.1). Any ideas?

QTAssistant's XSR (I am associated with it) has an easy to use function that allows one to automatically import and refactor XSD content as local files from all sorts of sources. In the process it'll update schema location references, etc.
I've made a simple screen capture of the steps involved in achieving a task like this which should demonstrate its usability.

Based on the solution of mschwehl, I made an improved class to achieve the fetch. It suited well with the question. See https://github.com/mfalaize/schema-fetcher

You can achieve this using SOAP UI.
Follow these steps :
Create a project using the WSDL.
Choose your interface and open in interface viewer.
Navigate to the tab 'WSDL Content'.
Use the last icon under the tab 'WSDL Content' : 'Export the entire WSDL and included/imported files to a local directory'.
select the folder where you want the XSDs to be exported to.
Note: SOAPUI will remove all relative paths and will save all XSDs to the same folder.

I have written a simple java-main that does the job and change to relative url's
package dl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class SchemaPersister {
private static final String EXPORT_FILESYSTEM_ROOT = "C:/export/xsd";
// some caching of the http-responses
private static Map<String,String> _httpContentCache = new HashMap<String,String>();
public static void main(String[] args) {
try {
new SchemaPersister().doIt();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void doIt() throws Exception {
// // if you need an inouse-Proxy
// final String authUser = "xxxxx";
// final String authPassword = "xxxx"
//
// System.setProperty("http.proxyHost", "xxxxx");
// System.setProperty("http.proxyPort", "xxxx");
// System.setProperty("http.proxyUser", authUser);
// System.setProperty("http.proxyPassword", authPassword);
//
// Authenticator.setDefault(
// new Authenticator() {
// public PasswordAuthentication getPasswordAuthentication() {
// return new PasswordAuthentication(authUser, authPassword.toCharArray());
// }
// }
// );
//
Set <SchemaElement> allElements = new HashSet<SchemaElement>() ;
// URL url = new URL("file:/C:/xauslaender-nachrichten-administration.xsd");
URL url = new URL("http://www.osci.de/xauslaender141/xauslaender-nachrichten-bamf-abh.xsd");
allElements.add ( new SchemaElement(url));
for (SchemaElement e: allElements) {
System.out.println("processing " + e);
e.doAll();
}
System.out.println("done!");
}
class SchemaElement {
private URL _url;
private String _content;
public List <SchemaElement> _imports ;
public List <SchemaElement> _includes ;
public SchemaElement(URL url) {
this._url = url;
}
public void checkIncludesAndImportsRecursive() throws Exception {
InputStream in = new ByteArrayInputStream(downloadContent() .getBytes("UTF-8"));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(in);
List<Node> includeNodeList = null;
List<Node> importNodeList = null;
includeNodeList = getXpathAttribute(doc,"/*[local-name()='schema']/*[local-name()='include']");
_includes = new ArrayList <SchemaElement> ();
for ( Node element: includeNodeList) {
Node sl = element.getAttributes().getNamedItem("schemaLocation");
if (sl == null) {
System.out.println(_url + " defines one import but no schemaLocation");
continue;
}
String asStringAttribute = sl.getNodeValue();
URL url = buildUrl(asStringAttribute,_url);
SchemaElement tmp = new SchemaElement(url);
tmp.setSchemaLocation(asStringAttribute);
tmp.checkIncludesAndImportsRecursive();
_includes.add(tmp);
}
importNodeList = getXpathAttribute(doc,"/*[local-name()='schema']/*[local-name()='import']");
_imports = new ArrayList <SchemaElement> ();
for ( Node element: importNodeList) {
Node sl = element.getAttributes().getNamedItem("schemaLocation");
if (sl == null) {
System.out.println(_url + " defines one import but no schemaLocation");
continue;
}
String asStringAttribute = sl.getNodeValue();
URL url = buildUrl(asStringAttribute,_url);
SchemaElement tmp = new SchemaElement(url);
tmp.setSchemaLocation(asStringAttribute);
tmp.checkIncludesAndImportsRecursive();
_imports.add(tmp);
}
in.close();
}
private String schemaLocation;
private void setSchemaLocation(String schemaLocation) {
this.schemaLocation = schemaLocation;
}
// http://stackoverflow.com/questions/10159186/how-to-get-parent-url-in-java
private URL buildUrl(String asStringAttribute, URL parent) throws Exception {
if (asStringAttribute.startsWith("http")) {
return new URL(asStringAttribute);
}
if (asStringAttribute.startsWith("file")) {
return new URL(asStringAttribute);
}
// relative URL
URI parentUri = parent.toURI().getPath().endsWith("/") ? parent.toURI().resolve("..") : parent.toURI().resolve(".");
return new URL(parentUri.toURL().toString() + asStringAttribute );
}
public void doAll() throws Exception {
System.out.println("READ ELEMENTS");
checkIncludesAndImportsRecursive();
System.out.println("PRINTING DEPENDENCYS");
printRecursive(0);
System.out.println("GENERATE OUTPUT");
patchAndPersistRecursive(0);
}
public void patchAndPersistRecursive(int level) throws Exception {
File f = new File(EXPORT_FILESYSTEM_ROOT + File.separator + this.getXDSName() );
System.out.println("FILENAME: " + f.getAbsolutePath());
if (_imports.size() > 0) {
for (int i = 0; i < level; i++) {
System.out.print(" ");
}
System.out.println("IMPORTS");
for (SchemaElement kid : _imports) {
kid.patchAndPersistRecursive(level+1);
}
}
if (_includes.size() > 0) {
for (int i = 0; i < level; i++) {
System.out.print(" ");
}
System.out.println("INCLUDES");
for (SchemaElement kid : _includes) {
kid.patchAndPersistRecursive(level+1);
}
}
String contentTemp = downloadContent();
for (SchemaElement i : _imports ) {
if (i.isHTTP()) {
contentTemp = contentTemp.replace(
"<xs:import schemaLocation=\"" + i.getSchemaLocation() ,
"<xs:import schemaLocation=\"" + i.getXDSName() );
}
}
for (SchemaElement i : _includes ) {
if (i.isHTTP()) {
contentTemp = contentTemp.replace(
"<xs:include schemaLocation=\"" + i.getSchemaLocation(),
"<xs:include schemaLocation=\"" + i.getXDSName() );
}
}
FileOutputStream fos = new FileOutputStream(f);
fos.write(contentTemp.getBytes("UTF-8"));
fos.close();
System.out.println("File written: " + f.getAbsolutePath() );
}
public void printRecursive(int level) {
for (int i = 0; i < level; i++) {
System.out.print(" ");
}
System.out.println(_url.toString());
if (this._imports.size() > 0) {
for (int i = 0; i < level; i++) {
System.out.print(" ");
}
System.out.println("IMPORTS");
for (SchemaElement kid : this._imports) {
kid.printRecursive(level+1);
}
}
if (this._includes.size() > 0) {
for (int i = 0; i < level; i++) {
System.out.print(" ");
}
System.out.println("INCLUDES");
for (SchemaElement kid : this._includes) {
kid.printRecursive(level+1);
}
}
}
String getSchemaLocation() {
return schemaLocation;
}
/**
* removes html:// and replaces / with _
* #return
*/
private String getXDSName() {
String tmp = schemaLocation;
// Root on local File-System -- just grap the last part of it
if (tmp == null) {
tmp = _url.toString().replaceFirst(".*/([^/?]+).*", "$1");
}
if ( isHTTP() ) {
tmp = tmp.replace("http://", "");
tmp = tmp.replace("/", "_");
} else {
tmp = tmp.replace("/", "_");
tmp = tmp.replace("\\", "_");
}
return tmp;
}
private boolean isHTTP() {
return _url.getProtocol().startsWith("http");
}
private String downloadContent() throws Exception {
if (_content == null) {
System.out.println("reading content from " + _url.toString());
if (_httpContentCache.containsKey(_url.toString())) {
this._content = _httpContentCache.get(_url.toString());
System.out.println("Cache hit! " + _url.toString());
} else {
System.out.println("Download " + _url.toString());
Scanner scan = new Scanner(_url.openStream(), "UTF-8");
if (isHTTP()) {
this._content = scan.useDelimiter("\\A").next();
} else {
this._content = scan.useDelimiter("\\Z").next();
}
scan.close();
if (this._content != null) {
_httpContentCache.put(_url.toString(), this._content);
}
}
}
if (_content == null) {
throw new NullPointerException("Content of " + _url.toString() + "is null ");
}
return _content;
}
private List<Node> getXpathAttribute(Document doc, String path) throws Exception {
List <Node> returnList = new ArrayList <Node> ();
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
{
XPathExpression expr = xpath.compile(path);
NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET );
for (int i = 0 ; i < nodeList.getLength(); i++) {
Node n = nodeList.item(i);
returnList.add(n);
}
}
return returnList;
}
#Override
public String toString() {
if (_url != null) {
return _url.toString();
}
return super.toString();
}
}
}

I created a python tool to recursively download XSDs with relative paths in import tags (eg: <import schemaLocation="../../../../abc)
https://github.com/n-a-t-e/xsd_download
After downloading the schema you can use xmllint to validate an XML document

I am using org.apache.xmlbeans.impl.tool.SchemaResourceManager from the xmlbeans project. This class is quick and easy to use.
for example:
SchemaResourceManager manager = new SchemaResourceManager(new File(dir));
manager.process(schemaUris, emptyArray(), false, true, true);
manager.writeCache();
This class has a main method that documents the different options available.

Related

How to replace <w:sdt> Xml in Word Doc using Apache POI as XWPFSDT and XWPFSDTContent object is read-only

In previous code we would use XWPFSParagraph and Runs to find Merge Tokens that we would replace with data in our database. However, now we need to use Content Controls and do the same thing. The problem with Paragraph and Runs is that Content controls do not appear as a single run, like a Merge Token would. And we need to get the content control title to act like a Merge Token name would be so we know where to find the data in the database, and then replace it in the document. In Content Controls we wouldn't be replacing the Content Control with the data from the db, but we would have to set the Text value in the <w:sdtContentControl> with that data.
I thought about converting the <x:sdt> object into the xml text, but then it is now removed from the Poi object because it is now a string on its own.
So I was thinking of finding the <x:sdt> and fully replacing it with a new one where each part of it would be the same except the part in the <w:sdtContentControl> section. Is this possible? Any recommendations on how to use Poi to "modify" the sdt we get from the Word doc?
XWPFSDT as well as XWPFSDTCell are in experimental state up to now. They don't even have access to their underlying CTSdtBlock, CTSdtRun and CTSdtCell classes. I don't know why. So extending them to provide writing into the CTSdtBlock, CTSdtRun or CTSdtCell is not possible. If that is the need, then a new class is needed which can be created from any kind of Word SDT content control object. This class SDTContentControl could look like so:
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtBlock;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtContentBlock;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtContentRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtCell;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtContentCell;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.values.XmlObjectBase;
import javax.xml.namespace.QName;
import java.util.Calendar;
import java.text.SimpleDateFormat;
import java.math.BigDecimal;
import java.text.DecimalFormat;
public class SDTContentControl {
private XmlObject object = null;
public SDTContentControl(XmlObject object) {
this.object = object;
}
public String getTitle() {
if (this.object instanceof CTSdtBlock) {
CTSdtBlock ctSdtBlock = (CTSdtBlock)this.object;
if (ctSdtBlock.isSetSdtPr()) {
if (ctSdtBlock.getSdtPr().isSetAlias()) {
return ctSdtBlock.getSdtPr().getAlias().getVal();
}
}
} else if (this.object instanceof CTSdtRun) {
CTSdtRun ctSdtRun = (CTSdtRun)this.object;
if (ctSdtRun.isSetSdtPr()) {
if (ctSdtRun.getSdtPr().isSetAlias()) {
return ctSdtRun.getSdtPr().getAlias().getVal();
}
}
} else if (this.object instanceof CTSdtCell) {
CTSdtCell ctSdtCell = (CTSdtCell)this.object;
if (ctSdtCell.isSetSdtPr()) {
if (ctSdtCell.getSdtPr().isSetAlias()) {
return ctSdtCell.getSdtPr().getAlias().getVal();
}
}
}
return null;
}
public String getTag() {
if (this.object instanceof CTSdtBlock) {
CTSdtBlock ctSdtBlock = (CTSdtBlock)this.object;
if (ctSdtBlock.isSetSdtPr()) {
if (ctSdtBlock.getSdtPr().isSetTag()) {
return ctSdtBlock.getSdtPr().getTag().getVal();
}
}
} else if (this.object instanceof CTSdtRun) {
CTSdtRun ctSdtRun = (CTSdtRun)this.object;
if (ctSdtRun.isSetSdtPr()) {
if (ctSdtRun.getSdtPr().isSetTag()) {
return ctSdtRun.getSdtPr().getTag().getVal();
}
}
} else if (this.object instanceof CTSdtCell) {
CTSdtCell ctSdtCell = (CTSdtCell)this.object;
if (ctSdtCell.isSetSdtPr()) {
if (ctSdtCell.getSdtPr().isSetTag()) {
return ctSdtCell.getSdtPr().getTag().getVal();
}
}
}
return null;
}
public String getContentText() {
XmlObject[] sdtContents = this.object.selectPath(
"declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' "
+".//w:sdtContent");
for (XmlObject sdtContent : sdtContents) {
if (sdtContent instanceof XmlObjectBase) {
return ((XmlObjectBase)sdtContent).getStringValue();
}
}
return null;
}
public void setContent(String text) {
if (this.object instanceof CTSdtBlock) {
CTSdtBlock ctSdtBlock = (CTSdtBlock)this.object;
if (ctSdtBlock.isSetSdtContent()) {
CTSdtContentBlock sdtContentBlock = ctSdtBlock.getSdtContent();
CTP ctP = sdtContentBlock.getPArray(0); if (ctP == null) ctP = CTP.Factory.newInstance();
for (int r = ctP.getRList().size()-1; r >= 0 ; r--) ctP.removeR(r);
CTR ctR = ctP.addNewR();
if (ctSdtBlock.isSetSdtPr()) {
if (ctSdtBlock.getSdtPr().isSetRPr()) {
ctR.setRPr(ctSdtBlock.getSdtPr().getRPr());
}
}
CTText ctText = ctR.addNewT();
ctText.setStringValue(text);
sdtContentBlock.setPArray(new CTP[]{ctP});
}
} else if (this.object instanceof CTSdtRun) {
CTSdtRun ctSdtRun = (CTSdtRun)this.object;
if (ctSdtRun.isSetSdtContent()) {
CTSdtContentRun sdtContentRun = ctSdtRun.getSdtContent();
CTR ctR = CTR.Factory.newInstance();
if (ctSdtRun.isSetSdtPr()) {
if (ctSdtRun.getSdtPr().isSetRPr()) {
ctR.setRPr(ctSdtRun.getSdtPr().getRPr());
}
}
CTText ctText = ctR.addNewT();
ctText.setStringValue(text);
sdtContentRun.setRArray(new CTR[]{ctR});
}
} else if (this.object instanceof CTSdtCell) {
CTSdtCell ctSdtCell = (CTSdtCell)this.object;
if (ctSdtCell.isSetSdtContent()) {
CTSdtContentCell sdtContentCell = ctSdtCell.getSdtContent();
for (int c = 0; c < sdtContentCell.getTcList().size(); c++) {
CTTc ctTc = sdtContentCell.getTcList().get(c);
CTP ctP = ctTc.getPArray(0); if (ctP == null) ctP = CTP.Factory.newInstance();
for (int r = ctP.getRList().size()-1; r >= 0 ; r--) ctP.removeR(r);
CTR ctR = ctP.addNewR();
if (ctSdtCell.isSetSdtPr()) {
if (ctSdtCell.getSdtPr().isSetRPr()) {
ctR.setRPr(ctSdtCell.getSdtPr().getRPr());
}
}
CTText ctText = ctR.addNewT();
ctText.setStringValue(text);
ctTc.setPArray(new CTP[]{ctP});
}
}
}
}
public void setContent(Calendar calendar) {
String dateFormat = "yyyy-MM-dd";
if (this.object instanceof CTSdtBlock) {
CTSdtBlock ctSdtBlock = (CTSdtBlock)this.object;
if (ctSdtBlock.isSetSdtPr()) {
if (ctSdtBlock.getSdtPr().isSetDate()) {
if (ctSdtBlock.getSdtPr().getDate().isSetDateFormat()) {
dateFormat = ctSdtBlock.getSdtPr().getDate().getDateFormat().getVal();
}
ctSdtBlock.getSdtPr().getDate().setFullDate(calendar);
}
}
} else if (this.object instanceof CTSdtRun) {
CTSdtRun ctSdtRun = (CTSdtRun)this.object;
if (ctSdtRun.isSetSdtPr()) {
if (ctSdtRun.getSdtPr().isSetDate()) {
if (ctSdtRun.getSdtPr().getDate().isSetDateFormat()) {
dateFormat = ctSdtRun.getSdtPr().getDate().getDateFormat().getVal();
}
ctSdtRun.getSdtPr().getDate().setFullDate(calendar);
}
}
} else if (this.object instanceof CTSdtCell) {
CTSdtCell ctSdtCell = (CTSdtCell)this.object;
if (ctSdtCell.isSetSdtPr()) {
if (ctSdtCell.getSdtPr().isSetDate()) {
if (ctSdtCell.getSdtPr().getDate().isSetDateFormat()) {
dateFormat = ctSdtCell.getSdtPr().getDate().getDateFormat().getVal();
}
ctSdtCell.getSdtPr().getDate().setFullDate(calendar);
}
}
}
SimpleDateFormat simpledDateFormat = new SimpleDateFormat(dateFormat);
String text = simpledDateFormat.format(calendar.getTime());
this.setContent(text);
}
public void setContent(BigDecimal value) {
DecimalFormat decimalFormat = new DecimalFormat("#,##0.00");
String text = decimalFormat.format(value.doubleValue());
this.setContent(text);
}
public void setContent(Object content) {
if (content instanceof String) {
this.setContent((String)content);
} else if (content instanceof Calendar) {
this.setContent((Calendar)content);
} else if (content instanceof BigDecimal) {
this.setContent((BigDecimal)content);
//} else if (content instanceof ...) {
//ToDo
} else {
this.setContent(String.valueOf(content));
}
}
}
A list of all SDT content control objects can be created from a XWPFDocument like so:
...
/*modifiers*/ List<SDTContentControl> extractSDTsFromBody(XWPFDocument document) {
SDTContentControl sdt;
XmlCursor xmlcursor = document.getDocument().getBody().newCursor();
QName qnameSdt = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "sdt", "w");
List<SDTContentControl> allsdts = new ArrayList<SDTContentControl>();
while (xmlcursor.hasNextToken()) {
XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
if (tokentype.isStart()) {
if (qnameSdt.equals(xmlcursor.getName())) {
if (xmlcursor.getObject() instanceof XmlObject) {
sdt = new SDTContentControl((XmlObject)xmlcursor.getObject());
allsdts.add(sdt);
}
}
}
}
return allsdts;
}
...
The SDTContentControl provides methods to get the content control title, the content control tag and the content control text content. It also provides a method to set the content control content from any kind of Object.
Complete example:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.xwpf.usermodel.*;
import java.util.List;
import java.util.ArrayList;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import javax.xml.namespace.QName;
import java.util.GregorianCalendar;
import java.math.BigDecimal;
public class WordFillContentControls {
private static List<SDTContentControl> extractSDTsFromBody(XWPFDocument document) {
SDTContentControl sdt;
XmlCursor xmlcursor = document.getDocument().getBody().newCursor();
QName qnameSdt = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "sdt", "w");
List<SDTContentControl> allsdts = new ArrayList<SDTContentControl>();
while (xmlcursor.hasNextToken()) {
XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
if (tokentype.isStart()) {
if (qnameSdt.equals(xmlcursor.getName())) {
if (xmlcursor.getObject() instanceof XmlObject) {
sdt = new SDTContentControl((XmlObject)xmlcursor.getObject());
allsdts.add(sdt);
}
}
}
}
return allsdts;
}
public static void main(String[] args) throws Exception {
String[] contentControlTags = new String[]{
"NameTag", "GenderTag", "DateTag", "AmountTag",
"DescriptionTag", "Col1Tag", "Col2Tag",
"Col1DateTag", "Col2ChooseTag"
};
Object[] contents = new Object[]{
"Axel Richter", "male", new GregorianCalendar(2022, 0, 1), BigDecimal.valueOf(1234.56),
"Lorem ipsum semit dolor ... dolor semit ...", "Blah blah", "Blubb blubb",
new GregorianCalendar(1964, 11, 21), "My choice"
};
XWPFDocument document = new XWPFDocument(new FileInputStream("./WordFormContentControl.docx"));
List<SDTContentControl> allsdts = extractSDTsFromBody(document);
for (SDTContentControl sdt : allsdts) {
//System.out.println(sdt);
String title = sdt.getTitle();
String tag = sdt.getTag();
String content = sdt.getContentText();
System.out.println(title + ": " + tag + ": " + content);
for (int i = 0; i < contentControlTags.length; i++) {
String tagToReplace = contentControlTags[i];
if (tagToReplace.equals(tag)) {
Object contentO = contents[i];
sdt.setContent(contentO);
}
}
}
allsdts = extractSDTsFromBody(document);
for (SDTContentControl sdt : allsdts) {
String title = sdt.getTitle();
String tag = sdt.getTag();
String content = sdt.getContentText();
System.out.println(title + ": " + tag + ": " + content);
}
FileOutputStream out = new FileOutputStream("./WordFormContentControlResult.docx");
document.write(out);
out.close();
document.close();
}
}

How can I read the schema (XSD) from Saxon after loading an XML & XSD file?

Our program displays a tree control showing the metadata structure of the XML file they are using as a datasource. So it displays all elements & attributes in use in the XML file, like this:
Employees
Employee
FirstName
LastName
Orders
Order
OrderId
For the case where the user does not pass us a XSD file, we need to walk the XML file and build up the metadata structure.
The full code for this is at SaxonQuestions.zip, TestBuildTreeWithSchema.java and is also listed below.
The below code works but it has a problem. Suppose under Employee there's an element for SpouseName. This is only populated if the employee is married. What if the sample data file I have is all unmarried employees? Then the below code does not know there's a SpouseName element.
So my question is - how can I read the schema directly, instead of using the below code. If I read the schema then I get every node & attribute including the optional ones. I also get the type. And the schema optionally has a description for each node and I get that too.
Therefore, I need to read the schema itself. How can I do that?
And secondary question - why is the type for an int BigInteger instead of Integer or Long? I see this with Employee/#EmployeeID in Southwind.xml & Southwind.xsd.
TestBuildTreeWithSample.java
import net.sf.saxon.s9api.*;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
public class TestBuildTreeWithSchema {
public static void main(String[] args) throws Exception {
XmlDatasource datasource = new XmlDatasource(
new FileInputStream(new File("files", "SouthWind.xml").getCanonicalPath()),
new FileInputStream(new File("files", "SouthWind.xsd").getCanonicalPath()));
// get the root element
XdmNode rootNode = null;
for (XdmNode node : datasource.getXmlRootNode().children()) {
if (node.getNodeKind() == XdmNodeKind.ELEMENT) {
rootNode = node;
break;
}
}
TestBuildTreeWithSchema buildTree = new TestBuildTreeWithSchema(rootNode);
Element root = buildTree.addNode();
System.out.println("Schema:");
printElement("", root);
}
private static void printElement(String indent, Element element) {
System.out.println(indent + "<" + element.name + "> (" + (element.type == null ? "null" : element.type.getSimpleName()) + ")");
indent += " ";
for (Attribute attr : element.attributes)
System.out.println(indent + "=" + attr.name + " (" + (attr.type == null ? "null" : attr.type.getSimpleName()) + ")");
for (Element child : element.children)
printElement(indent, child);
}
protected XdmItem currentNode;
public TestBuildTreeWithSchema(XdmItem currentNode) {
this.currentNode = currentNode;
}
private Element addNode() throws SaxonApiException {
String name = ((XdmNode)currentNode).getNodeName().getLocalName();
// Question:
// Is this the best way to determine that this element has data (as opposed to child elements)?
Boolean elementHasData;
try {
((XdmNode) currentNode).getTypedValue();
elementHasData = true;
} catch (Exception ex) {
elementHasData = false;
}
// Questions:
// Is this the best way to get the type of the element value?
// Why BigInteger instead of Long for int?
Class valueClass = ! elementHasData ? null : ((XdmAtomicValue)((XdmNode)currentNode).getTypedValue()).getValue().getClass();
Element element = new Element(name, valueClass, null);
// add in attributes
XdmSequenceIterator currentSequence;
if ((currentSequence = moveTo(Axis.ATTRIBUTE)) != null) {
do {
name = ((XdmNode) currentNode).getNodeName().getLocalName();
// Questions:
// Is this the best way to get the type of the attribute value?
// Why BigInteger instead of Long for int?
valueClass = ((XdmAtomicValue)((XdmNode)currentNode).getTypedValue()).getValue().getClass();
Attribute attr = new Attribute(name, valueClass, null);
element.attributes.add(attr);
} while (moveToNextInCurrentSequence(currentSequence));
moveTo(Axis.PARENT);
}
// add in children elements
if ((currentSequence = moveTo(Axis.CHILD)) != null) {
do {
Element child = addNode();
// if we don't have this, add it
Element existing = element.getChildByName(child.name);
if (existing == null)
element.children.add(child);
else
// add in any children this does not have
existing.addNewItems (child);
} while (moveToNextInCurrentSequence(currentSequence));
moveTo(Axis.PARENT);
}
return element;
}
// moves to element or attribute
private XdmSequenceIterator moveTo(Axis axis) {
XdmSequenceIterator en = ((XdmNode) currentNode).axisIterator(axis);
boolean gotIt = false;
while (en.hasNext()) {
currentNode = en.next();
if (((XdmNode) currentNode).getNodeKind() == XdmNodeKind.ELEMENT || ((XdmNode) currentNode).getNodeKind() == XdmNodeKind.ATTRIBUTE) {
gotIt = true;
break;
}
}
if (gotIt) {
if (axis == Axis.ATTRIBUTE || axis == Axis.CHILD || axis == Axis.NAMESPACE)
return en;
return null;
}
return null;
}
// moves to next element or attribute
private Boolean moveToNextInCurrentSequence(XdmSequenceIterator currentSequence)
{
if (currentSequence == null)
return false;
while (currentSequence.hasNext())
{
currentNode = currentSequence.next();
if (((XdmNode)currentNode).getNodeKind() == XdmNodeKind.ELEMENT || ((XdmNode)currentNode).getNodeKind() == XdmNodeKind.ATTRIBUTE)
return true;
}
return false;
}
static class Node {
String name;
Class type;
String description;
public Node(String name, Class type, String description) {
this.name = name;
this.type = type;
this.description = description;
}
}
static class Element extends Node {
List<Element> children;
List<Attribute> attributes;
public Element(String name, Class type, String description) {
super(name, type, description);
children = new ArrayList<>();
attributes = new ArrayList<>();
}
public Element getChildByName(String name) {
for (Element child : children) {
if (child.name.equals(name))
return child;
}
return null;
}
public void addNewItems(Element child) {
for (Attribute attrAdd : child.attributes) {
boolean haveIt = false;
for (Attribute attrExist : attributes)
if (attrExist.name.equals(attrAdd.name)) {
haveIt = true;
break;
}
if (!haveIt)
attributes.add(attrAdd);
}
for (Element elemAdd : child.children) {
Element exist = null;
for (Element elemExist : children)
if (elemExist.name.equals(elemAdd.name)) {
exist = elemExist;
break;
}
if (exist == null)
children.add(elemAdd);
else
exist.addNewItems(elemAdd);
}
}
}
static class Attribute extends Node {
public Attribute(String name, Class type, String description) {
super(name, type, description);
}
}
}
XmlDatasource.java
import com.saxonica.config.EnterpriseConfiguration;
import com.saxonica.ee.s9api.SchemaValidatorImpl;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.FeatureKeys;
import net.sf.saxon.s9api.*;
import net.sf.saxon.type.SchemaException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
public class XmlDatasource {
/** the DOM all searches are against */
private XdmNode xmlRootNode;
private XPathCompiler xPathCompiler;
/** key == the prefix; value == the uri mapped to that prefix */
private HashMap<String, String> prefixToUriMap = new HashMap<>();
/** key == the uri mapped to that prefix; value == the prefix */
private HashMap<String, String> uriToPrefixMap = new HashMap<>();
public XmlDatasource (InputStream xmlData, InputStream schemaFile) throws SAXException, SchemaException, SaxonApiException, IOException {
boolean haveSchema = schemaFile != null;
// call this before any instantiation of Saxon classes.
Configuration config = createEnterpriseConfiguration();
if (haveSchema) {
Source schemaSource = new StreamSource(schemaFile);
config.addSchemaSource(schemaSource);
}
Processor processor = new Processor(config);
DocumentBuilder doc_builder = processor.newDocumentBuilder();
XMLReader reader = createXMLReader();
InputSource xmlSource = new InputSource(xmlData);
SAXSource saxSource = new SAXSource(reader, xmlSource);
if (haveSchema) {
SchemaValidator validator = new SchemaValidatorImpl(processor);
doc_builder.setSchemaValidator(validator);
}
xmlRootNode = doc_builder.build(saxSource);
xPathCompiler = processor.newXPathCompiler();
if (haveSchema)
xPathCompiler.setSchemaAware(true);
declareNameSpaces();
}
public XdmNode getXmlRootNode() {
return xmlRootNode;
}
public XPathCompiler getxPathCompiler() {
return xPathCompiler;
}
/**
* Create a XMLReader set to disallow XXE aattacks.
* #return a safe XMLReader.
*/
public static XMLReader createXMLReader() throws SAXException {
XMLReader reader = XMLReaderFactory.createXMLReader();
// stop XXE https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
return reader;
}
private void declareNameSpaces() throws SaxonApiException {
// saxon has some of their functions set up with this.
prefixToUriMap.put("saxon", "http://saxon.sf.net");
uriToPrefixMap.put("http://saxon.sf.net", "saxon");
XdmValue list = xPathCompiler.evaluate("//namespace::*", xmlRootNode);
if (list == null || list.size() == 0)
return;
for (int index=0; index<list.size(); index++) {
XdmNode node = (XdmNode) list.itemAt(index);
String prefix = node.getNodeName() == null ? "" : node.getNodeName().getLocalName();
// xml, xsd, & xsi are XML structure ones, not ones used in the XML
if (prefix.equals("xml") || prefix.equals("xsd") || prefix.equals("xsi"))
continue;
// use default prefix if prefix is empty.
if (prefix == null || prefix.isEmpty())
prefix = "def";
// this returns repeats, so if a repeat, go on to next.
if (prefixToUriMap.containsKey(prefix))
continue;
String uri = node.getStringValue();
if (uri != null && !uri.isEmpty()) {
xPathCompiler.declareNamespace(prefix, uri);
prefixToUriMap.put(prefix, uri);
uriToPrefixMap.put(uri, prefix); }
}
}
public static EnterpriseConfiguration createEnterpriseConfiguration()
{
EnterpriseConfiguration configuration = new EnterpriseConfiguration();
configuration.supplyLicenseKey(new BufferedReader(new java.io.StringReader(deobfuscate(key))));
configuration.setConfigurationProperty(FeatureKeys.SUPPRESS_XPATH_WARNINGS, Boolean.TRUE);
return configuration;
}
}
Thanks for the clarifications. I think your real goal is to find a way to parse and process an XML Schema in Java without having to treat the XSD as an ordinary XML document (it is an ordinary XML document, but processing it using the standard facilities is not easy).
On that basis, I think this thread should help: In Java, how do I parse an xml schema (xsd) to learn what's valid at a given element?
Personally, I've never found any library that does a better job than the EMF XSD model. It's complex, but comprehensive.

How to read a compressed (gzip) file without extension in Spark

I am new to Spark and have a fun task in hand where I have to read a bunch of files from S3, which have some xml content in them.
These files are compressed (Gzip) but do not have that extension.
I read some questions on this here where people suggest to extend the default codec in Spark and force a different extension.
But in my case, there is no extension and the files are named in some 16 digit UUID format such as 2c7358ca472ad91057da84adfba.
You can use newAPIHadoopFile (instead of textFile) with a custom/modified TextInputFormat which forces the use of the GzipCodec.
Instead of calling sparkContext.textFile,
// gzip compressed but no .gz extension:
sparkContext.textFile("s3://mybucket/uuid")
we can use the underlying sparkContext.newAPIHadoopFile which allows us to specify how to read the input:
import org.apache.hadoop.mapreduce.lib.input.GzipInputFormatWithoutExtention
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.io.{LongWritable, Text}
sparkContext
.newAPIHadoopFile(
"s3://mybucket/uuid",
classOf[GzipInputFormatWithoutExtention], // This is our custom reader
classOf[LongWritable],
classOf[Text],
new Configuration(sparkContext.hadoopConfiguration)
)
.map { case (_, text) => text.toString }
The usual way of calling newAPIHadoopFile would be with TextInputFormat. This is the part which wraps how the file is read and where the compression codec is chosen based on the file extension.
Let's call it GzipInputFormatWithoutExtention and implement it as follow as an extension of TextInputFormat (this is a Java file and let's put it in package src/main/java/org/apache/hadoop/mapreduce/lib/input):
package org.apache.hadoop.mapreduce.lib.input;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import com.google.common.base.Charsets;
public class GzipInputFormatWithoutExtention extends TextInputFormat {
public RecordReader<LongWritable, Text> createRecordReader(
InputSplit split,
TaskAttemptContext context
) {
String delimiter =
context.getConfiguration().get("textinputformat.record.delimiter");
byte[] recordDelimiterBytes = null;
if (null != delimiter)
recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
// Here we use our custom `GzipWithoutExtentionLineRecordReader`
// instead of `LineRecordReader`:
return new GzipWithoutExtentionLineRecordReader(recordDelimiterBytes);
}
#Override
protected boolean isSplitable(JobContext context, Path file) {
return false; // gzip isn't a splittable codec (as opposed to bzip2)
}
}
In fact we have to go one level deeper and also replace the default LineRecordReader (Java) with our own (let's call it GzipWithoutExtentionLineRecordReader).
As it's quite difficult to inherit from LineRecordReader, we can copy LineRecordReader (in src/main/java/org/apache/hadoop/mapreduce/lib/input) and slightly modify (and simplify) the initialize(InputSplit genericSplit, TaskAttemptContext context) method by forcing the usage of the Gzip codec:
(the only changes compared to the original LineRecordReader have been given a comment explaining what's happening)
package org.apache.hadoop.mapreduce.lib.input;
import java.io.IOException;
import org.apache.hadoop.io.compress.*;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.Seekable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#InterfaceAudience.LimitedPrivate({"MapReduce", "Pig"})
#InterfaceStability.Evolving
public class GzipWithoutExtentionLineRecordReader extends RecordReader<LongWritable, Text> {
private static final Logger LOG =
LoggerFactory.getLogger(GzipWithoutExtentionLineRecordReader.class);
public static final String MAX_LINE_LENGTH =
"mapreduce.input.linerecordreader.line.maxlength";
private long start;
private long pos;
private long end;
private SplitLineReader in;
private FSDataInputStream fileIn;
private Seekable filePosition;
private int maxLineLength;
private LongWritable key;
private Text value;
private boolean isCompressedInput;
private Decompressor decompressor;
private byte[] recordDelimiterBytes;
public GzipWithoutExtentionLineRecordReader(byte[] recordDelimiter) {
this.recordDelimiterBytes = recordDelimiter;
}
public void initialize(
InputSplit genericSplit,
TaskAttemptContext context
) throws IOException {
FileSplit split = (FileSplit) genericSplit;
Configuration job = context.getConfiguration();
this.maxLineLength = job.getInt(MAX_LINE_LENGTH, Integer.MAX_VALUE);
start = split.getStart();
end = start + split.getLength();
final Path file = split.getPath();
// open the file and seek to the start of the split
final FileSystem fs = file.getFileSystem(job);
fileIn = fs.open(file);
// This line is modified to force the use of the GzipCodec:
// CompressionCodec codec = new CompressionCodecFactory(job).getCodec(file);
CompressionCodecFactory ccf = new CompressionCodecFactory(job);
CompressionCodec codec = ccf.getCodecByClassName(GzipCodec.class.getName());
// This part has been extremely simplified as we don't have to handle
// all the different codecs:
isCompressedInput = true;
decompressor = CodecPool.getDecompressor(codec);
if (start != 0) {
throw new IOException(
"Cannot seek in " + codec.getClass().getSimpleName() + " compressed stream"
);
}
in = new SplitLineReader(
codec.createInputStream(fileIn, decompressor), job, this.recordDelimiterBytes
);
filePosition = fileIn;
if (start != 0) {
start += in.readLine(new Text(), 0, maxBytesToConsume(start));
}
this.pos = start;
}
private int maxBytesToConsume(long pos) {
return isCompressedInput
? Integer.MAX_VALUE
: (int) Math.max(Math.min(Integer.MAX_VALUE, end - pos), maxLineLength);
}
private long getFilePosition() throws IOException {
long retVal;
if (isCompressedInput && null != filePosition) {
retVal = filePosition.getPos();
} else {
retVal = pos;
}
return retVal;
}
private int skipUtfByteOrderMark() throws IOException {
int newMaxLineLength = (int) Math.min(3L + (long) maxLineLength,
Integer.MAX_VALUE);
int newSize = in.readLine(value, newMaxLineLength, maxBytesToConsume(pos));
pos += newSize;
int textLength = value.getLength();
byte[] textBytes = value.getBytes();
if ((textLength >= 3) && (textBytes[0] == (byte)0xEF) &&
(textBytes[1] == (byte)0xBB) && (textBytes[2] == (byte)0xBF)) {
LOG.info("Found UTF-8 BOM and skipped it");
textLength -= 3;
newSize -= 3;
if (textLength > 0) {
textBytes = value.copyBytes();
value.set(textBytes, 3, textLength);
} else {
value.clear();
}
}
return newSize;
}
public boolean nextKeyValue() throws IOException {
if (key == null) {
key = new LongWritable();
}
key.set(pos);
if (value == null) {
value = new Text();
}
int newSize = 0;
while (getFilePosition() <= end || in.needAdditionalRecordAfterSplit()) {
if (pos == 0) {
newSize = skipUtfByteOrderMark();
} else {
newSize = in.readLine(value, maxLineLength, maxBytesToConsume(pos));
pos += newSize;
}
if ((newSize == 0) || (newSize < maxLineLength)) {
break;
}
LOG.info("Skipped line of size " + newSize + " at pos " +
(pos - newSize));
}
if (newSize == 0) {
key = null;
value = null;
return false;
} else {
return true;
}
}
#Override
public LongWritable getCurrentKey() {
return key;
}
#Override
public Text getCurrentValue() {
return value;
}
public float getProgress() throws IOException {
if (start == end) {
return 0.0f;
} else {
return Math.min(1.0f, (getFilePosition() - start) / (float)(end - start));
}
}
public synchronized void close() throws IOException {
try {
if (in != null) {
in.close();
}
} finally {
if (decompressor != null) {
CodecPool.returnDecompressor(decompressor);
decompressor = null;
}
}
}
}

Files or no Files

I want my java program to constantly scan for new files in a particular folder.
I am expecting something like:
while(1) {
String csvFile = "D:\\myfiles\\done\\*.*";
br = new BufferedReader(new FileReader(csvFile));
// DO Some thing
}
Is there a good way to do this?
You probably do not want to execute the loop over and over, as this would cause poor performance. You can watch for directory changes instead, which will suspend the working thread while no new files are available (except from the article linked):
WatchKey key;
try {
key = watcher.take(); // blocking call, similar to sleep()
} catch (InterruptedException x) {
return; // this happens if your thread got interrupted
}
// do something with the recived key
You can use File to get all the files.
Check this:
File folder = new File("D:/myfiles/done");
File[] files = folder.listFiles();
for(File f : files){
// Read f in here
}
This FileObserver class looks quite promising:
/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: FileObserver.java,v 1.3 2008/06/25 05:44:08 qcheng Exp $
*
*/
package com.sun.identity.sm.flatfile;
import com.iplanet.am.util.SystemProperties;
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.SMSObjectListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
class FileObserver extends Thread {
private static Debug debug = Debug.getInstance("amSMSEvent");
private Map snapShot;
private int interval;
private boolean running;
private FlatFileEventManager eventManager;
FileObserver(FlatFileEventManager eventManager) {
setDaemon(true);
getPollingInterval();
this.eventManager = eventManager;
}
private void getPollingInterval() {
String time = SystemProperties.get(
Constants.CACHE_POLLING_TIME_PROPERTY);
interval = Constants.DEFAULT_CACHE_POLLING_TIME;
if (time != null) {
try {
interval = Integer.parseInt(time);
} catch (NumberFormatException nfe) {
debug.error(
"FileObserver.getCachePollingInterval", nfe);
}
}
interval = interval * 60 * 1000;
}
/**
* Returns <code>true</code> if thread is running.
*
* #return <code>true</code> if thread is running.
*/
public boolean isRunning() {
return running;
}
/**
* Stops the thread.
*/
public void stopThread() {
running = false;
}
public void run() {
running = true;
snapShot = getCurrentSnapShot();
try {
while (running) {
/*
* This flag set to false in the begin of the thread.
* when a node is added/delete from the file system, we need
* to toggle this flag which in turn ask the
* SMSEnhancedFlatFileObject to rebuild the directory tree.
*/
boolean needReloadRootNode = false;
sleep(interval);
Map newSnapShot = getCurrentSnapShot();
if (snapShot != null) {
for (Iterator i = newSnapShot.keySet().iterator();
i.hasNext();
) {
String filename = (String)i.next();
if (snapShot.containsKey(filename)) {
long prev =((Long)snapShot.get(filename))
.longValue();
long curr =((Long)newSnapShot.get(filename))
.longValue();
if (prev != curr) {
eventManager.notify(getDN(filename),
SMSObjectListener.MODIFY);
}
} else {
if (!needReloadRootNode) {
eventManager.reloadRootNode();
needReloadRootNode = true;
}
eventManager.notify(getDN(filename),
SMSObjectListener.ADD);
}
}
for (Iterator i = snapShot.keySet().iterator();
i.hasNext();
) {
String filename = (String)i.next();
if (!newSnapShot.containsKey(filename)) {
if (!needReloadRootNode) {
eventManager.reloadRootNode();
needReloadRootNode = true;
}
eventManager.notify(getDN(filename),
SMSObjectListener.DELETE);
}
}
}
snapShot = newSnapShot;
}
} catch (InterruptedException e) {
debug.warning("FileObserver.run", e);
}
}
private String getDN(String filename) {
BufferedReader buff = null;
String dn = null;
try{
buff = new BufferedReader(new FileReader(filename));
String line = buff.readLine();
if ((line != null) && line.startsWith("#")) {
dn = line.substring(1);
}
} catch (IOException e) {
debug.warning("FileObserver.getDN", e);
} finally {
if (buff != null) {
try {
buff.close();
} catch (IOException ex) {
//ignored
}
}
}
return dn;
}
private Map getCurrentSnapShot() {
Map snapshot = null;
String baseDir = SystemProperties.get(
SMSFlatFileObjectBase.SMS_FLATFILE_ROOTDIR_PROPERTY);
File dir = new File(baseDir);
String[] files = dir.list();
// if the war is not configured we may not get any files here.
if (files.length > 0) {
snapshot = new HashMap(files.length *2);
for (int i = 0; i < files.length; i++) {
String filename = baseDir + "/" + files[i];
File f = new File(filename);
if (!f.isDirectory()) {
snapshot.put(filename, new Long(f.lastModified()));
}
}
}
return snapshot;
}
}
If you prefer a library you can also check the Apache commons FileAlterationObserver.
This may be useful.
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
public class DirectoryWatchDemo {
public static void main(String[] args) {
try {
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("D:\\Mani_Test\\");
dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
System.out.println("Watch Service registered for dir: " + dir.getFileName());
while (true) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException ex) {
ex.printStackTrace();
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path fileName = ev.context();
System.out.println(kind.name() + ": " + fileName);
}
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (IOException ex) {
System.err.println(ex);
}
}
}
File object can be used for folder also,
Here is the code
String filePath = "D:\\myfiles\\done\\";
File dir = new File(filePath);
if (dir.isDirectory())
{
File[] fileList = dir.listFiles();
if (fileList.length > 0)
{
// Your code here
}
}

How can I get log4j to delete old rotating log files?

How can I get log4j to delete old rotating log files? I know I can set up automated jobs (cron for UNIX and scheduled task for Windows), but I want it cross platform, and I want it in our application's log configuration as a part of our application, rather than in separate code outside in OS specific scripting languages. Our application is not written in OS scripting languages, and I don't want to do this part of it in them.
RollingFileAppender does this. You just need to set maxBackupIndex to the highest value for the backup file.
Logs rotate for a reason, so that you only keep so many log files around. In log4j.xml you can add this to your node:
<param name="MaxBackupIndex" value="20"/>
The value tells log4j.xml to only keep 20 rotated log files around. You can limit this to 5 if you want or even 1. If your application isn't logging that much data, and you have 20 log files spanning the last 8 months, but you only need a weeks worth of logs, then I think you need to tweak your log4j.xml "MaxBackupIndex" and "MaxFileSize" params.
Alternatively, if you are using a properties file (instead of the xml) and wish to save 15 files (for example)
log4j.appender.[appenderName].MaxBackupIndex = 15
There is no default value to control deleting old log files created by DailyRollingFileAppender. But you can write your own custom Appender that deletes old log files in much the same way as setting maxBackupIndex does for RollingFileAppender.
Simple instructions found here
From 1:
If you are trying to use the Apache Log4J DailyRollingFileAppender for a daily log file, you may need to want to specify the maximum number of files which should be kept. Just like rolling RollingFileAppender supports maxBackupIndex. But the current version of Log4j (Apache log4j 1.2.16) does not provide any mechanism to delete old log files if you are using DailyRollingFileAppender. I tried to make small modifications in the original version of DailyRollingFileAppender to add maxBackupIndex property. So, it would be possible to clean up old log files which may not be required for future usage.
You can achieve it using custom log4j appender.
MaxNumberOfDays - possibility to set amount of days of rotated log files.
CompressBackups - possibility to archive old logs with zip extension.
package com.example.package;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class CustomLog4jAppender extends FileAppender {
private static final int TOP_OF_TROUBLE = -1;
private static final int TOP_OF_MINUTE = 0;
private static final int TOP_OF_HOUR = 1;
private static final int HALF_DAY = 2;
private static final int TOP_OF_DAY = 3;
private static final int TOP_OF_WEEK = 4;
private static final int TOP_OF_MONTH = 5;
private String datePattern = "'.'yyyy-MM-dd";
private String compressBackups = "false";
private String maxNumberOfDays = "7";
private String scheduledFilename;
private long nextCheck = System.currentTimeMillis() - 1;
private Date now = new Date();
private SimpleDateFormat sdf;
private RollingCalendar rc = new RollingCalendar();
private static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
public CustomLog4jAppender() {
}
public CustomLog4jAppender(Layout layout, String filename, String datePattern) throws IOException {
super(layout, filename, true);
this.datePattern = datePattern;
activateOptions();
}
public void setDatePattern(String pattern) {
datePattern = pattern;
}
public String getDatePattern() {
return datePattern;
}
#Override
public void activateOptions() {
super.activateOptions();
if (datePattern != null && fileName != null) {
now.setTime(System.currentTimeMillis());
sdf = new SimpleDateFormat(datePattern);
int type = computeCheckPeriod();
printPeriodicity(type);
rc.setType(type);
File file = new File(fileName);
scheduledFilename = fileName + sdf.format(new Date(file.lastModified()));
} else {
LogLog.error("Either File or DatePattern options are not set for appender [" + name + "].");
}
}
private void printPeriodicity(int type) {
String appender = "Log4J Appender: ";
switch (type) {
case TOP_OF_MINUTE:
LogLog.debug(appender + name + " to be rolled every minute.");
break;
case TOP_OF_HOUR:
LogLog.debug(appender + name + " to be rolled on top of every hour.");
break;
case HALF_DAY:
LogLog.debug(appender + name + " to be rolled at midday and midnight.");
break;
case TOP_OF_DAY:
LogLog.debug(appender + name + " to be rolled at midnight.");
break;
case TOP_OF_WEEK:
LogLog.debug(appender + name + " to be rolled at start of week.");
break;
case TOP_OF_MONTH:
LogLog.debug(appender + name + " to be rolled at start of every month.");
break;
default:
LogLog.warn("Unknown periodicity for appender [" + name + "].");
}
}
private int computeCheckPeriod() {
RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.ENGLISH);
Date epoch = new Date(0);
if (datePattern != null) {
for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
simpleDateFormat.setTimeZone(gmtTimeZone);
String r0 = simpleDateFormat.format(epoch);
rollingCalendar.setType(i);
Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
String r1 = simpleDateFormat.format(next);
if (!r0.equals(r1)) {
return i;
}
}
}
return TOP_OF_TROUBLE;
}
private void rollOver() throws IOException {
if (datePattern == null) {
errorHandler.error("Missing DatePattern option in rollOver().");
return;
}
String datedFilename = fileName + sdf.format(now);
if (scheduledFilename.equals(datedFilename)) {
return;
}
this.closeFile();
File target = new File(scheduledFilename);
if (target.exists()) {
Files.delete(target.toPath());
}
File file = new File(fileName);
boolean result = file.renameTo(target);
if (result) {
LogLog.debug(fileName + " -> " + scheduledFilename);
} else {
LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");
}
try {
this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
} catch (IOException e) {
errorHandler.error("setFile(" + fileName + ", false) call failed.");
}
scheduledFilename = datedFilename;
}
#Override
protected void subAppend(LoggingEvent event) {
long n = System.currentTimeMillis();
if (n >= nextCheck) {
now.setTime(n);
nextCheck = rc.getNextCheckMillis(now);
try {
cleanupAndRollOver();
} catch (IOException ioe) {
LogLog.error("cleanupAndRollover() failed.", ioe);
}
}
super.subAppend(event);
}
public String getCompressBackups() {
return compressBackups;
}
public void setCompressBackups(String compressBackups) {
this.compressBackups = compressBackups;
}
public String getMaxNumberOfDays() {
return maxNumberOfDays;
}
public void setMaxNumberOfDays(String maxNumberOfDays) {
this.maxNumberOfDays = maxNumberOfDays;
}
protected void cleanupAndRollOver() throws IOException {
File file = new File(fileName);
Calendar cal = Calendar.getInstance();
int maxDays = 7;
try {
maxDays = Integer.parseInt(getMaxNumberOfDays());
} catch (Exception e) {
// just leave it at 7.
}
cal.add(Calendar.DATE, -maxDays);
Date cutoffDate = cal.getTime();
if (file.getParentFile().exists()) {
File[] files = file.getParentFile().listFiles(new StartsWithFileFilter(file.getName(), false));
int nameLength = file.getName().length();
for (File value : Optional.ofNullable(files).orElse(new File[0])) {
String datePart;
try {
datePart = value.getName().substring(nameLength);
Date date = sdf.parse(datePart);
if (date.before(cutoffDate)) {
Files.delete(value.toPath());
} else if (getCompressBackups().equalsIgnoreCase("YES") || getCompressBackups().equalsIgnoreCase("TRUE")) {
zipAndDelete(value);
}
} catch (Exception pe) {
// This isn't a file we should touch (it isn't named correctly)
}
}
}
rollOver();
}
private void zipAndDelete(File file) throws IOException {
if (!file.getName().endsWith(".zip")) {
File zipFile = new File(file.getParent(), file.getName() + ".zip");
try (FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(zipFile);
ZipOutputStream zos = new ZipOutputStream(fos)) {
ZipEntry zipEntry = new ZipEntry(file.getName());
zos.putNextEntry(zipEntry);
byte[] buffer = new byte[4096];
while (true) {
int bytesRead = fis.read(buffer);
if (bytesRead == -1) {
break;
} else {
zos.write(buffer, 0, bytesRead);
}
}
zos.closeEntry();
}
Files.delete(file.toPath());
}
}
class StartsWithFileFilter implements FileFilter {
private String startsWith;
private boolean inclDirs;
StartsWithFileFilter(String startsWith, boolean includeDirectories) {
super();
this.startsWith = startsWith.toUpperCase();
inclDirs = includeDirectories;
}
public boolean accept(File pathname) {
if (!inclDirs && pathname.isDirectory()) {
return false;
} else {
return pathname.getName().toUpperCase().startsWith(startsWith);
}
}
}
class RollingCalendar extends GregorianCalendar {
private static final long serialVersionUID = -3560331770601814177L;
int type = CustomLog4jAppender.TOP_OF_TROUBLE;
RollingCalendar() {
super();
}
RollingCalendar(TimeZone tz, Locale locale) {
super(tz, locale);
}
void setType(int type) {
this.type = type;
}
long getNextCheckMillis(Date now) {
return getNextCheckDate(now).getTime();
}
Date getNextCheckDate(Date now) {
this.setTime(now);
switch (type) {
case CustomLog4jAppender.TOP_OF_MINUTE:
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.MINUTE, 1);
break;
case CustomLog4jAppender.TOP_OF_HOUR:
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.HOUR_OF_DAY, 1);
break;
case CustomLog4jAppender.HALF_DAY:
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
int hour = get(Calendar.HOUR_OF_DAY);
if (hour < 12) {
this.set(Calendar.HOUR_OF_DAY, 12);
} else {
this.set(Calendar.HOUR_OF_DAY, 0);
this.add(Calendar.DAY_OF_MONTH, 1);
}
break;
case CustomLog4jAppender.TOP_OF_DAY:
this.set(Calendar.HOUR_OF_DAY, 0);
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.DATE, 1);
break;
case CustomLog4jAppender.TOP_OF_WEEK:
this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
this.set(Calendar.HOUR_OF_DAY, 0);
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.WEEK_OF_YEAR, 1);
break;
case CustomLog4jAppender.TOP_OF_MONTH:
this.set(Calendar.DATE, 1);
this.set(Calendar.HOUR_OF_DAY, 0);
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.MONTH, 1);
break;
default:
throw new IllegalStateException("Unknown periodicity type.");
}
return getTime();
}
}
}
And use this properties in your log4j config file:
log4j.appender.[appenderName]=com.example.package.CustomLog4jAppender
log4j.appender.[appenderName].File=/logs/app-daily.log
log4j.appender.[appenderName].Append=true
log4j.appender.[appenderName].encoding=UTF-8
log4j.appender.[appenderName].layout=org.apache.log4j.EnhancedPatternLayout
log4j.appender.[appenderName].layout.ConversionPattern=%-5.5p %d %C{1.} - %m%n
log4j.appender.[appenderName].DatePattern='.'yyyy-MM-dd
log4j.appender.[appenderName].MaxNumberOfDays=7
log4j.appender.[appenderName].CompressBackups=true

Resources