ConfigurationPersistence.java
/*
** Module : ConfigurationPersistence.java
** Abstract : Reads and writes analytics configuration using XML.
**
** Copyright (c) 2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description----------------------------------
** 001 ECF 20170625 Created first version.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.report;
import java.io.*;
import java.sql.Date;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.Element;
import org.xml.sax.*;
import com.goldencode.p2j.report.server.*;
import com.goldencode.p2j.report.server.FileFilter;
import com.goldencode.util.*;
/**
* Helper object to persist and load report-related configuration to and from XML. This is used
* to export and import various bits of configuration created by users using the web application,
* which must survive database recreates.
* <p>
* Currently, the following information is handled by this mechanism:
* <ul>
* <li>User-defined report definitions.</li>
* <li>User credentials.</li>
* <li>Search history (TRPL expressions used in searches and custom reports).</li>
* <li>Filter profile definitions.</li>
* </ul>
*/
public class ConfigurationPersistence
implements XmlConstants,
ReportConstants
{
/**
* Default constructor.
*/
public ConfigurationPersistence()
{
}
/**
* Read the given file, which is expected to contain an XML document defining one or more
* reports. Add each report definition to the given list.
*
* @param file
* File containing XML report definitions.
* @param reports
* List of report definitions to be populated with those read from the file.
*
* @return Type of reports contained in the file; or 0 if file does not exist or is not valid.
*
* @throws ParserConfigurationException
* if there is an error configuring the XML parser.
* @throws SAXException
* if the XML SAX parser encounters an error.
* @throws IOException
* if there is an error accessing the file.
*/
public int readXmlReports(File file, List<ReportDefinition> reports)
throws ParserConfigurationException,
IOException,
SAXException
{
int type = SRC_UNKNOWN;
// is this a valid file?
if (!file.exists() || !file.isFile())
{
return type;
}
Document dom = XmlHelper.parse(file);
Element root = dom.getDocumentElement();
// read the database mode
String db = root.getAttribute(ATTR_DB);
if (db != null && db.length() > 0)
{
type = Boolean.parseBoolean(db) ? SRC_SCHEMA : SRC_CODE_CACHE;
}
// get the list of reports
NodeList rpts = root.getElementsByTagName(ELEM_RPT);
String trueVal = String.valueOf(Boolean.TRUE);
int rlen = rpts.getLength();
// process each report
for (int i = 0; i < rlen; i++)
{
ReportDefinition rpt = new ReportDefinition();
Element elem = (Element) rpts.item(i);
// mandatory attributes
rpt.title = elem.getAttribute(ATTR_TITLE);
rpt.condition = elem.getAttribute(ATTR_COND);
rpt.setTagList(elem.getAttribute(ATTR_TAG));
// optional attributes
rpt.user = trueVal.equalsIgnoreCase(elem.getAttribute(ATTR_USER));
String dumpType = elem.getAttribute(ATTR_DUMPTYPE);
if (dumpType != null && dumpType.length() > 0)
{
rpt.dumpType = dumpType;
}
String dumpExpr = elem.getAttribute(ATTR_DUMPEXPR);
if (dumpExpr != null && dumpExpr.length() > 0)
{
rpt.dumpExpr = dumpExpr;
}
String dumpLevel = elem.getAttribute(ATTR_DUMPLVL);
if (dumpLevel != null && dumpLevel.length() > 0)
{
rpt.dumpLevel = Integer.parseInt(dumpLevel);
}
String multiplexExpr = elem.getAttribute(ATTR_MULTIPLEX);
if (multiplexExpr != null && multiplexExpr.length() > 0)
{
rpt.multiplexExpr = multiplexExpr;
}
String supportLvlExpr = elem.getAttribute(ATTR_SUPPORT);
if (supportLvlExpr != null && supportLvlExpr.length() > 0)
{
rpt.supportLvlExpr = supportLvlExpr;
}
reports.add(rpt);
}
return type;
}
/**
* Create a new XML document and export the given list of report definitions into it. Save
* the document into this object's file, which will be created if it does not exist. If the
* file already exists, it is overwritten without confirmation.
*
* @param file
* Destination file.
* @param reports
* List of report definitions to write.
* @param schema
* {@code True} if reports are schema reports; {@code false} if they are code reports.
*
* @throws ParserConfigurationException
* if there is an XML parser configuration error.
* @throws IOException
* if there is an error writing the XML content to the file system.
*/
public void writeXmlReports(File file, List<ReportDefinition> reports, boolean schema)
throws ParserConfigurationException,
IOException
{
Document dom = XmlHelper.newDocument();
Element listElem = dom.createElement(ELEM_LIST);
dom.appendChild(listElem);
XmlHelper.setAttribute(listElem, ATTR_DB, String.valueOf(schema));
for (ReportDefinition def : reports)
{
Element rptElem = dom.createElement(ELEM_RPT);
listElem.appendChild(rptElem);
XmlHelper.setAttribute(rptElem, ATTR_TITLE, def.getTitle());
XmlHelper.setAttribute(rptElem, ATTR_COND, def.getCondition());
XmlHelper.setAttribute(rptElem, ATTR_USER, String.valueOf(def.user));
Set<String> tagSet = def.getTags();
if (tagSet != null)
{
StringBuilder buf = new StringBuilder();
int i = 0;
for (String tag : def.getTags())
{
if (i++ > 0)
{
buf.append(",");
}
buf.append(tag);
}
String tags = buf.toString();
XmlHelper.setAttribute(rptElem, ATTR_TAG, tags);
}
String dumpType = def.getDumpType();
if (dumpType != null && dumpType.trim().length() > 0)
{
XmlHelper.setAttribute(rptElem, ATTR_DUMPTYPE, dumpType);
}
String dumpExpr = def.getDumpExpr();
if (dumpExpr != null && dumpExpr.trim().length() > 0)
{
XmlHelper.setAttribute(rptElem, ATTR_DUMPEXPR, dumpExpr);
}
int dumpLevel = def.getDumpLevel();
if (dumpLevel > 0)
{
XmlHelper.setAttribute(rptElem, ATTR_DUMPLVL, String.valueOf(dumpLevel));
}
String mplex = def.getMultiplexExpr();
if (mplex != null && mplex.trim().length() > 0)
{
XmlHelper.setAttribute(rptElem, ATTR_MULTIPLEX, mplex);
}
String support = def.getSupportLvlExpr();
if (support != null && support.trim().length() > 0)
{
XmlHelper.setAttribute(rptElem, ATTR_SUPPORT, support);
}
}
XmlHelper.write(dom, file);
}
/**
* Read the given file, which is expected to contain an XML document defining one or more
* sets of user credentials. Return the list of entries read.
*
* @param file
* File containing XML user credentials.
*
* @return List of user credential entries; may be empty, but will not be {@code null}.
*
* @throws ParserConfigurationException
* if there is an error configuring the XML parser.
* @throws SAXException
* if the XML SAX parser encounters an error.
* @throws IOException
* if there is an error accessing the file.
*/
public List<UserRecord> readXmlUsers(File file)
throws ParserConfigurationException,
IOException,
SAXException
{
// is this a valid file?
if (!file.exists() || !file.isFile())
{
return Collections.emptyList();
}
List<UserRecord> list = new ArrayList<>();
Document dom = XmlHelper.parse(file);
Element root = dom.getDocumentElement();
// get the list of user credentials
NodeList users = root.getElementsByTagName(ELEM_USER);
String trueVal = String.valueOf(Boolean.TRUE);
// process each search element
int ulen = users.getLength();
for (int i = 0; i < ulen; i++)
{
Element userElem = (Element) users.item(i);
String name = userElem.getAttribute(ATTR_NAME);
boolean admin = trueVal.equalsIgnoreCase(userElem.getAttribute(ATTR_ADMIN));
byte[] salt = DatatypeConverter.parseHexBinary(userElem.getAttribute(ATTR_SALT));
byte[] pass = DatatypeConverter.parseHexBinary(userElem.getAttribute(ATTR_PASSWORD));
String passChanged = userElem.getAttribute(ATTR_PASS_CHANGED);
Date date = passChanged != null && passChanged.length() > 0
? Date.valueOf(passChanged)
: null;
UserRecord user = new UserRecord(name, admin, salt, pass, date);
list.add(user);
}
return list;
}
/**
* Create a new XML document and export the given list of user credentials into it. Save
* the document into this object's file, which will be created if it does not exist. If the
* file already exists, it is overwritten without confirmation.
*
* @param file
* Destination file.
* @param users
* List of user credential records to write.
*
* @throws ParserConfigurationException
* if there is an XML parser configuration error.
* @throws IOException
* if there is an error writing the XML content to the file system.
*/
public void writeXmlUsers(File file, List<UserRecord> users)
throws ParserConfigurationException,
IOException
{
Document dom = XmlHelper.newDocument();
Element listElem = dom.createElement(ELEM_LIST);
dom.appendChild(listElem);
for (UserRecord user : users)
{
Element userElem = dom.createElement(ELEM_USER);
listElem.appendChild(userElem);
XmlHelper.setAttribute(userElem, ATTR_NAME, user.getName());
XmlHelper.setAttribute(userElem, ATTR_ADMIN, String.valueOf(user.isAdmin()));
String hexSalt = DatatypeConverter.printHexBinary(user.getSalt());
XmlHelper.setAttribute(userElem, ATTR_SALT, hexSalt);
String hexPass = DatatypeConverter.printHexBinary(user.getPassword());
XmlHelper.setAttribute(userElem, ATTR_PASSWORD, hexPass);
Date date = user.getPassChanged();
if (date != null)
{
XmlHelper.setAttribute(userElem, ATTR_PASS_CHANGED, date.toString());
}
}
XmlHelper.write(dom, file);
}
/**
* Read the given file, which is expected to contain an XML document defining one or more
* file filter profiles. Return the list of entries read.
*
* @param file
* File containing XML filter profile data.
*
* @return List of filter profiles; may be empty, but will not be {@code null}.
*
* @throws ParserConfigurationException
* if there is an error configuring the XML parser.
* @throws SAXException
* if the XML SAX parser encounters an error.
* @throws IOException
* if there is an error accessing the file.
*/
public List<FilterProfile> readXmlFilters(File file)
throws ParserConfigurationException,
IOException,
SAXException
{
// is this a valid file?
if (!file.exists() || !file.isFile())
{
return Collections.emptyList();
}
List<FilterProfile> list = new ArrayList<>();
Document dom = XmlHelper.parse(file);
Element root = dom.getDocumentElement();
// get the list of searches
NodeList searches = root.getElementsByTagName(ELEM_PROFILE);
String trueVal = String.valueOf(Boolean.TRUE);
// process each search element
int slen = searches.getLength();
for (int i = 0; i < slen; i++)
{
Element profileElem = (Element) searches.item(i);
String name = profileElem.getAttribute(ATTR_NAME);
FilterProfile profile = new FilterProfile();
profile.setName(name);
// add filters associated with the profile
NodeList filters = profileElem.getElementsByTagName(ELEM_FILTER);
int ulen = filters.getLength();
for (int j = 0; j < ulen; j++)
{
Element filterElem = (Element) filters.item(j);
String spec = filterElem.getAttribute(ATTR_SPEC);
boolean wild = trueVal.equalsIgnoreCase(filterElem.getAttribute(ATTR_WILD));
FileFilter filter = new FileFilter();
filter.setSpec(spec);
filter.setWild(wild);
profile.addFilter(filter);
}
list.add(profile);
}
return list;
}
/**
* Create a new XML document and export the given list of file filter profiles into it. Save
* the document into this object's file, which will be created if it does not exist. If the
* file already exists, it is overwritten without confirmation.
*
* @param file
* Destination file.
* @param profiles
* List of filter profiles to write.
*
* @throws ParserConfigurationException
* if there is an XML parser configuration error.
* @throws IOException
* if there is an error writing the XML content to the file system.
*/
public void writeXmlFilters(File file, List<FilterProfile> profiles)
throws ParserConfigurationException,
IOException
{
Document dom = XmlHelper.newDocument();
Element listElem = dom.createElement(ELEM_LIST);
dom.appendChild(listElem);
for (FilterProfile profile : profiles)
{
Element profileElem = dom.createElement(ELEM_PROFILE);
listElem.appendChild(profileElem);
XmlHelper.setAttribute(profileElem, ATTR_NAME, profile.getName());
List<FileFilter> filters = profile.getFilters();
for (FileFilter filter : filters)
{
Element filterElem = dom.createElement(ELEM_FILTER);
profileElem.appendChild(filterElem);
XmlHelper.setAttribute(filterElem, ATTR_SPEC, filter.getSpec());
XmlHelper.setAttribute(filterElem, ATTR_WILD, String.valueOf(filter.isWild()));
}
}
XmlHelper.write(dom, file);
}
/**
* Read the given file, which is expected to contain an XML document defining one or more
* search history entries. Return the list of entries read.
*
* @param file
* File containing XML search history.
*
* @return List of search history entries; may be empty, but will not be {@code null}.
*
* @throws ParserConfigurationException
* if there is an error configuring the XML parser.
* @throws SAXException
* if the XML SAX parser encounters an error.
* @throws IOException
* if there is an error accessing the file.
*/
public List<SearchHistory> readXmlSearches(File file)
throws ParserConfigurationException,
IOException,
SAXException
{
// is this a valid file?
if (!file.exists() || !file.isFile())
{
return Collections.emptyList();
}
List<SearchHistory> list = new ArrayList<>();
Document dom = XmlHelper.parse(file);
Element root = dom.getDocumentElement();
// get the list of searches
NodeList searches = root.getElementsByTagName(ELEM_SEARCH);
// process each search element
int slen = searches.getLength();
for (int i = 0; i < slen; i++)
{
Element searchElem = (Element) searches.item(i);
String attr = searchElem.getAttribute(ATTR_ID);
long sid;
try
{
sid = Long.parseLong(attr);
}
catch (NumberFormatException exc)
{
throw new RuntimeException("Invalid search ID attribute: " + attr, exc);
}
String condition = searchElem.getAttribute(ATTR_COND);
SearchHistory hist = new SearchHistory(sid, condition);
// add users of the search
NodeList users = searchElem.getElementsByTagName(ELEM_USER);
int ulen = users.getLength();
for (int j = 0; j < ulen; j++)
{
Element userElem = (Element) users.item(j);
String name = userElem.getAttribute(ATTR_NAME);
hist.addUser(name);
}
list.add(hist);
}
return list;
}
/**
* Create a new XML document and export the given list of search history entries into it. Save
* the document into this object's file, which will be created if it does not exist. If the
* file already exists, it is overwritten without confirmation.
*
* @param file
* Destination file.
* @param searches
* List of search history entries to write.
*
* @throws ParserConfigurationException
* if there is an XML parser configuration error.
* @throws IOException
* if there is an error writing the XML content to the file system.
*/
public void writeXmlSearches(File file, List<SearchHistory> searches)
throws ParserConfigurationException,
IOException
{
Document dom = XmlHelper.newDocument();
Element listElem = dom.createElement(ELEM_LIST);
dom.appendChild(listElem);
for (SearchHistory hist : searches)
{
Element searchElem = dom.createElement(ELEM_SEARCH);
listElem.appendChild(searchElem);
XmlHelper.setAttribute(searchElem, ATTR_ID, String.valueOf(hist.getSearchId()));
XmlHelper.setAttribute(searchElem, ATTR_COND, hist.getCondition());
List<String> users = hist.getUsers();
for (String user : users)
{
Element userElem = dom.createElement(ELEM_USER);
searchElem.appendChild(userElem);
XmlHelper.setAttribute(userElem, ATTR_NAME, user);
}
}
XmlHelper.write(dom, file);
}
}