001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package de.schlund.pfixxml.config.includes;
020:
021: import java.io.IOException;
022: import java.util.ArrayList;
023: import java.util.HashSet;
024: import java.util.List;
025: import java.util.Set;
026:
027: import javax.xml.transform.TransformerException;
028: import javax.xml.xpath.XPathConstants;
029: import javax.xml.xpath.XPathExpressionException;
030: import javax.xml.xpath.XPathFactory;
031:
032: import org.w3c.dom.Document;
033: import org.w3c.dom.Element;
034: import org.w3c.dom.Node;
035: import org.w3c.dom.NodeList;
036: import org.xml.sax.SAXException;
037:
038: import de.schlund.pfixxml.resources.FileResource;
039: import de.schlund.pfixxml.resources.ResourceUtil;
040: import de.schlund.pfixxml.util.Generics;
041: import de.schlund.pfixxml.util.XPath;
042: import de.schlund.pfixxml.util.Xml;
043:
044: public class IncludesResolver {
045: private List<FileIncludeEventListener> listeners = new ArrayList<FileIncludeEventListener>();
046:
047: private String namespace;
048:
049: private String includeTag;
050:
051: private SimpleNamespaceContext nsContext;
052:
053: private final static String TAGNAME = "config-include";
054:
055: private final static String CONFIG_FRAGMENTS_NS = "http://pustefix.sourceforge.net/configfragments200609";
056:
057: private final static String CONFIG_FRAGMENTS_ROOT_TAG = "config-fragments";
058:
059: private final static String CONTEXTXML_NS = "http://pustefix.sourceforge.net/properties200401";
060:
061: private ThreadLocal<Set<Tupel<String, String>>> includesList = new ThreadLocal<Set<Tupel<String, String>>>();
062:
063: public IncludesResolver(String namespace) {
064: this (namespace, TAGNAME);
065: }
066:
067: public IncludesResolver(String namespace, String includeTag) {
068: this .namespace = namespace;
069: this .includeTag = includeTag;
070: this .nsContext = new SimpleNamespaceContext();
071: this .nsContext.addNamespace("fr", CONFIG_FRAGMENTS_NS);
072: this .nsContext.addNamespace("pr", CONTEXTXML_NS);
073: }
074:
075: public void registerListener(FileIncludeEventListener listener) {
076: listeners.add(listener);
077: }
078:
079: public void resolveIncludes(Document doc) throws SAXException {
080: List<Element> nodes;
081: try {
082: nodes = Generics.convertList(XPath.select(doc,
083: "//*[local-name()='" + includeTag + "']"));
084: } catch (TransformerException e) {
085: throw new RuntimeException("Unexpected XPath error!");
086: }
087: for (Element elem : nodes) {
088: if ((namespace == null && elem.getNamespaceURI() != null)
089: || (namespace != null && !namespace.equals(elem
090: .getNamespaceURI()))) {
091: continue;
092: }
093:
094: String xpath = null, refid = null, section = null;
095: if (elem.hasAttribute("xpath")) {
096: xpath = elem.getAttribute("xpath");
097: }
098: if (elem.hasAttribute("refid")) {
099: refid = elem.getAttribute("refid");
100: }
101: if (elem.hasAttribute("section")) {
102: section = elem.getAttribute("section");
103: }
104:
105: if (xpath != null) {
106: if (refid != null || section != null) {
107: throw new SAXException(
108: "Only one of the \"xpath\", \"refid\" or \"section\" attributes may be supplied to the include tag!");
109: }
110: // Just use the supplied XPath expression
111: } else if (refid != null) {
112: if (section != null || xpath != null) {
113: throw new SAXException(
114: "Only one of the \"xpath\", \"refid\" or \"section\" attributes may be supplied to the include tag!");
115: }
116: // xpath = "/*[local-name()='config-fragments']/*[@id='" + refid + "']/node()";
117: xpath = "/fr:config-fragments/*[@id='" + refid
118: + "']/node()";
119: } else if (section != null) {
120: if (xpath != null || refid != null) {
121: throw new SAXException(
122: "Only one of the \"xpath\", \"refid\" or \"section\" attributes may be supplied to the include tag!");
123: }
124: if (!checkSectionType(section)) {
125: throw new SAXException("\"" + section
126: + "\" is not a valid include section type!");
127: }
128: // xpath = "/*[local-name()='config-fragments']/*[local-name()='" + section + "']/node()";
129: xpath = "/fr:config-fragments/fr:" + section
130: + "/node()";
131: } else {
132: throw new SAXException(
133: "One of the \"xpath\", \"refid\" or \"section\" attributes must be set for the include tag!");
134: }
135:
136: String filepath = elem.getAttribute("file");
137: if (filepath == null) {
138: throw new SAXException(
139: "The attribute \"file\" must be set for the include tag!");
140: }
141:
142: // Look if the same include has been performed ealier in the recursion
143: // If yes, we have a cyclic dependency
144: Set<Tupel<String, String>> list = includesList.get();
145: if (list == null) {
146: list = new HashSet<Tupel<String, String>>();
147: includesList.set(list);
148: }
149: if (list.contains(filepath + "#" + xpath)) {
150: throw new SAXException(
151: "Cyclic dependency in include detected: "
152: + filepath.toString());
153: }
154:
155: FileResource includeFile = ResourceUtil
156: .getFileResourceFromDocroot(filepath);
157: Document includeDocument;
158: try {
159: includeDocument = Xml.parseMutable(includeFile);
160: } catch (IOException e) {
161: throw new SAXException(
162: "I/O exception on included file "
163: + includeFile.toString());
164: }
165:
166: if (!CONFIG_FRAGMENTS_NS.equals(includeDocument
167: .getDocumentElement().getNamespaceURI())
168: || !CONFIG_FRAGMENTS_ROOT_TAG
169: .equals(includeDocument
170: .getDocumentElement()
171: .getLocalName())) {
172: throw new SAXException(
173: "File "
174: + filepath
175: + " seems not to be a valid configuration fragments file!");
176: }
177:
178: list.add(new Tupel<String, String>(filepath, xpath));
179: try {
180: resolveIncludes(includeDocument);
181: } finally {
182: list.remove(new Tupel<String, String>(filepath, xpath));
183: }
184:
185: NodeList includeNodes;
186: try {
187: javax.xml.xpath.XPath xpathProc = XPathFactory
188: .newInstance().newXPath();
189: xpathProc.setNamespaceContext(this .nsContext);
190: includeNodes = (NodeList) xpathProc.evaluate(xpath,
191: includeDocument, XPathConstants.NODESET);
192: } catch (XPathExpressionException e) {
193: throw new SAXException("XPath Expression invalid: "
194: + xpath);
195: }
196:
197: for (int i = 0; i < includeNodes.getLength(); i++) {
198: Node node = includeNodes.item(i);
199: Node newNode = doc.importNode(node, true);
200: elem.getParentNode().insertBefore(newNode, elem);
201: }
202: elem.getParentNode().removeChild(elem);
203:
204: // Trigger event
205: FileIncludeEvent ev = new FileIncludeEvent(this ,
206: includeFile);
207: for (FileIncludeEventListener listener : listeners) {
208: listener.fileIncluded(ev);
209: }
210: }
211: }
212:
213: private boolean checkSectionType(String section) {
214: if (section.equals("targets") || section.equals("navigation")
215: || section.equals("pageflows")
216: || section.equals("pagerequests")
217: || section.equals("properties")
218: || section.equals("interceptors")
219: || section.equals("scriptedflows")
220: || section.equals("roles")
221: || section.equals("authconstraints")
222: || section.equals("resources")) {
223: return true;
224: } else {
225: return false;
226: }
227: }
228:
229: private class Tupel<A, B> {
230: private A obj1;
231: private B obj2;
232:
233: public Tupel(A v1, B v2) {
234: obj1 = v1;
235: obj2 = v2;
236: }
237:
238: public boolean equals(Object obj) {
239: if (obj == null) {
240: return false;
241: }
242:
243: if (!(obj instanceof Tupel)) {
244: return false;
245: }
246: Tupel<?, ?> tupel = (Tupel<?, ?>) obj;
247:
248: return obj1.equals(tupel.obj1) && obj2.equals(tupel.obj2);
249: }
250:
251: public int hashCode() {
252: return obj1.hashCode() + obj2.hashCode();
253: }
254: }
255: }
|