diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java b/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java deleted file mode 100644 index 288ff8941f..0000000000 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the LICENSE file that accompanied this code. - * - * This code 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 General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -package org.phoebus.framework.preferences; - -/** - * Static methods for translating Base64 encoded strings to byte arrays - * and vice-versa. - * - * @author Josh Bloch - * @see Preferences - * @since 1.4 - */ -class Base64 { - /** - * Translates the specified byte array into a Base64 string as per - * Preferences.put(byte[]). - */ - static String byteArrayToBase64(byte[] a) { - return byteArrayToBase64(a, false); - } - - /** - * Translates the specified byte array into an "alternate representation" - * Base64 string. This non-standard variant uses an alphabet that does - * not contain the uppercase alphabetic characters, which makes it - * suitable for use in situations where case-folding occurs. - */ - static String byteArrayToAltBase64(byte[] a) { - return byteArrayToBase64(a, true); - } - - private static String byteArrayToBase64(byte[] a, boolean alternate) { - int aLen = a.length; - int numFullGroups = aLen/3; - int numBytesInPartialGroup = aLen - 3*numFullGroups; - int resultLen = 4*((aLen + 2)/3); - StringBuffer result = new StringBuffer(resultLen); - char[] intToAlpha = (alternate ? intToAltBase64 : intToBase64); - - // Translate all full groups from byte array elements to Base64 - int inCursor = 0; - for (int i=0; i> 2]); - result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); - result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]); - result.append(intToAlpha[byte2 & 0x3f]); - } - - // Translate partial group if present - if (numBytesInPartialGroup != 0) { - int byte0 = a[inCursor++] & 0xff; - result.append(intToAlpha[byte0 >> 2]); - if (numBytesInPartialGroup == 1) { - result.append(intToAlpha[(byte0 << 4) & 0x3f]); - result.append("=="); - } else { - // assert numBytesInPartialGroup == 2; - int byte1 = a[inCursor++] & 0xff; - result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); - result.append(intToAlpha[(byte1 << 2)&0x3f]); - result.append('='); - } - } - // assert inCursor == a.length; - // assert result.length() == resultLen; - return result.toString(); - } - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Base64 Alphabet" equivalents as specified - * in Table 1 of RFC 2045. - */ - private static final char intToBase64[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Alternate Base64 Alphabet" equivalents. - * This is NOT the real Base64 Alphabet as per in Table 1 of RFC 2045. - * This alternate alphabet does not use the capital letters. It is - * designed for use in environments where "case folding" occurs. - */ - private static final char intToAltBase64[] = { - '!', '"', '#', '$', '%', '&', '\'', '(', ')', ',', '-', '.', ':', - ';', '<', '>', '@', '[', ']', '^', '`', '_', '{', '|', '}', '~', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '?' - }; - - /** - * Translates the specified Base64 string (as per Preferences.get(byte[])) - * into a byte array. - * - * @throw IllegalArgumentException if s is not a valid Base64 - * string. - */ - static byte[] base64ToByteArray(String s) { - return base64ToByteArray(s, false); - } - - /** - * Translates the specified "alternate representation" Base64 string - * into a byte array. - * - * @throw IllegalArgumentException or ArrayOutOfBoundsException - * if s is not a valid alternate representation - * Base64 string. - */ - static byte[] altBase64ToByteArray(String s) { - return base64ToByteArray(s, true); - } - - private static byte[] base64ToByteArray(String s, boolean alternate) { - byte[] alphaToInt = (alternate ? altBase64ToInt : base64ToInt); - int sLen = s.length(); - int numGroups = sLen/4; - if (4*numGroups != sLen) - throw new IllegalArgumentException( - "String length must be a multiple of four."); - int missingBytesInLastGroup = 0; - int numFullGroups = numGroups; - if (sLen != 0) { - if (s.charAt(sLen-1) == '=') { - missingBytesInLastGroup++; - numFullGroups--; - } - if (s.charAt(sLen-2) == '=') - missingBytesInLastGroup++; - } - byte[] result = new byte[3*numGroups - missingBytesInLastGroup]; - - // Translate all full groups from base64 to byte array elements - int inCursor = 0, outCursor = 0; - for (int i=0; i> 4)); - result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2)); - result[outCursor++] = (byte) ((ch2 << 6) | ch3); - } - - // Translate partial group, if present - if (missingBytesInLastGroup != 0) { - int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt); - int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt); - result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4)); - - if (missingBytesInLastGroup == 1) { - int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt); - result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2)); - } - } - // assert inCursor == s.length()-missingBytesInLastGroup; - // assert outCursor == result.length; - return result; - } - - /** - * Translates the specified character, which is assumed to be in the - * "Base 64 Alphabet" into its equivalent 6-bit positive integer. - * - * @throw IllegalArgumentException or ArrayOutOfBoundsException if - * c is not in the Base64 Alphabet. - */ - private static int base64toInt(char c, byte[] alphaToInt) { - int result = alphaToInt[c]; - if (result < 0) - throw new IllegalArgumentException("Illegal character " + c); - return result; - } - - /** - * This array is a lookup table that translates unicode characters - * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) - * into their 6-bit positive integer equivalents. Characters that - * are not in the Base64 alphabet but fall within the bounds of the - * array are translated to -1. - */ - private static final byte base64ToInt[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - }; - - /** - * This array is the analogue of base64ToInt, but for the nonstandard - * variant that avoids the use of uppercase alphabetic characters. - */ - private static final byte altBase64ToInt[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, - 2, 3, 4, 5, 6, 7, 8, -1, 62, 9, 10, 11, -1 , 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 12, 13, 14, -1, 15, 63, 16, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 17, -1, 18, 19, 21, 20, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 22, 23, 24, 25 - }; - - public static void main(String args[]) { - int numRuns = Integer.parseInt(args[0]); - int numBytes = Integer.parseInt(args[1]); - java.util.Random rnd = new java.util.Random(); - for (int i=0; i" + - - "" + - - "" + "" + - - "" + "" + - - "" + "" + - - "" + "" - + "" + ""; - /** - * Version number for the format exported preferences files. - */ - private static final String EXTERNAL_XML_VERSION = "1.0"; - - /* - * Version number for the internal map files. - */ - private static final String MAP_XML_VERSION = "1.0"; - - /** - * Export the specified preferences node and, if subTree is true, all subnodes, - * to the specified output stream. Preferences are exported as an XML document - * conforming to the definition in the Preferences spec. - * - * @throws IOException - * if writing to the specified output stream results in an - * IOException. - * @throws BackingStoreException - * if preference data cannot be read from backing store. - * @throws IllegalStateException - * if this node (or an ancestor) has been removed with the - * {@link #removeNode()} method. - */ - static void export(OutputStream os, final Preferences p, boolean subTree) - throws IOException, BackingStoreException { - if (((FileSystemPreferences) p).isRemoved()) - throw new IllegalStateException("Node has been removed"); - Document doc = createPrefsDoc("preferences"); - Element preferences = doc.getDocumentElement(); - preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION); - Element xmlRoot = (Element) preferences.appendChild(doc.createElement("root")); - xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system")); - - // Get bottom-up list of nodes from p to root, excluding root - List ancestors = new ArrayList(); - - for (Preferences kid = p, dad = kid.parent(); dad != null; kid = dad, dad = kid.parent()) { - ancestors.add(kid); - } - Element e = xmlRoot; - for (int i = ancestors.size() - 1; i >= 0; i--) { - e.appendChild(doc.createElement("map")); - e = (Element) e.appendChild(doc.createElement("node")); - e.setAttribute("name", ((Preferences) ancestors.get(i)).name()); - } - putPreferencesInXml(e, doc, p, subTree); - - writeDoc(doc, os); - } - - /** - * Put the preferences in the specified Preferences node into the specified XML - * element which is assumed to represent a node in the specified XML document - * which is assumed to conform to PREFS_DTD. If subTree is true, create children - * of the specified XML node conforming to all of the children of the specified - * Preferences node and recurse. - * - * @throws BackingStoreException - * if it is not possible to read the preferences or children out of - * the specified preferences node. - */ - private static void putPreferencesInXml(Element elt, Document doc, Preferences prefs, boolean subTree) - throws BackingStoreException { - Preferences[] kidsCopy = null; - String[] kidNames = null; - - // Node is locked to export its contents and get a - // copy of children, then lock is released, - // and, if subTree = true, recursive calls are made on children - - // to remove a node we need an exclusive lock - - // to remove a node we need an exclusive lock - if (!((FileSystemPreferences) prefs).lockFile(false)) - throw (new BackingStoreException("Couldn't get file lock.")); - try { - // Check if this node was concurrently removed. If yes - // remove it from XML Document and return. - if (((FileSystemPreferences) prefs).isRemoved()) { - elt.getParentNode().removeChild(elt); - return; - } - // Put map in xml element - String[] keys = prefs.keys(); - Element map = (Element) elt.appendChild(doc.createElement("map")); - for (int i = 0; i < keys.length; i++) { - Element entry = (Element) map.appendChild(doc.createElement("entry")); - entry.setAttribute("key", keys[i]); - // NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL - entry.setAttribute("value", prefs.get(keys[i], null)); - } - // Recurse if appropriate - if (subTree) { - /* Get a copy of kids while lock is held */ - kidNames = prefs.childrenNames(); - kidsCopy = new Preferences[kidNames.length]; - for (int i = 0; i < kidNames.length; i++) - kidsCopy[i] = prefs.node(kidNames[i]); - } - } finally { - ((FileSystemPreferences) prefs).unlockFile(); - } - if (subTree) { - for (int i = 0; i < kidNames.length; i++) { - Element xmlKid = (Element) elt.appendChild(doc.createElement("node")); - xmlKid.setAttribute("name", kidNames[i]); - putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree); - } - } - } - - /** - * Import preferences from the specified input stream, which is assumed to - * contain an XML document in the format described in the Preferences spec. - * - * @throws IOException - * if reading from the specified output stream results in an - * IOException. - * @throws InvalidPreferencesFormatException - * Data on input stream does not constitute a valid XML document - * with the mandated document type. - */ - static void importPreferences(InputStream is) throws IOException, InvalidPreferencesFormatException { - try { - Document doc = loadPrefsDoc(is); - String xmlVersion = doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION"); - if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) - throw new InvalidPreferencesFormatException("Exported preferences file format version " + xmlVersion - + " is not supported. This java installation can read" + " versions " + EXTERNAL_XML_VERSION - + " or older. You may need" + " to install a newer version of JDK."); - - Element xmlRoot = (Element) doc.getDocumentElement().getChildNodes().item(0); - Preferences prefsRoot = (xmlRoot.getAttribute("type").equals("user") ? Preferences.userRoot() - : Preferences.systemRoot()); - ImportSubtree(prefsRoot, xmlRoot); - } catch (SAXException | BackingStoreException e) { - throw new InvalidPreferencesFormatException(e); - } - } - - /** - * Create a new prefs XML document. - */ - private static Document createPrefsDoc(String qname) { - try { - DOMImplementation di = DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation(); - DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI); - return di.createDocument(null, qname, dt); - } catch (ParserConfigurationException e) { - throw new AssertionError(e); - } - } - - /** - * Load an XML document from specified input stream, which must have the - * requisite DTD URI. - */ - private static Document loadPrefsDoc(InputStream in) throws SAXException, IOException { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setIgnoringElementContentWhitespace(true); - dbf.setValidating(true); - dbf.setCoalescing(true); - dbf.setIgnoringComments(true); - try { - DocumentBuilder db = dbf.newDocumentBuilder(); - db.setEntityResolver(new Resolver()); - db.setErrorHandler(new EH()); - return db.parse(new InputSource(in)); - } catch (ParserConfigurationException e) { - throw new AssertionError(e); - } - } - - /** - * Write XML document to the specified output stream. - */ - private static final void writeDoc(Document doc, OutputStream out) throws IOException { - try { - TransformerFactory tf = TransformerFactory.newInstance(); - try { - tf.setAttribute("indent-number", Integer.valueOf(2)); - } catch (IllegalArgumentException iae) { - // Ignore the IAE. Should not fail the writeout even the - // transformer provider does not support "indent-number". - } - Transformer t = tf.newTransformer(); - t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId()); - t.setOutputProperty(OutputKeys.INDENT, "yes"); - // Transformer resets the "indent" info if the "result" is a StreamResult with - // an OutputStream object embedded, creating a Writer object on top of that - // OutputStream object however works. - t.transform(new DOMSource(doc), new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")))); - } catch (TransformerException e) { - throw new AssertionError(e); - } - } - - /** - * Recursively traverse the specified preferences node and store the described - * preferences into the system or current user preferences tree, as appropriate. - * @throws BackingStoreException - */ - private static void ImportSubtree(Preferences prefsNode, Element xmlNode) throws BackingStoreException { - NodeList xmlKids = xmlNode.getChildNodes(); - int numXmlKids = xmlKids.getLength(); - /* - * We first lock the node, import its contents and get child nodes. Then we - * unlock the node and go to children Since some of the children might have been - * concurrently deleted we check for this. - */ - Preferences[] prefsKids; - /* Lock the node */ - // to remove a node we need an exclusive lock - if (!((FileSystemPreferences) prefsNode).lockFile(false)) - throw (new BackingStoreException("Couldn't get file lock.")); - try { - // If removed, return silently - if (((FileSystemPreferences) prefsNode).isRemoved()) - return; - - // Import any preferences at this node - Element firstXmlKid = (Element) xmlKids.item(0); - ImportPrefs(prefsNode, firstXmlKid); - prefsKids = new Preferences[numXmlKids - 1]; - - // Get involved children - for (int i = 1; i < numXmlKids; i++) { - Element xmlKid = (Element) xmlKids.item(i); - prefsKids[i - 1] = prefsNode.node(xmlKid.getAttribute("name")); - } - } finally { - // unlocked the node - ((FileSystemPreferences) prefsNode).unlockFile(); - } - - // import children - for (int i = 1; i < numXmlKids; i++) - ImportSubtree(prefsKids[i - 1], (Element) xmlKids.item(i)); - } - - /** - * Import the preferences described by the specified XML element (a map from a - * preferences document) into the specified preferences node. - */ - private static void ImportPrefs(Preferences prefsNode, Element map) { - NodeList entries = map.getChildNodes(); - for (int i = 0, numEntries = entries.getLength(); i < numEntries; i++) { - Element entry = (Element) entries.item(i); - prefsNode.put(entry.getAttribute("key"), entry.getAttribute("value")); - } - } - - /** - * Export the specified Map to a map document on the specified - * OutputStream as per the prefs DTD. This is used as the internal - * (undocumented) format for FileSystemPrefs. - * - * @throws IOException - * if writing to the specified output stream results in an - * IOException. - */ - static void exportMap(OutputStream os, Map map) throws IOException { - Document doc = createPrefsDoc("map"); - Element xmlMap = doc.getDocumentElement(); - xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION); - - for (Iterator i = map.entrySet().iterator(); i.hasNext();) { - Map.Entry e = (Map.Entry) i.next(); - Element xe = (Element) xmlMap.appendChild(doc.createElement("entry")); - xe.setAttribute("key", (String) e.getKey()); - xe.setAttribute("value", (String) e.getValue()); - } - - writeDoc(doc, os); - } - - /** - * Import Map from the specified input stream, which is assumed to contain a map - * document as per the prefs DTD. This is used as the internal (undocumented) - * format for FileSystemPrefs. The key-value pairs specified in the XML document - * will be put into the specified Map. (If this Map is empty, it will contain - * exactly the key-value pairs int the XML-document when this method returns.) - * - * @throws IOException - * if reading from the specified output stream results in an - * IOException. - * @throws InvalidPreferencesFormatException - * Data on input stream does not constitute a valid XML document - * with the mandated document type. - */ - static void importMap(InputStream is, Map m) throws IOException, InvalidPreferencesFormatException { - try { - Document doc = loadPrefsDoc(is); - Element xmlMap = doc.getDocumentElement(); - // check version - String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION"); - if (mapVersion.compareTo(MAP_XML_VERSION) > 0) - throw new InvalidPreferencesFormatException("Preferences map file format version " + mapVersion - + " is not supported. This java installation can read" + " versions " + MAP_XML_VERSION - + " or older. You may need" + " to install a newer version of JDK."); - - NodeList entries = xmlMap.getChildNodes(); - for (int i = 0, numEntries = entries.getLength(); i < numEntries; i++) { - Element entry = (Element) entries.item(i); - m.put(entry.getAttribute("key"), entry.getAttribute("value")); - } - } catch (SAXException e) { - throw new InvalidPreferencesFormatException(e); - } - } - - private static class Resolver implements EntityResolver { - public InputSource resolveEntity(String pid, String sid) throws SAXException { - if (sid.equals(PREFS_DTD_URI)) { - InputSource is; - is = new InputSource(new StringReader(PREFS_DTD)); - is.setSystemId(PREFS_DTD_URI); - return is; - } - throw new SAXException("Invalid system identifier: " + sid); - } - } - - private static class EH implements ErrorHandler { - public void error(SAXParseException x) throws SAXException { - throw x; - } - - public void fatalError(SAXParseException x) throws SAXException { - throw x; - } - - public void warning(SAXParseException x) throws SAXException { - throw x; - } - } -} \ No newline at end of file diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java deleted file mode 100644 index 534ed13bdb..0000000000 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java +++ /dev/null @@ -1,964 +0,0 @@ -/* - * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the LICENSE file that accompanied this code. - * - * This code 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 General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -package org.phoebus.framework.preferences; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileLock; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.AbstractPreferences; -import java.util.prefs.BackingStoreException; -import java.util.prefs.InvalidPreferencesFormatException; -import java.util.prefs.Preferences; - -import org.phoebus.framework.workbench.Locations; - -/** - * Preferences implementation for Unix. Preferences are stored in the file - * system, with one directory per preferences node. All of the preferences at - * each node are stored in a single file. Atomic file system operations (e.g. - * File.renameTo) are used to ensure integrity. An in-memory cache of the - * "explored" portion of the tree is maintained for performance, and written - * back to the disk periodically. File-locking is used to ensure reasonable - * behavior when multiple VMs are running at the same time. (The file lock is - * obtained only for sync(), flush() and removeNode().) - * - * @author Josh Bloch - * @see Preferences - * @since 1.4 - */ - -@SuppressWarnings({"unchecked", "nls"}) -class FileSystemPreferences extends AbstractPreferences { - - /** - * Sync interval in seconds. - */ - private static final int SYNC_INTERVAL = Math.max(1, - Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction() { - @Override - public String run() { - return System.getProperty("java.util.prefs.syncInterval", "30"); - } - }))); - - /** - * Returns logger for error messages. Backing store exceptions are logged at - * WARNING level. - */ - private static Logger getLogger() { - return Logger.getLogger("java.util.prefs"); - } - - /** - * Directory for system preferences. - */ - private static File systemRootDir; - - /* - * Flag, indicating whether systemRoot directory is writable - */ - private static boolean isSystemRootWritable; - - /** - * Directory for user preferences. - */ - private static File userRootDir; - - /* - * Flag, indicating whether userRoot directory is writable - */ - private static boolean isUserRootWritable; - - /** - * The user root. - */ - static Preferences userRoot = null; - - static synchronized Preferences getUserRoot() { - if (userRoot == null) { - setupUserRoot(); - userRoot = new FileSystemPreferences(true); - } - return userRoot; - } - - private static void setupUserRoot() { - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - - // If Phoebus locations have been set, - // place the user preferences in the user directory that also stores mementos etc. - // Otherwise fall back to original FileSystemPreferences behavior - if (System.getProperty(Locations.PHOEBUS_USER, "").length() > 0) - userRootDir = new File(System.getProperty(Locations.PHOEBUS_USER), ".userPrefs"); - else - userRootDir = new File(System.getProperty("java.util.prefs.userRoot", System.getProperty("user.home")), - ".phoebus/.userPrefs"); - - // Attempt to create root dir if it does not yet exist. - if (!userRootDir.exists()) { - if (userRootDir.mkdirs()) { - getLogger().info("Created user preferences directory."); - } else - getLogger().warning( - "Couldn't create user preferences" + " directory. User preferences are unusable."); - } - isUserRootWritable = userRootDir.canWrite(); - String USER_NAME = System.getProperty("user.name"); - userLockFile = new File(userRootDir, ".user.lock." + USER_NAME); - userRootModFile = new File(userRootDir, ".userRootModFile." + USER_NAME); - if (!userRootModFile.exists()) - try { - // create if does not exist. - userRootModFile.createNewFile(); - } catch (IOException e) { - getLogger().warning(e.toString()); - } - userRootModTime = userRootModFile.lastModified(); - return null; - } - }); - } - - /** - * The system root. - */ - static Preferences systemRoot; - - static synchronized Preferences getSystemRoot() { - if (systemRoot == null) { - setupSystemRoot(); - systemRoot = new FileSystemPreferences(false); - } - return systemRoot; - } - - private static void setupSystemRoot() { - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - String systemPrefsDirName = System.getProperty("java.util.prefs.systemRoot", "/etc/.java"); - systemRootDir = new File(systemPrefsDirName, ".systemPrefs"); - // Attempt to create root dir if it does not yet exist. - if (!systemRootDir.exists()) { - // system root does not exist in /etc/.java - // Switching to java.home - systemRootDir = new File(System.getProperty("java.home"), ".systemPrefs"); - if (!systemRootDir.exists()) { - if (systemRootDir.mkdirs()) { - getLogger().info("Created system preferences directory " + "in java.home."); - } else { - getLogger().warning("Could not create " + "system preferences directory. System " - + "preferences are unusable."); - } - } - } - isSystemRootWritable = systemRootDir.canWrite(); - systemLockFile = new File(systemRootDir, ".system.lock"); - systemRootModFile = new File(systemRootDir, ".systemRootModFile"); - if (!systemRootModFile.exists() && isSystemRootWritable) - try { - // create if does not exist. - systemRootModFile.createNewFile(); - } catch (IOException e) { - getLogger().warning(e.toString()); - } - systemRootModTime = systemRootModFile.lastModified(); - return null; - } - }); - } - - /** - * The lock file for the user tree. - */ - static File userLockFile; - - /** - * The lock file for the system tree. - */ - static File systemLockFile; - - /** - * Unix lock handle for userRoot. Zero, if unlocked. - */ - - private static RandomAccessFile userRootLockFile = null; - private static FileLock userRootLockHandle = null; - - /** - * Unix lock handle for systemRoot. Zero, if unlocked. - */ - - private static RandomAccessFile systemRootLockFile = null; - private static FileLock systemRootLockHandle = null; - - /** - * The directory representing this preference node. There is no guarantee that - * this directory exits, as another VM can delete it at any time that it (the - * other VM) holds the file-lock. While the root node cannot be deleted, it may - * not yet have been created, or the underlying directory could have been - * deleted accidentally. - */ - private final File dir; - - /** - * The file representing this preference node's preferences. The file format is - * undocumented, and subject to change from release to release, but I'm sure - * that you can figure it out if you try real hard. - */ - private final File prefsFile; - - /** - * A temporary file used for saving changes to preferences. As part of the sync - * operation, changes are first saved into this file, and then atomically - * renamed to prefsFile. This results in an atomic state change from one valid - * set of preferences to another. The the file-lock is held for the duration of - * this transformation. - */ - private final File tmpFile; - - /** - * File, which keeps track of global modifications of userRoot. - */ - private static File userRootModFile; - - /** - * Flag, which indicated whether userRoot was modified by another VM - */ - private static boolean isUserRootModified = false; - - /** - * Keeps track of userRoot modification time. This time is reset to zero after - * UNIX reboot, and is increased by 1 second each time userRoot is modified. - */ - private static long userRootModTime; - - /* - * File, which keeps track of global modifications of systemRoot - */ - private static File systemRootModFile; - /* - * Flag, which indicates whether systemRoot was modified by another VM - */ - private static boolean isSystemRootModified = false; - - /** - * Keeps track of systemRoot modification time. This time is reset to zero after - * system reboot, and is increased by 1 second each time systemRoot is modified. - */ - private static long systemRootModTime; - - /** - * Locally cached preferences for this node (includes uncommitted changes). This - * map is initialized with from disk when the first get or put operation occurs - * on this node. It is synchronized with the corresponding disk file (prefsFile) - * by the sync operation. The initial value is read *without* acquiring the - * file-lock. - */ - private Map prefsCache = null; - - /** - * The last modification time of the file backing this node at the time that - * prefCache was last synchronized (or initially read). This value is set - * *before* reading the file, so it's conservative; the actual timestamp could - * be (slightly) higher. A value of zero indicates that we were unable to - * initialize prefsCache from the disk, or have not yet attempted to do so. (If - * prefsCache is non-null, it indicates the former; if it's null, the latter.) - */ - private long lastSyncTime = 0; - - /** - * Unix error code for locked file. - */ - private static final int EAGAIN = 11; - - /** - * Unix error code for denied access. - */ - private static final int EACCES = 13; - - /** - * A list of all uncommitted preference changes. The elements in this list are - * of type PrefChange. If this node is concurrently modified on disk by another - * VM, the two sets of changes are merged when this node is sync'ed by - * overwriting our prefsCache with the preference map last written out to disk - * (by the other VM), and then replaying this change log against that map. The - * resulting map is then written back to the disk. - */ - final List changeLog = new ArrayList(); - - /** - * Represents a change to a preference. - */ - private abstract class Change { - /** - * Reapplies the change to prefsCache. - */ - abstract void replay(); - }; - - /** - * Represents a preference put. - */ - private class Put extends Change { - String key, value; - - Put(String key, String value) { - this.key = key; - this.value = value; - } - - @Override - void replay() { - prefsCache.put(key, value); - } - } - - /** - * Represents a preference remove. - */ - private class Remove extends Change { - String key; - - Remove(String key) { - this.key = key; - } - - @Override - void replay() { - prefsCache.remove(key); - } - } - - /** - * Represents the creation of this node. - */ - private class NodeCreate extends Change { - /** - * Performs no action, but the presence of this object in changeLog will force - * the node and its ancestors to be made permanent at the next sync. - */ - @Override - void replay() { - } - } - - /** - * NodeCreate object for this node. - */ - NodeCreate nodeCreate = null; - - /** - * Replay changeLog against prefsCache. - */ - private void replayChanges() { - for (int i = 0, n = changeLog.size(); i < n; i++) - ((Change) changeLog.get(i)).replay(); - } - - private static Timer syncTimer = new Timer(true); // Daemon Thread - - static { - // Add periodic timer task to periodically sync cached prefs - syncTimer.schedule(new TimerTask() { - @Override - public void run() { - syncWorld(); - } - }, SYNC_INTERVAL * 1000, SYNC_INTERVAL * 1000); - - // Add shutdown hook to flush cached prefs on normal termination - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - syncTimer.cancel(); - syncWorld(); - } - }); - return null; - } - }); - } - - private static void syncWorld() { - /* - * Synchronization necessary because userRoot and systemRoot are lazily - * initialized. - */ - Preferences userRt; - Preferences systemRt; - synchronized (FileSystemPreferences.class) { - userRt = userRoot; - systemRt = systemRoot; - } - - try { - if (userRt != null) - userRt.flush(); - } catch (BackingStoreException e) { - getLogger().warning("Couldn't flush user prefs: " + e); - } - - try { - if (systemRt != null) - systemRt.flush(); - } catch (BackingStoreException e) { - getLogger().warning("Couldn't flush system prefs: " + e); - } - } - - private final boolean isUserNode; - - /** - * Special constructor for roots (both user and system). This constructor will - * only be called twice, by the static initializer. - */ - private FileSystemPreferences(boolean user) { - super(null, ""); - isUserNode = user; - dir = (user ? userRootDir : systemRootDir); - prefsFile = new File(dir, "prefs.xml"); - tmpFile = new File(dir, "prefs.tmp"); - } - - /** - * Construct a new FileSystemPreferences instance with the specified parent node - * and name. This constructor, called from childSpi, is used to make every node - * except for the two //roots. - */ - private FileSystemPreferences(FileSystemPreferences parent, String name) { - super(parent, name); - isUserNode = parent.isUserNode; - dir = new File(parent.dir, dirName(name)); - prefsFile = new File(dir, "prefs.xml"); - tmpFile = new File(dir, "prefs.tmp"); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - newNode = !dir.exists(); - return null; - } - }); - if (newNode) { - // These 2 things guarantee node will get wrtten at next flush/sync - prefsCache = new TreeMap(); - nodeCreate = new NodeCreate(); - changeLog.add(nodeCreate); - } - } - - @Override - public boolean isUserNode() { - return isUserNode; - } - - @Override - protected void putSpi(String key, String value) { - initCacheIfNecessary(); - changeLog.add(new Put(key, value)); - prefsCache.put(key, value); - } - - @Override - protected String getSpi(String key) { - initCacheIfNecessary(); - return (String) prefsCache.get(key); - } - - @Override - protected void removeSpi(String key) { - initCacheIfNecessary(); - changeLog.add(new Remove(key)); - prefsCache.remove(key); - } - - /** - * Initialize prefsCache if it has yet to be initialized. When this method - * returns, prefsCache will be non-null. If the data was successfully read from - * the file, lastSyncTime will be updated. If prefsCache was null, but it was - * impossible to read the file (because it didn't exist or for any other reason) - * prefsCache will be initialized to an empty, modifiable Map, and lastSyncTime - * remain zero. - */ - private void initCacheIfNecessary() { - if (prefsCache != null) - return; - - try { - loadCache(); - } catch (Exception e) { - // assert lastSyncTime == 0; - prefsCache = new TreeMap(); - } - } - - /** - * Attempt to load prefsCache from the backing store. If the attempt succeeds, - * lastSyncTime will be updated (the new value will typically correspond to the - * data loaded into the map, but it may be less, if another VM is updating this - * node concurrently). If the attempt fails, a BackingStoreException is thrown - * and both prefsCache and lastSyncTime are unaffected by the call. - */ - private void loadCache() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - Map m = new TreeMap(); - long newLastSyncTime = 0; - try { - newLastSyncTime = prefsFile.lastModified(); - FileInputStream fis = new FileInputStream(prefsFile); - FilePreferencesXmlSupport.importMap(fis, m); - fis.close(); - } catch (Exception e) { - if (e instanceof InvalidPreferencesFormatException) { - getLogger().warning("Invalid preferences format in " + prefsFile.getPath()); - prefsFile.renameTo(new File(prefsFile.getParentFile(), "IncorrectFormatPrefs.xml")); - m = new TreeMap(); - } else if (e instanceof FileNotFoundException) { - getLogger().warning("Prefs file removed in background " + prefsFile.getPath()); - } else { - throw new BackingStoreException(e); - } - } - // Attempt succeeded; update state - prefsCache = m; - lastSyncTime = newLastSyncTime; - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - /** - * Attempt to write back prefsCache to the backing store. If the attempt - * succeeds, lastSyncTime will be updated (the new value will correspond exactly - * to the data thust written back, as we hold the file lock, which prevents a - * concurrent write. If the attempt fails, a BackingStoreException is thrown and - * both the backing store (prefsFile) and lastSyncTime will be unaffected by - * this call. This call will NEVER leave prefsFile in a corrupt state. - */ - private void writeBackCache() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - try { - if (!dir.exists() && !dir.mkdirs()) - throw new BackingStoreException(dir + " create failed."); - FileOutputStream fos = new FileOutputStream(tmpFile); - FilePreferencesXmlSupport.exportMap(fos, prefsCache); - fos.close(); - Files.move(tmpFile.toPath(), prefsFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (Exception e) { - throw new BackingStoreException(e); - } - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - @Override - protected String[] keysSpi() { - initCacheIfNecessary(); - return (String[]) prefsCache.keySet().toArray(new String[prefsCache.size()]); - } - - @Override - protected String[] childrenNamesSpi() { - return (String[]) AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - List result = new ArrayList(); - File[] dirContents = dir.listFiles(); - if (dirContents != null) { - for (int i = 0; i < dirContents.length; i++) - if (dirContents[i].isDirectory()) - result.add(nodeName(dirContents[i].getName())); - } - return result.toArray(EMPTY_STRING_ARRAY); - } - }); - } - - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - @Override - protected AbstractPreferences childSpi(String name) { - return new FileSystemPreferences(this, name); - } - - @Override - public void removeNode() throws BackingStoreException { - synchronized (isUserNode() ? userLockFile : systemLockFile) { - // to remove a node we need an exclusive lock - if (!lockFile(false)) - throw (new BackingStoreException("Couldn't get file lock.")); - try { - super.removeNode(); - } finally { - unlockFile(); - } - } - } - - /** - * Called with file lock held (in addition to node locks). - */ - @Override - protected void removeNodeSpi() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - if (changeLog.contains(nodeCreate)) { - changeLog.remove(nodeCreate); - nodeCreate = null; - return null; - } - if (!dir.exists()) - return null; - prefsFile.delete(); - tmpFile.delete(); - // dir should be empty now. If it's not, empty it - File[] junk = dir.listFiles(); - if (junk.length != 0) { - getLogger().warning("Found extraneous files when removing node: " + Arrays.asList(junk)); - for (int i = 0; i < junk.length; i++) - junk[i].delete(); - } - if (!dir.delete()) - throw new BackingStoreException("Couldn't delete dir: " + dir); - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - @Override - public synchronized void sync() throws BackingStoreException { - boolean userNode = isUserNode(); - boolean shared; - - if (userNode) { - shared = false; /* use exclusive lock for user prefs */ - } else { - /* - * if can write to system root, use exclusive lock. otherwise use shared lock. - */ - shared = !isSystemRootWritable; - } - synchronized (isUserNode() ? userLockFile : systemLockFile) { - if (!lockFile(shared)) - throw (new BackingStoreException("Couldn't get file lock.")); - final Long newModTime = (Long) AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - long nmt; - if (isUserNode()) { - nmt = userRootModFile.lastModified(); - isUserRootModified = userRootModTime == nmt; - } else { - nmt = systemRootModFile.lastModified(); - isSystemRootModified = systemRootModTime == nmt; - } - return Long.valueOf(nmt); - } - }); - try { - super.sync(); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - if (isUserNode()) { - userRootModTime = newModTime.longValue() + 1000; - userRootModFile.setLastModified(userRootModTime); - } else { - systemRootModTime = newModTime.longValue() + 1000; - systemRootModFile.setLastModified(systemRootModTime); - } - return null; - } - }); - } finally { - unlockFile(); - } - } - } - - @Override - protected void syncSpi() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - syncSpiPrivileged(); - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - private void syncSpiPrivileged() throws BackingStoreException { - if (isRemoved()) - throw new IllegalStateException("Node has been removed"); - if (prefsCache == null) - return; // We've never been used, don't bother syncing - long lastModifiedTime; - if ((isUserNode() ? isUserRootModified : isSystemRootModified)) { - lastModifiedTime = prefsFile.lastModified(); - if (lastModifiedTime != lastSyncTime) { - // Prefs at this node were externally modified; read in node and - // playback any local mods since last sync - loadCache(); - replayChanges(); - lastSyncTime = lastModifiedTime; - } - } else if (lastSyncTime != 0 && !dir.exists()) { - // This node was removed in the background. Playback any changes - // against a virgin (empty) Map. - prefsCache = new TreeMap(); - replayChanges(); - } - if (!changeLog.isEmpty()) { - writeBackCache(); // Creates directory & file if necessary - /* - * Attempt succeeded; it's barely possible that the call to lastModified might - * fail (i.e., return 0), but this would not be a disaster, as lastSyncTime is - * allowed to lag. - */ - lastModifiedTime = prefsFile.lastModified(); - /* - * If lastSyncTime did not change, or went back increment by 1 second. Since we - * hold the lock lastSyncTime always monotonically encreases in the atomic - * sense. - */ - if (lastSyncTime <= lastModifiedTime) { - lastSyncTime = lastModifiedTime + 1000; - prefsFile.setLastModified(lastSyncTime); - } - changeLog.clear(); - } - } - - @Override - public void flush() throws BackingStoreException { - if (isRemoved()) - return; - sync(); - } - - @Override - protected void flushSpi() throws BackingStoreException { - // assert false; - } - - @Override - protected boolean isRemoved() { - return super.isRemoved(); - } - - /** - * Returns true if the specified character is appropriate for use in Unix - * directory names. A character is appropriate if it's a printable ASCII - * character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f), dot ('.', - * 0x2e), or underscore ('_', 0x5f). - */ - private static boolean isDirChar(char ch) { - return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_'; - } - - /** - * Returns the directory name corresponding to the specified node name. - * Generally, this is just the node name. If the node name includes - * inappropriate characters (as per isDirChar) it is translated to Base64. with - * the underscore character ('_', 0x5f) prepended. - */ - private static String dirName(String nodeName) { - for (int i = 0, n = nodeName.length(); i < n; i++) - if (!isDirChar(nodeName.charAt(i))) - return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName)); - return nodeName; - } - - /** - * Translate a string into a byte array by translating each character into two - * bytes, high-byte first ("big-endian"). - */ - private static byte[] byteArray(String s) { - int len = s.length(); - byte[] result = new byte[2 * len]; - for (int i = 0, j = 0; i < len; i++) { - char c = s.charAt(i); - result[j++] = (byte) (c >> 8); - result[j++] = (byte) c; - } - return result; - } - - /** - * Returns the node name corresponding to the specified directory name. (Inverts - * the transformation of dirName(String). - */ - private static String nodeName(String dirName) { - if (dirName.charAt(0) != '_') - return dirName; - byte a[] = Base64.altBase64ToByteArray(dirName.substring(1)); - StringBuffer result = new StringBuffer(a.length / 2); - for (int i = 0; i < a.length;) { - int highByte = a[i++] & 0xff; - int lowByte = a[i++] & 0xff; - result.append((char) ((highByte << 8) | lowByte)); - } - return result.toString(); - } - - /** - * Try to acquire the appropriate file lock (user or system). If the initial - * attempt fails, several more attempts are made using an exponential backoff - * strategy. If all attempts fail, this method returns false. - * - * @throws SecurityException - * if file access denied. - */ - boolean lockFile(boolean shared) throws SecurityException { - boolean usernode = isUserNode(); - int errorCode = 0; - File lockFile = (usernode ? userLockFile : systemLockFile); - long sleepTime = INIT_SLEEP_TIME; - for (int i = 0; i < MAX_ATTEMPTS; i++) { - try { - RandomAccessFile file = new RandomAccessFile(lockFile.getCanonicalPath(), "rw"); - FileLock lock = file.getChannel().lock(); - - if (lock != null) { - if (usernode) { - userRootLockFile = file; - userRootLockHandle = lock; - } else { - systemRootLockFile = file; - systemRootLockHandle = lock; - } - return true; - } - } catch (IOException e) { - } - - try { - Thread.sleep(sleepTime); - } catch (InterruptedException e) { - checkLockFile0ErrorCode(errorCode); - return false; - } - sleepTime *= 2; - } - checkLockFile0ErrorCode(errorCode); - return false; - } - - /** - * Checks if unlockFile0() returned an error. Throws a SecurityException, if - * access denied. Logs a warning otherwise. - */ - private void checkLockFile0ErrorCode(int errorCode) throws SecurityException { - if (errorCode == EACCES) - throw new SecurityException( - "Could not lock " + (isUserNode() ? "User prefs." : "System prefs.") + " Lock file access denied."); - if (errorCode != EAGAIN) - getLogger().warning("Could not lock " + (isUserNode() ? "User prefs. " : "System prefs.") - + " Unix error code " + errorCode + "."); - } - - /** - * Initial time between lock attempts, in ms. The time is doubled after each - * failing attempt (except the first). - */ - private static int INIT_SLEEP_TIME = 50; - - /** - * Maximum number of lock attempts. - */ - private static int MAX_ATTEMPTS = 5; - - /** - * Release the the appropriate file lock (user or system). - * - * @throws SecurityException - * if file access denied. - */ - void unlockFile() { - boolean usernode = isUserNode(); - RandomAccessFile rootLockFile = (usernode ? userRootLockFile : systemRootLockFile); - FileLock lockHandle = (usernode ? userRootLockHandle : systemRootLockHandle); - try { - lockHandle.close(); - } catch (IOException e) { - getLogger().warning("Unlock: zero lockHandle for " + (usernode ? "user" : "system") + " preferences.)"); - return; - } finally { - try { - rootLockFile.close(); - } catch (IOException e) { - getLogger().log(Level.WARNING, "Cannot close lock file " + rootLockFile, e); - } - } - - } -} \ No newline at end of file diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java new file mode 100644 index 0000000000..e0cea922f1 --- /dev/null +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2024 Oak Ridge National Laboratory. + * 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 + *******************************************************************************/ +package org.phoebus.framework.preferences; + +import java.util.HashMap; +import java.util.Map; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +/** Java Preferences that are held in memory, not persisted + * @author Kay Kasemir + */ +class InMemoryPreferences extends AbstractPreferences +{ + /** Preferences for both "user" and "system" */ + private static final InMemoryPreferences prefs = new InMemoryPreferences(null, ""); + + /** Settings for this node in the preferences hierarchy */ + private Map cache = new HashMap<>(); + + // Javadoc for all ..Spi calls includes + // "This method is invoked with the lock on this node held." + // so no need for ConcurrentHashMap or our own locking + + /** @return User preferences */ + public static Preferences getUserRoot() + { + return prefs; + } + + /** @return System preferences */ + public static Preferences getSystemRoot() + { + return prefs; + } + + InMemoryPreferences(final InMemoryPreferences parent, final String name) + { + super(parent, name); + } + + /** @inheritDoc */ + @Override + protected void putSpi(String key, String value) + { + cache.put(key, value); + } + + /** @inheritDoc */ + @Override + protected String getSpi(String key) + { + return cache.get(key); + } + + /** @inheritDoc */ + @Override + protected void removeSpi(String key) + { + cache.remove(key); + } + + /** @inheritDoc */ + @Override + protected void removeNodeSpi() throws BackingStoreException + { + // Nothing to remove in the file system + cache.clear(); + } + + /** @inheritDoc */ + @Override + protected String[] keysSpi() throws BackingStoreException + { + return cache.keySet().toArray(new String[cache.size()]); + } + + /** @inheritDoc */ + @Override + protected String[] childrenNamesSpi() throws BackingStoreException + { + // This method need not return the names of any nodes already cached + // by the AbstractPreferences + return new String[0]; + } + + /** @inheritDoc */ + @Override + protected AbstractPreferences childSpi(String name) + { + // AbstractPreferences guaranteed that the named node has not been returned + // by a previous invocation of this method or {@link #getChild(String)}, + // so only called once and can then create the one and only new child + return new InMemoryPreferences(this, name); + } + + /** @inheritDoc */ + @Override + protected void syncSpi() throws BackingStoreException + { + // Nothing to sync + } + + /** @inheritDoc */ + @Override + protected void flushSpi() throws BackingStoreException + { + // Nothing to sync + } +} diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java index fe4f6fb453..fe9b303ccc 100644 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java @@ -4,7 +4,7 @@ import java.util.prefs.PreferencesFactory; /** - * A service which enabled the use of the file backed implementation of java {@link Preferences}. + * A service which enabled the use of the in-memory implementation of java {@link Preferences}. * @author Kunal Shroff * */ @@ -22,13 +22,13 @@ public static Preferences userNodeForClass(final Class clazz) @Override public Preferences userRoot() { - return FileSystemPreferences.getUserRoot(); + return InMemoryPreferences.getUserRoot(); } @Override public Preferences systemRoot() { - return FileSystemPreferences.getSystemRoot(); + return InMemoryPreferences.getSystemRoot(); } } diff --git a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java index 6583817b53..0add9484a5 100644 --- a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java +++ b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2020 Oak Ridge National Laboratory. + * Copyright (c) 2017-2024 Oak Ridge National Laboratory. * 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 @@ -11,34 +11,27 @@ import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.util.Arrays; import java.util.List; import java.util.logging.Level; -import org.phoebus.framework.jobs.JobManager; -import org.phoebus.framework.preferences.PropertyPreferenceLoader; import org.phoebus.framework.preferences.PropertyPreferenceWriter; import org.phoebus.framework.workbench.ApplicationService; import org.phoebus.framework.workbench.Locations; import org.phoebus.ui.application.Messages; import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.dialog.OpenFileDialog; import org.phoebus.ui.docking.DockPane; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.ui.javafx.ReadOnlyTextCell; import org.phoebus.ui.spi.MenuEntry; -import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TableCell; @@ -47,10 +40,8 @@ import javafx.scene.control.TextArea; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import javafx.stage.FileChooser.ExtensionFilter; /** Menu entry to open 'about' * @author Kay Kasemir @@ -216,44 +207,11 @@ private Node createDetailSection() area = new TextArea(prefs_buf.toString()); area.setEditable(false); - final Button import_prefs = new Button("Import Preferences"); - import_prefs.setOnAction(event -> import_preferences()); - final HBox bottom_row = new HBox(import_prefs); - bottom_row.setAlignment(Pos.BASELINE_RIGHT); - VBox.setVgrow(area, Priority.ALWAYS); - final Tab prefs = new Tab(Messages.HelpAboutPrefs, new VBox(5, area, bottom_row)); + final Tab prefs = new Tab(Messages.HelpAboutPrefs, area); final TabPane tabs = new TabPane(apps, envs, props, prefs); return tabs; } - - /** Prompt for settings.ini to import, then offer restart */ - private void import_preferences() - { - final DockPane parent = DockPane.getActiveDockPane(); - - final ExtensionFilter[] ini = new ExtensionFilter[] { new ExtensionFilter("Preference settings.ini", List.of("*.ini")) }; - final File file = new OpenFileDialog().promptForFile(parent.getScene().getWindow(), Messages.Open, null, ini); - if (file == null) - return; - - JobManager.schedule("Load preferences", monitor -> - { - PropertyPreferenceLoader.load(new FileInputStream(file)); - Platform.runLater(() -> - { - final Alert restart = new Alert(AlertType.CONFIRMATION); - restart.setHeaderText("Restart to activate loaded settings"); - restart.setContentText("For performance reasons, preference settings are only loaded once on startup.\n" + - "Exit application so you can then start it again?"); - restart.getDialogPane().setPrefSize(500, 300); - restart.setResizable(true); - DialogHelper.positionDialog(restart, parent, -400, -300); - if (restart.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) - System.exit(0); - }); - }); - } } diff --git a/docs/source/locations.rst b/docs/source/locations.rst index 2c0ee43a56..4eace4c04e 100644 --- a/docs/source/locations.rst +++ b/docs/source/locations.rst @@ -19,7 +19,7 @@ set when starting the product. ``phoebus.user``: Location where phoebus keeps the memento - and preferences. + and saved layouts. Defaults to ``.phoebus`` in the user's home directory. Site-Specific Branding and Settings diff --git a/docs/source/preferences.rst b/docs/source/preferences.rst index 67357bd0a6..df70bd01c9 100644 --- a/docs/source/preferences.rst +++ b/docs/source/preferences.rst @@ -64,22 +64,15 @@ that lists all preference settings in the same format that is used by the ``settings.ini`` file. You can copy settings that you need to change from the display into your settings file. -The same details pane that lists current preference settings also -offers an ``Import Preferences`` button for loading a ``settings.ini`` -file. You may use that as an alternative to the command line ``-settings ..`` option, -but note that settings loaded via this button only become effective -after a restart. - -Settings loaded via either the ``-settings ..`` command line option -or the ``Import Preferences`` button are stored in the user location (see :ref:`locations`). -They remain effective until different settings are loaded or the user location is deleted. -It is therefore not necessary to always run the application with the same -``-settings ..`` command line option. Just invoking with the command line option -once or using the ``Import Preferences`` button once suffices to load settings. -In practice, however, it is advisable to include the ``-settings ..`` command line option -in a site-specific application start script. -This way, new users do not need to remember to once start with the option, -and existing users will benefit from changes to the settings file. +Settings loaded via the ``-settings ..`` command line option +only remain effective while the application is running. +It is therefore necessary to always run the application with the same +``-settings ..`` command line option to get the same results. +In practice, it is advisable to include the ``-settings ..`` command line option +in a site-specific application start script or add them to a site-specific +product as detailed below. +This way, new users do not need to remember any command line settings +because they are applied in the launcher script or bundled into the product. Conceptually, preference settings are meant to hold critical configuration parameters like the control system network configuration. @@ -94,7 +87,7 @@ like context menus or configuration dialogs. When you package phoebus for distribution at your site, you can also place a file ``settings.ini`` in the installation location (see :ref:`locations`). At startup, Phoebus will automatically load the file ``settings.ini`` -from the installation location, eliminating the need for your users +from the installation location, eliminating the need for your users or a launcher script to add the ``-settings ..`` command line option. @@ -145,7 +138,7 @@ In your application code, you can most conveniently access them like this:: The ``AnnotatedPreferences`` helper will read your ``*preferences.properties``, -apply updates from ``java.util.prefs.Preferences``, and then set the values +apply updates from ``java.util.prefs.Preferences`` that have been added via ``-settings ..``, and then set the values of all static fields annotated with ``@Preference``. It handles basic types like ``int``, ``long``, ``double``, ``boolean``, ``String``, ``File``. It can also parse comma-separated items into ``int[]`` or ``String[]``. @@ -169,10 +162,6 @@ returns a ``PreferencesReader``, or you could directly use that lower level API // and parse as desired. The ``PreferencesReader`` loads defaults from the property file, -then allows overrides via the ``java.util.prefs.Preferences`` API. -By default, the user settings are stored in a ``.phoebus`` folder -in the home directory. -This location can be changed by setting the Java property ``phoebus.user``. - -In the future, a preference UI might be added, but as mentioned -the preference settings are not meant to be adjusted by end users. +then allows overrides via the ``java.util.prefs.Preferences`` API +that is used when loading a ``settings.ini`` in the installation location +and by the ``-settings ..`` provided on the command line.