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;
020:
021: import java.text.MessageFormat;
022: import java.util.List;
023:
024: import javax.xml.transform.TransformerException;
025:
026: import org.apache.log4j.Logger;
027: import org.w3c.dom.Document;
028: import org.w3c.dom.Element;
029: import org.w3c.dom.Node;
030: import org.xml.sax.SAXException;
031:
032: import de.schlund.pfixxml.config.GlobalConfig;
033: import de.schlund.pfixxml.resources.DocrootResource;
034: import de.schlund.pfixxml.resources.FileResource;
035: import de.schlund.pfixxml.resources.ResourceUtil;
036: import de.schlund.pfixxml.targets.TargetGenerator;
037: import de.schlund.pfixxml.targets.TargetGeneratorFactory;
038: import de.schlund.pfixxml.targets.VirtualTarget;
039: import de.schlund.pfixxml.util.XPath;
040: import de.schlund.pfixxml.util.Xml;
041: import de.schlund.pfixxml.util.XsltContext;
042:
043: /**
044: * IncludeDocumentExtension.java
045: *
046: * @author <a href="mailto:jtl@schlund.de">Jens Lautenbacher </a>
047: * @author <a href="mailto:haecker@schlund.de">Joerg Haecker </a>
048: *
049: * This class is responsible to return the requested parts of an
050: * {@link IncludeDocument}. It provides a static method which is called from
051: * XSL via the extension mechanism (and from nowhere else!).
052: */
053: public final class IncludeDocumentExtension {
054: //~ Instance/static variables
055: // ..................................................................
056: private static final Logger LOG = Logger
057: .getLogger(IncludeDocumentExtension.class);
058: private static final String NOTARGET = "__NONE__";
059: private static final String XPPARTNAME = "/include_parts/part[@name='";
060: private static final String XTHEMENAME = "/theme[@name = '";
061: private static final String XPNAMEEND = "']";
062:
063: //~ Methods
064: // ....................................................................................
065: /**
066: * Get the requested IncludeDocument from {@link IncludeDocumentFactory}
067: * and retrieve desired information from it.</br> Note: The nested
068: * document in the Includedocument is immutable, any attempts to modify it
069: * will cause an exception.
070: *
071: * @param path the path to the Includedocument in the file system relative
072: * to docroot.
073: * @param part the part in the Includedocument.
074: * @param docroot the document root in the file system
075: * @param targetgen
076: * @param targetkey
077: * @param parent_path
078: * @param parent_part
079: * @param parent_theme
080: * @return a list of nodes understood by the current transformer(currently saxon)
081: * @throws Exception on all errors
082: */
083: public static final Object get(XsltContext context,
084: String path_str, String part, String targetgen,
085: String targetkey, String parent_part_in,
086: String parent_theme_in, String computed_inc)
087: throws Exception {
088:
089: String parent_path_str = "";
090: String parent_part = "";
091: String parent_theme = "";
092: FileResource tgen_path = ResourceUtil
093: .getFileResource(targetgen);
094:
095: if (computed_inc.equals("false") && isIncludeDocument(context)) {
096: parent_path_str = makeSystemIdRelative(context);
097: parent_part = parent_part_in;
098: parent_theme = parent_theme_in;
099: }
100:
101: if (path_str == null || path_str.equals("")) {
102: throw new XMLException(
103: "Need href attribute for pfx:include or path of parent part must be deducible");
104: }
105:
106: // EEEEK! this code is in need of some serious beautifying....
107:
108: try {
109: DocrootResource path = ResourceUtil
110: .getFileResourceFromDocroot(path_str);
111: DocrootResource parent_path = "".equals(parent_path_str) ? null
112: : ResourceUtil
113: .getFileResourceFromDocroot(parent_path_str);
114: boolean dolog = !targetkey.equals(NOTARGET);
115: int length = 0;
116: IncludeDocument iDoc = null;
117: Document doc;
118:
119: TargetGenerator tgen = TargetGeneratorFactory.getInstance()
120: .createGenerator(tgen_path);
121: VirtualTarget target = (VirtualTarget) tgen
122: .getTarget(targetkey);
123:
124: String[] themes = tgen.getGlobalThemes().getThemesArr();
125: if (!targetkey.equals(NOTARGET)) {
126: themes = target.getThemes().getThemesArr();
127: }
128: if (themes == null) {
129: XMLException ex = new XMLException(
130: "Target has a 'null' themes array!");
131: target.setStoredException(ex);
132: throw ex;
133: }
134: if (themes.length < 1) {
135: XMLException ex = new XMLException(
136: "Target has an empty themes array!");
137: target.setStoredException(ex);
138: throw ex;
139: }
140:
141: String DEF_THEME = tgen.getDefaultTheme();
142:
143: if (!path.exists()) {
144: if (dolog) {
145: DependencyTracker.logTyped("text", path, part,
146: DEF_THEME, parent_path, parent_part,
147: parent_theme, target);
148: }
149: return errorNode(context, DEF_THEME);
150: //return new EmptyNodeSet();
151: }
152: // get the includedocument
153: try {
154: iDoc = IncludeDocumentFactory.getInstance()
155: .getIncludeDocument(context.getXsltVersion(),
156: path, false);
157: } catch (SAXException saxex) {
158: if (dolog)
159: DependencyTracker.logTyped("text", path, part,
160: DEF_THEME, parent_path, parent_part,
161: parent_theme, target);
162: target.setStoredException(saxex);
163: throw saxex;
164: }
165: doc = iDoc.getDocument();
166: // Get the part
167: List<Node> ns;
168: try {
169: ns = XPath.select(doc, XPPARTNAME + part + XPNAMEEND);
170: } catch (TransformerException e) {
171: if (dolog)
172: DependencyTracker.logTyped("text", path, part,
173: DEF_THEME, parent_path, parent_part,
174: parent_theme, target);
175: throw e;
176: }
177: length = ns.size();
178: if (length == 0) {
179: // part not found
180: LOG
181: .debug("*** Part '" + part
182: + "' is 0 times defined.");
183: if (dolog) {
184: DependencyTracker.logTyped("text", path, part,
185: DEF_THEME, parent_path, parent_part,
186: parent_theme, target);
187: }
188: return errorNode(context, DEF_THEME);
189: //return new EmptyNodeSet();
190: } else if (length > 1) {
191: // too many parts. Error!
192: if (dolog) {
193: DependencyTracker.logTyped("text", path, part,
194: DEF_THEME, parent_path, parent_part,
195: parent_theme, target);
196: }
197: XMLException ex = new XMLException(
198: "*** Part '"
199: + part
200: + "' is multiple times defined! Must be exactly 1");
201: target.setStoredException(ex);
202: throw ex;
203: }
204:
205: // OK, we have found the part. Find the specfic theme branch matching the theme fallback list.
206: LOG.debug(" => Found part '" + part + "'");
207:
208: for (int i = 0; i < themes.length; i++) {
209:
210: String curr_theme = themes[i];
211: LOG
212: .debug(" => Trying to find theme branch for theme '"
213: + curr_theme + "'");
214:
215: try {
216: ns = XPath.select(doc, XPPARTNAME + part
217: + XPNAMEEND + XTHEMENAME + curr_theme
218: + XPNAMEEND);
219: } catch (TransformerException e) {
220: if (dolog)
221: DependencyTracker.logTyped("text", path, part,
222: DEF_THEME, parent_path, parent_part,
223: parent_theme, target);
224: throw e;
225: }
226: length = ns.size();
227: if (length == 0) {
228: // Didn't find a theme part matching curr_theme, trying next in fallback line
229: if (i < (themes.length - 1)) {
230: LOG.debug(" Part '" + part
231: + "' has no theme branch matching '"
232: + curr_theme + "', trying next theme");
233: } else {
234: LOG.warn(" Part '" + part
235: + "' has no theme branch matching '"
236: + curr_theme
237: + "', no more theme to try!");
238: }
239: continue;
240: } else if (length == 1) {
241: LOG.debug(" Found theme branch '"
242: + curr_theme + "' => STOP");
243: // specific theme found
244: boolean ok = true;
245: if (dolog) {
246: try {
247: DependencyTracker.logTyped("text", path,
248: part, curr_theme, parent_path,
249: parent_part, parent_theme, target);
250: } catch (Exception e) {
251: // TODO
252: ok = false;
253: }
254: }
255: return ok ? (Object) ns.get(0) : errorNode(context,
256: curr_theme);
257: //return ok? (Object) ns.get(0) : new EmptyNodeSet();
258: } else {
259: // too many specific themes found. Error!
260: if (dolog) {
261: DependencyTracker.logTyped("text", path, part,
262: DEF_THEME, parent_path, parent_part,
263: parent_theme, target);
264: }
265: XMLException ex = new XMLException(
266: "*** Theme branch '"
267: + curr_theme
268: + "' is defined multiple times under part '"
269: + part + "@" + path + "'");
270: target.setStoredException(ex);
271: throw ex;
272: }
273: }
274:
275: // We are only here if none of the themes produced a match:
276: @SuppressWarnings("unused")
277: boolean ok = true;
278: if (dolog) {
279: try {
280: DependencyTracker.logTyped("text", path, part,
281: DEF_THEME, parent_path, parent_part,
282: parent_theme, target);
283: } catch (Exception e) { // TODO
284: ok = false;
285: }
286: }
287: return errorNode(context, DEF_THEME);
288: //return new EmptyNodeSet();
289:
290: } catch (Exception e) {
291: Object[] args = { path_str, part, targetgen, targetkey,
292: parent_path_str, parent_part, parent_theme };
293: String sb = MessageFormat
294: .format(
295: "path={0}|part={1}|targetgen={2}|targetkey={3}|"
296: + "parent_path={4}|parent_part={5}|parent_theme={6}",
297: args);
298: LOG
299: .error("Caught exception in extension function! Params:\n"
300: + sb + "\n Stacktrace follows.");
301: throw e;
302: }
303: }
304:
305: private static final Node errorNode(XsltContext context,
306: String prodname) {
307: Document retdoc = Xml.createDocumentBuilder().newDocument();
308: Element retelem = retdoc.createElement("missing");
309: retelem.setAttribute("name", prodname);
310: retdoc.appendChild(retelem);
311: retdoc = Xml.parse(context.getXsltVersion(), retdoc);
312: return retdoc.getDocumentElement();
313: }
314:
315: public static final String makeSystemIdRelative(XsltContext context) {
316: return makeSystemIdRelative(context, "dummy");
317: }
318:
319: public static final String makeSystemIdRelative(
320: XsltContext context, String dummy) {
321: String sysid = context.getSystemId();
322:
323: if (sysid.startsWith("pfixroot://")) {
324: sysid = sysid.substring(("pfixroot://").length());
325: }
326: String docroot_url = GlobalConfig.getDocrootAsURL().toString()
327: + "/";
328: if (sysid.startsWith(docroot_url)) {
329: sysid = sysid.substring(docroot_url.length());
330: }
331: if (sysid.startsWith("/")) {
332: sysid = sysid.substring(1);
333: }
334: return sysid;
335: }
336:
337: public static boolean isIncludeDocument(XsltContext context) {
338: return context.getDocumentElementName().equals("include_parts");
339: }
340: }// end of class IncludeDocumentExtension
|