/******************************************************************************* * Copyright (c) 2010, 2016 Nicolas Roduit. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation ******************************************************************************/ package org.weasis.core.api.service; import java.io.IOException; import java.io.OutputStream; import java.util.Collection; import java.util.Iterator; import java.util.Map; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import org.apache.felix.prefs.BackingStore; import org.apache.felix.prefs.PreferencesDescription; import org.apache.felix.prefs.PreferencesImpl; import org.osgi.framework.BundleContext; import org.osgi.service.prefs.BackingStoreException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.util.EscapeChars; import org.weasis.core.api.util.FileUtil; /** * This is an abstract implementation of a backing store which uses streams to read/write the preferences and stores a * complete preferences tree in a single stream. */ public abstract class StreamBackingStoreImpl implements BackingStore { private static final Logger LOGGER = LoggerFactory.getLogger(StreamBackingStoreImpl.class); /** The bundle context. */ protected final BundleContext bundleContext; public StreamBackingStoreImpl(BundleContext context) { this.bundleContext = context; } /** * This method is invoked to check if the backing store is accessible right now. * * @throws BackingStoreException */ protected abstract void checkAccess() throws BackingStoreException; /** * Get the output stream to write the preferences. */ protected abstract OutputStream getOutputStream(PreferencesDescription desc) throws IOException; /** * @see org.apache.felix.prefs.BackingStore#store(org.apache.felix.prefs.PreferencesImpl) */ @Override public void store(PreferencesImpl prefs) throws BackingStoreException { // do we need to store at all? if (!this.hasChanges(prefs)) { return; } this.checkAccess(); // load existing data PreferencesImpl savedData = null; try { savedData = this.load(prefs.getBackingStoreManager(), prefs.getDescription()); } catch (BackingStoreException e1) { // if the file is empty or corrupted LOGGER.error("Cannot store preferences", e1); //$NON-NLS-1$ } final PreferencesImpl rootPrefs; if (savedData == null) { rootPrefs = prefs.getRoot(); } else { // merge with saved version final PreferencesImpl n = savedData.getOrCreateNode(prefs.absolutePath()); n.applyChanges(prefs); rootPrefs = n.getRoot(); } XMLStreamWriter writer = null; try { final OutputStream os = this.getOutputStream(rootPrefs.getDescription()); XMLOutputFactory factory = XMLOutputFactory.newInstance(); writer = factory.createXMLStreamWriter(os, "UTF-8"); //$NON-NLS-1$ writer.writeStartDocument("UTF-8", "1.0"); //$NON-NLS-1$ //$NON-NLS-2$ writer.writeStartElement("preferences"); //$NON-NLS-1$ this.write(rootPrefs, writer); writer.writeEndElement(); writer.writeEndDocument(); } catch (IOException ioe) { throw new BackingStoreException("Unable to store preferences.", ioe); //$NON-NLS-1$ } catch (XMLStreamException e) { throw new BackingStoreException("Unable to store preferences.", e); //$NON-NLS-1$ } finally { FileUtil.safeClose(writer); } } /** * Has the tree changes. * * @param prefs the prefs * @return true, if successful */ protected boolean hasChanges(PreferencesImpl prefs) { if (prefs.getChangeSet().hasChanges()) { return true; } final Iterator<?> i = prefs.getChildren().iterator(); while (i.hasNext()) { final PreferencesImpl current = (PreferencesImpl) i.next(); if (this.hasChanges(current)) { return true; } } return false; } /** * @see org.apache.felix.prefs.BackingStore#update(org.apache.felix.prefs.PreferencesImpl) */ @Override public void update(PreferencesImpl prefs) throws BackingStoreException { // Do nothing, only update when writing } /** * Write the preferences recursively to the output stream. * * @param prefs * @param os * @throws IOException * @throws XMLStreamException */ protected void write(PreferencesImpl prefs, XMLStreamWriter writer) throws XMLStreamException { final int size = prefs.getProperties().size(); if (size > 0) { this.writePreferences(prefs, writer); } final Collection<?> children = prefs.getChildren(); final Iterator<?> i = children.iterator(); while (i.hasNext()) { final PreferencesImpl child = (PreferencesImpl) i.next(); writer.writeStartElement(child.name()); this.write(child, writer); writer.writeEndElement(); } writer.flush(); } protected void read(PreferencesImpl prefs, XMLStreamReader xmler, String startKey) throws XMLStreamException { int eventType; while (xmler.hasNext()) { eventType = xmler.next(); switch (eventType) { // It is a properties of the node case XMLStreamConstants.CHARACTERS: prefs.getProperties().put(startKey, xmler.getText()); break; // It is a child of the node case XMLStreamConstants.START_ELEMENT: PreferencesImpl impl = prefs.getOrCreateNode(startKey); this.read(impl, xmler, xmler.getName().getLocalPart()); break; case XMLStreamConstants.END_ELEMENT: // In case the tag does not contain values or inner tag if (prefs.getProperties().isEmpty() && prefs.getChildren().isEmpty()) { prefs.getOrCreateNode(startKey); } if (startKey.equals(xmler.getName().getLocalPart())) { return; // Return to the parent tag } break; default: break; } } } protected void writePreferences(PreferencesImpl prefs, XMLStreamWriter writer) throws XMLStreamException { final Iterator<?> i = prefs.getProperties().entrySet().iterator(); while (i.hasNext()) { final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next(); writer.writeStartElement(entry.getKey().toString()); writer.writeCharacters(EscapeChars.forXML(entry.getValue().toString())); writer.writeEndElement(); } writer.flush(); } }