SLING-3737 : Instance Sling Identifier may be randomly reset on restart. Apply patch from Timothee Maret
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1608798 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 349146e..2a767db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,5 +117,17 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.8.2</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/src/main/java/org/apache/sling/settings/impl/SlingSettingsServiceImpl.java b/src/main/java/org/apache/sling/settings/impl/SlingSettingsServiceImpl.java
index 5fadb1e..1281495 100644
--- a/src/main/java/org/apache/sling/settings/impl/SlingSettingsServiceImpl.java
+++ b/src/main/java/org/apache/sling/settings/impl/SlingSettingsServiceImpl.java
@@ -18,6 +18,8 @@
*/
package org.apache.sling.settings.impl;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -77,6 +79,9 @@
/** The name of the data file holding install run mode options */
private static final String OPTIONS_FILE = "sling.options.file";
+ /** The length in bytes of a sling identifier */
+ private static final int SLING_ID_LENGTH = 36;
+
/** The properties for name, description. */
private final Map<String, String> slingProps = new HashMap<String, String>();
@@ -124,7 +129,7 @@
// the osgi framework does not support storing something in the file system
throw new RuntimeException("Unable to read from bundle data file.");
}
- this.slingId = this.readSlingId(idFile);
+ this.slingId = this.readSlingId(idFile, SLING_ID_LENGTH);
// no sling id yet or failure to read file: create an id and store
if (slingId == null) {
@@ -147,7 +152,7 @@
}
}
- private static final class Options implements Serializable {
+ static final class Options implements Serializable {
private static final long serialVersionUID = 1L;
String[] modes;
String selected;
@@ -263,7 +268,7 @@
return optionsList;
}
- private void writeOptions(final BundleContext context, final List<Options> optionsList) {
+ void writeOptions(final BundleContext context, final List<Options> optionsList) {
final File file = context.getDataFile(OPTIONS_FILE);
FileOutputStream fos = null;
ObjectOutputStream oos = null;
@@ -286,30 +291,28 @@
/**
* Read the id from a file.
*/
- private String readSlingId(final File idFile) {
- if (idFile.exists() && idFile.length() >= 36) {
- FileInputStream fin = null;
+ String readSlingId(final File idFile, int maxLength) {
+ if (idFile.exists() && idFile.length() >= maxLength) {
+ DataInputStream dis = null;
try {
- fin = new FileInputStream(idFile);
- final byte[] rawBytes = new byte[36];
- if (fin.read(rawBytes) == 36) {
- final String rawString = new String(rawBytes, "ISO-8859-1");
+ final byte[] rawBytes = new byte[maxLength];
+ dis = new DataInputStream(new FileInputStream(idFile));
+ dis.readFully(rawBytes);
+ final String rawString = new String(rawBytes, "ISO-8859-1");
- // roundtrip to ensure correct format of UUID value
- final String id = UUID.fromString(rawString).toString();
- logger.debug("Got Sling ID {} from file {}", id, idFile);
+ // roundtrip to ensure correct format of UUID value
+ final String id = UUID.fromString(rawString).toString();
+ logger.debug("Got Sling ID {} from file {}", id, idFile);
- return id;
- }
+ return id;
} catch (final Throwable t) {
logger.error("Failed reading UUID from id file " + idFile
+ ", creating new id", t);
} finally {
- if (fin != null) {
+ if (dis != null) {
try {
- fin.close();
- } catch (IOException ignore) {
- }
+ dis.close();
+ } catch (IOException ignore){}
}
}
}
@@ -319,22 +322,22 @@
/**
* Write the sling id file.
*/
- private void writeSlingId(final File idFile, final String id) {
+ void writeSlingId(final File idFile, final String id) {
idFile.delete();
idFile.getParentFile().mkdirs();
- FileOutputStream fout = null;
+ DataOutputStream dos = null;
try {
- fout = new FileOutputStream(idFile);
- fout.write(slingId.getBytes("ISO-8859-1"));
- fout.flush();
+ final byte[] rawBytes = id.getBytes("ISO-8859-1");
+ dos = new DataOutputStream(new FileOutputStream(idFile));
+ dos.write(rawBytes, 0, rawBytes.length);
+ dos.flush();
} catch (final Throwable t) {
logger.error("Failed writing UUID to id file " + idFile, t);
} finally {
- if (fout != null) {
+ if (dos != null) {
try {
- fout.close();
- } catch (IOException ignore) {
- }
+ dos.close();
+ } catch (IOException ignore) {}
}
}
}
diff --git a/src/test/java/org/apache/sling/settings/impl/SlingSettingsServiceImplTest.java b/src/test/java/org/apache/sling/settings/impl/SlingSettingsServiceImplTest.java
new file mode 100644
index 0000000..81e3dd1
--- /dev/null
+++ b/src/test/java/org/apache/sling/settings/impl/SlingSettingsServiceImplTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.settings.impl;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.apache.sling.launchpad.api.StartupHandler;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+
+public class SlingSettingsServiceImplTest {
+
+ private static final String SLING_ID_FILE_NAME = "sling.id.file";
+
+ private static final String OPTIONS_FILE_NAME = "sling.options.file";
+
+ private static final String SLING_ID = "097bae9b-bf60-45a2-ad8c-ccdd374dd9b0";
+
+ private File slingIdFile = null;
+
+ private File optionsFile = null;
+
+ @Before
+ public void before() throws IOException {
+ slingIdFile = File.createTempFile(
+ SLING_ID_FILE_NAME, "");
+ optionsFile = File.createTempFile(
+ OPTIONS_FILE_NAME, "");
+ }
+
+ @After
+ public void after() throws IOException {
+ if (slingIdFile != null ) {
+ slingIdFile.delete();
+ slingIdFile = null;
+ }
+ if (optionsFile != null) {
+ optionsFile.delete();
+ optionsFile = null;
+ }
+ }
+
+ @Test
+ public void testGenerateSlingId()
+ throws IOException {
+ String slingId = readSlingId(slingIdFile, optionsFile, SLING_ID.length());
+ Assert.assertNotNull(slingId);
+ }
+
+ @Test
+ public void testGetSlingId()
+ throws IOException {
+ writeSlingId(slingIdFile, optionsFile, SLING_ID);
+ String generated = readSlingId(slingIdFile, optionsFile, SLING_ID.length());
+ Assert.assertNotNull(generated);
+ Assert.assertEquals(SLING_ID, generated);
+ String slingId = readSlingId(slingIdFile, optionsFile, SLING_ID.length());
+ Assert.assertNotNull(slingId);
+ Assert.assertEquals(generated, slingId);
+ }
+
+ @Test
+ public void testGetLongSlingIdFromTooLargeData()
+ throws IOException {
+ String data = SLING_ID + RandomStringUtils.randomAscii(1024 * 1024); // 1MB long random String
+ writeSlingId(slingIdFile, optionsFile, data);
+ String slingId = readSlingId(slingIdFile, optionsFile, SLING_ID.length());
+ Assert.assertNotNull(slingId);
+ Assert.assertEquals(SLING_ID, slingId);
+ }
+
+ @Test
+ public void testGetSlingIdFromTooShortData()
+ throws IOException {
+ String data = RandomStringUtils.randomAscii(8); // 8 byte long string
+ writeSlingId(slingIdFile, optionsFile, data);
+ String slingId = readSlingId(slingIdFile, optionsFile, SLING_ID.length());
+ Assert.assertNotNull(slingId);
+ Assert.assertNotEquals(SLING_ID, slingId);
+ }
+
+ private String readSlingId(File slingIdFile, File optionsFile, int maxLength)
+ throws IOException {
+ SlingSettingsServiceImpl settings = getSlingSettings(slingIdFile, optionsFile);
+ return settings.readSlingId(slingIdFile, maxLength);
+ }
+
+ private void writeSlingId(File slingIdFile, File optionsFile, String slingId)
+ throws IOException {
+ SlingSettingsServiceImpl settings = getSlingSettings(slingIdFile, optionsFile);
+ settings.writeSlingId(slingIdFile, slingId);
+ }
+
+ private SlingSettingsServiceImpl getSlingSettings(File slingIdFile, File optionsFile)
+ throws IOException {
+ BundleContext context = Mockito.mock(BundleContext.class);
+ Mockito.when(context.getDataFile(SLING_ID_FILE_NAME))
+ .thenReturn(slingIdFile);
+ Mockito.when(context.getDataFile(OPTIONS_FILE_NAME))
+ .thenReturn(optionsFile);
+ StartupHandler handler = Mockito.mock(StartupHandler.class);
+ // write options
+ List<SlingSettingsServiceImpl.Options> options = new ArrayList<SlingSettingsServiceImpl.Options>();
+ FileOutputStream fos = null;
+ ObjectOutputStream oos = null;
+ try {
+ fos = new FileOutputStream(optionsFile);
+ oos = new ObjectOutputStream(fos);
+ oos.writeObject(options);
+ } catch ( final IOException ioe ) {
+ throw new RuntimeException("Unable to write to options data file.", ioe);
+ } finally {
+ if ( oos != null ) {
+ try {
+ oos.close();
+ } catch (IOException ignore) {
+ // ...
+ }
+ }
+ if ( fos != null ) {
+ try {
+ fos.close();
+ } catch (IOException ignore) {
+ // ...
+ }
+ }
+ }
+ return new SlingSettingsServiceImpl(context, handler);
+ }
+}