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.pfixcore.editor2.core.spring.internal;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.IOException;
025: import java.util.Collection;
026: import java.util.HashSet;
027: import java.util.Iterator;
028:
029: import javax.xml.transform.TransformerException;
030:
031: import org.apache.log4j.Logger;
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.Attributes;
037: import org.xml.sax.ContentHandler;
038: import org.xml.sax.InputSource;
039: import org.xml.sax.SAXException;
040: import org.xml.sax.XMLReader;
041: import org.xml.sax.helpers.DefaultHandler;
042: import org.xml.sax.helpers.XMLReaderFactory;
043:
044: import de.schlund.pfixcore.editor2.core.dom.AbstractIncludePartThemeVariant;
045: import de.schlund.pfixcore.editor2.core.dom.IncludePart;
046: import de.schlund.pfixcore.editor2.core.dom.IncludePartThemeVariant;
047: import de.schlund.pfixcore.editor2.core.dom.Page;
048: import de.schlund.pfixcore.editor2.core.dom.Theme;
049: import de.schlund.pfixcore.editor2.core.exception.EditorIOException;
050: import de.schlund.pfixcore.editor2.core.exception.EditorParsingException;
051: import de.schlund.pfixcore.editor2.core.exception.EditorSecurityException;
052: import de.schlund.pfixcore.editor2.core.spring.BackupService;
053: import de.schlund.pfixcore.editor2.core.spring.ConfigurationService;
054: import de.schlund.pfixcore.editor2.core.spring.FileSystemService;
055: import de.schlund.pfixcore.editor2.core.spring.PathResolverService;
056: import de.schlund.pfixcore.lucefix.PfixQueueManager;
057: import de.schlund.pfixcore.lucefix.Tripel;
058: import de.schlund.pfixxml.util.MD5Utils;
059: import de.schlund.pfixxml.util.XPath;
060: import de.schlund.pfixxml.util.Xml;
061:
062: /**
063: * Common implementation of IncludePartThemeVariant
064: *
065: * @author Sebastian Marsching <sebastian.marsching@1und1.de>
066: */
067: public abstract class CommonIncludePartThemeVariantImpl extends
068: AbstractIncludePartThemeVariant {
069: private final static String XML_THEME_TAG_NAME = "theme";
070:
071: private Theme theme;
072:
073: private IncludePart part;
074:
075: private FileSystemService filesystem;
076:
077: private PathResolverService pathresolver;
078:
079: private ConfigurationService configuration;
080:
081: private BackupService backup;
082:
083: public CommonIncludePartThemeVariantImpl(
084: FileSystemService filesystem,
085: PathResolverService pathresolver,
086: ConfigurationService configuration, BackupService backup,
087: Theme theme, IncludePart part) {
088: this .filesystem = filesystem;
089: this .pathresolver = pathresolver;
090: this .configuration = configuration;
091: this .backup = backup;
092: this .theme = theme;
093: this .part = part;
094: }
095:
096: /**
097: * Override to do security check in implementation
098: */
099: protected abstract void securityCheckCreateIncludePartThemeVariant()
100: throws EditorSecurityException;
101:
102: /**
103: * Override to do security check in implementation
104: */
105: protected abstract void securityCheckEditIncludePartThemeVariant()
106: throws EditorSecurityException;
107:
108: /**
109: * Override to implement ChangeLog entries
110: */
111: protected abstract void writeChangeLog();
112:
113: /*
114: * (non-Javadoc)
115: *
116: * @see de.schlund.pfixcore.editor2.core.dom.IncludePartThemeVariant#getTheme()
117: */
118: public Theme getTheme() {
119: return this .theme;
120: }
121:
122: /*
123: * (non-Javadoc)
124: *
125: * @see de.schlund.pfixcore.editor2.core.dom.IncludePartThemeVariant#getIncludePart()
126: */
127: public IncludePart getIncludePart() {
128: return this .part;
129: }
130:
131: /*
132: * (non-Javadoc)
133: *
134: * @see de.schlund.pfixcore.editor2.core.dom.IncludePartThemeVariant#getXML()
135: */
136: public Node getXML() {
137: Node parentXml = this .getIncludePart().getContentXML();
138: if (parentXml == null) {
139: return null;
140: }
141: try {
142: return XPath.selectNode(parentXml, XML_THEME_TAG_NAME
143: + "[@name='" + this .getTheme().getName() + "']");
144: } catch (TransformerException e) {
145: // Should NEVER happen
146: // So if it does, assume there is no node
147: Logger.getLogger(this .getClass()).error("XPath error!", e);
148: return null;
149: }
150: }
151:
152: public void setXML(Node xml) throws EditorIOException,
153: EditorParsingException, EditorSecurityException {
154: setXML(xml, true);
155: }
156:
157: /*
158: * (non-Javadoc)
159: *
160: * @see de.schlund.pfixcore.editor2.core.dom.IncludePartThemeVariant#setXML(org.w3c.dom.Node)
161: */
162: public void setXML(Node xml, boolean indent)
163: throws EditorIOException, EditorParsingException,
164: EditorSecurityException {
165: File xmlFile = new File(this .pathresolver.resolve(this
166: .getIncludePart().getIncludeFile().getPath()));
167: Object lock = this .filesystem.getLock(xmlFile);
168: synchronized (lock) {
169: Document doc;
170: Element root;
171: Element part;
172: Element theme;
173: if (this .getIncludePart().getIncludeFile().getContentXML() == null) {
174: // File does not yet exist and has to be created
175: // Honor default namespace-prefixes during creation!
176:
177: // Do security check
178: this .securityCheckCreateIncludePartThemeVariant();
179: if (!xmlFile.exists()) {
180: try {
181: xmlFile.createNewFile();
182: } catch (IOException e) {
183: String err = "Could not create file "
184: + xmlFile.getAbsolutePath() + "!";
185: Logger.getLogger(this .getClass()).error(err, e);
186: throw new EditorIOException(err, e);
187: }
188: }
189: doc = Xml.createDocument();
190: root = doc.createElement("include_parts");
191: doc.appendChild(root);
192:
193: part = doc.createElement("part");
194: // Keep proper indention level
195: Node temp = doc.createTextNode("\n ");
196: root.appendChild(temp);
197: root.appendChild(part);
198: temp = doc.createTextNode("\n");
199: root.appendChild(temp);
200: part.setAttribute("name", this .getIncludePart()
201: .getName());
202: // Keep proper indention level
203: temp = doc.createTextNode("\n ");
204: part.appendChild(temp);
205: theme = doc.createElement(XML_THEME_TAG_NAME);
206: part.appendChild(theme);
207: temp = doc.createTextNode("\n ");
208: part.appendChild(temp);
209: theme.setAttribute("name", this .getTheme().getName());
210: } else {
211: // Do security check
212: this .securityCheckEditIncludePartThemeVariant();
213:
214: try {
215: doc = this .filesystem
216: .readXMLDocumentFromFile(xmlFile);
217: } catch (FileNotFoundException e) {
218: String err = "File "
219: + xmlFile.getAbsolutePath()
220: + " could not be found although Pustefix core could obviously read it!";
221: Logger.getLogger(this .getClass()).error(err, e);
222: throw new EditorIOException(err, e);
223: } catch (SAXException e) {
224: String err = "Error during parsing file "
225: + xmlFile.getAbsolutePath() + "!";
226: Logger.getLogger(this .getClass()).error(err, e);
227: throw new EditorParsingException(err, e);
228: } catch (IOException e) {
229: String err = "File "
230: + xmlFile.getAbsolutePath()
231: + " could not be read although Pustefix core could obviously read it!";
232: Logger.getLogger(this .getClass()).error(err, e);
233: throw new EditorIOException(err, e);
234: }
235: root = doc.getDocumentElement();
236: try {
237: part = (Element) XPath.selectNode(root,
238: "part[@name='"
239: + this .getIncludePart().getName()
240: + "']");
241: } catch (TransformerException e) {
242: // Should never happen as a DOM document is always
243: // well-formed!
244: String err = "XPath error!";
245: Logger.getLogger(this .getClass()).error(err, e);
246: throw new RuntimeException(err, e);
247: }
248: if (part == null) {
249: // Part is not yet existing, so create it
250: part = doc.createElement("part");
251: part.setAttribute("name", this .getIncludePart()
252: .getName());
253: // Keep indention
254: Node temp = doc.createTextNode(" ");
255: root.appendChild(temp);
256: root.appendChild(part);
257: temp = doc.createTextNode("\n");
258: root.appendChild(temp);
259: temp = doc.createTextNode("\n ");
260: part.appendChild(temp);
261: }
262: try {
263: theme = (Element) XPath.selectNode(part,
264: XML_THEME_TAG_NAME + "[@name='"
265: + this .getTheme().getName() + "']");
266: } catch (TransformerException e) {
267: // Should never happen as a DOM document is always
268: // well-formed!
269: String err = "XPath error!";
270: Logger.getLogger(this .getClass()).error(err, e);
271: throw new RuntimeException(err, e);
272: }
273: if (theme == null) {
274: // No branch for this theme - create it
275: theme = doc.createElement(XML_THEME_TAG_NAME);
276: theme.setAttribute("name", this .getTheme()
277: .getName());
278: // Keep indention
279: Node temp = doc.createTextNode(" ");
280: part.appendChild(temp);
281: part.appendChild(theme);
282: temp = doc.createTextNode("\n ");
283: part.appendChild(temp);
284: } else {
285: // Branch is already existing, so make a backup
286: this .backup.backupInclude(this );
287:
288: // Replace node
289: Node oldTheme = theme;
290: Document parentdoc = oldTheme.getOwnerDocument();
291: theme = parentdoc.createElement(XML_THEME_TAG_NAME);
292: theme.setAttribute("name", this .getTheme()
293: .getName());
294: Node parent = oldTheme.getParentNode();
295: // Insert at right location to keep indention
296: Node temp = oldTheme.getNextSibling();
297: parent.removeChild(oldTheme);
298: parent.insertBefore(theme, temp);
299: }
300: }
301:
302: // Create namespace declarations in root node if needed
303: Collection<String> partPrefixes = extractPrefixesFromTree(xml);
304: Collection<String> declaredPrefixes = extractPrefixDeclarationsFromFileRoot(xmlFile);
305: Collection<String> knownPrefixes = this .configuration
306: .getPrefixToNamespaceMappings().keySet();
307: for (String prefix : partPrefixes) {
308: if (knownPrefixes.contains(prefix)
309: && !declaredPrefixes.contains(prefix)) {
310: String url = this .configuration
311: .getPrefixToNamespaceMappings().get(prefix);
312: root.setAttributeNS(
313: "http://www.w3.org/2000/xmlns/", "xmlns:"
314: + prefix, url);
315: }
316: }
317:
318: // Copy over all nodes except attributes to the theme node
319: // Keep indention
320: Node temp;
321: if (indent) {
322: temp = doc.createTextNode("\n");
323: theme.appendChild(temp);
324: }
325: NodeList nlist = xml.getChildNodes();
326: for (int i = 0; i < nlist.getLength(); i++) {
327: Node child = nlist.item(i);
328: if (child.getNodeType() != Node.ATTRIBUTE_NODE) {
329: theme.appendChild(doc.importNode(child, true));
330: }
331: }
332: if (indent) {
333: temp = doc.createTextNode("\n ");
334: theme.appendChild(temp);
335: }
336:
337: // Log change
338: this .writeChangeLog();
339:
340: // Save file
341: try {
342: this .filesystem.storeXMLDocumentToFile(xmlFile, doc);
343: } catch (IOException e) {
344: String err = "File " + xmlFile.getAbsolutePath()
345: + " could not written!";
346: Logger.getLogger(this .getClass()).error(err, e);
347: throw new EditorIOException(err, e);
348: }
349:
350: // Force refresh of file cache
351: this .getIncludePart().getIncludeFile().getContentXML(true);
352:
353: // Register affected pages for regeneration
354: Page affectedpage = null;
355: for (Iterator<Page> i = this .getAffectedPages().iterator(); i
356: .hasNext();) {
357: Page page = i.next();
358: page.registerForUpdate();
359: affectedpage = page;
360: }
361:
362: // Regenerate exactly ONE affected page synchronously
363: // to make sure changes in the dependency tree are
364: // visible at once
365: if (affectedpage != null) {
366: affectedpage.update();
367: } else {
368: // This theme branch does not have any affected pages,
369: // probably because it was just created - so look for
370: // pages using other branches, which will use this branch
371: // when being regenerated
372: pageloop: for (Iterator<IncludePartThemeVariant> i = this
373: .getIncludePart().getThemeVariants().iterator(); i
374: .hasNext();) {
375: IncludePartThemeVariant iv = i.next();
376: for (Iterator<Page> i2 = iv.getAffectedPages()
377: .iterator(); i2.hasNext();) {
378: Page p = i2.next();
379: if (p.getThemes().themeOverridesTheme(
380: this .getTheme(), iv.getTheme())) {
381: p.update();
382: break pageloop;
383: }
384: }
385: }
386: }
387:
388: PfixQueueManager.getInstance(null).queue(
389: new Tripel(getTheme().getName(), getIncludePart()
390: .getName(), getIncludePart()
391: .getIncludeFile().getPath(),
392: Tripel.Type.EDITORUPDATE));
393:
394: }
395: }
396:
397: private Collection<String> extractPrefixDeclarationsFromFileRoot(
398: File xmlFile) {
399: XMLReader xreader;
400: try {
401: xreader = XMLReaderFactory.createXMLReader();
402: } catch (SAXException e) {
403: throw new RuntimeException("Could not create XMLReader", e);
404: }
405:
406: final Collection<String> prefixes = new HashSet<String>();
407: ContentHandler nsPrefixHandler = new DefaultHandler() {
408: private boolean foundFirstElement = false;
409:
410: public void startElement(String uri, String localName,
411: String qName, Attributes attributes)
412: throws SAXException {
413: if (foundFirstElement == false) {
414: foundFirstElement = true;
415: }
416: }
417:
418: public void startPrefixMapping(String prefix, String uri)
419: throws SAXException {
420: if (!foundFirstElement) {
421: prefixes.add(prefix);
422: }
423: }
424: };
425:
426: xreader.setContentHandler(nsPrefixHandler);
427: try {
428: xreader
429: .parse(new InputSource(new FileInputStream(xmlFile)));
430: } catch (FileNotFoundException e) {
431: // File might not yet be valid - ignore
432: } catch (IOException e) {
433: // File might not yet be valid - ignore
434: } catch (SAXException e) {
435: // File might not yet be valid - ignore
436: }
437:
438: return prefixes;
439: }
440:
441: private Collection<String> extractPrefixesFromTree(Node xml) {
442: Collection<String> prefixes = new HashSet<String>();
443: String prefix = xml.getPrefix();
444: if (prefix != null) {
445: prefixes.add(prefix);
446: }
447:
448: NodeList childs = xml.getChildNodes();
449: for (int i = 0; i < childs.getLength(); i++) {
450: Node node = childs.item(i);
451: prefixes.addAll(extractPrefixesFromTree(node));
452: }
453:
454: return prefixes;
455: }
456:
457: public String getMD5() {
458: Node xml = this .getXML();
459: if (xml == null) {
460: return "0";
461: }
462: String md5 = MD5Utils.hex_md5(xml);
463: return md5;
464: }
465: }
|