FELIX-1479 Enhance FilePersistenceManager by checks for the system
SecurityManager and if set using the access control context setup
at instance creation time call the respective operations in
a Privileged[Exception]Action
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@804404 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java b/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java
index 07642c1..1a0234a 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java
@@ -25,6 +25,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
import java.util.BitSet;
import java.util.Dictionary;
import java.util.Enumeration;
@@ -121,6 +126,11 @@
private static final BitSet VALID_PATH_CHARS;
/**
+ * The access control context we use in the presence of a security manager.
+ */
+ private final AccessControlContext acc;
+
+ /**
* The abstract path name of the configuration files.
*/
private final File location;
@@ -283,6 +293,16 @@
*/
public FilePersistenceManager( BundleContext bundleContext, String location )
{
+ // setup the access control context from the calling setup
+ if ( System.getSecurityManager() != null )
+ {
+ acc = AccessController.getContext();
+ }
+ else
+ {
+ acc = null;
+ }
+
// no configured location, use the config dir in the bundle persistent
// area
if ( location == null && bundleContext != null )
@@ -369,7 +389,33 @@
*
* @param pid The identifier of the configuration file to delete.
*/
- public void delete( String pid )
+ public void delete( final String pid )
+ {
+ if ( System.getSecurityManager() != null )
+ {
+ _privilegedDelete( pid );
+ }
+ else
+ {
+ _delete( pid );
+ }
+ }
+
+
+ private void _privilegedDelete( final String pid )
+ {
+ AccessController.doPrivileged( new PrivilegedAction()
+ {
+ public Object run()
+ {
+ _delete( pid );
+ return null;
+ }
+ }, acc );
+ }
+
+
+ private void _delete( final String pid )
{
synchronized ( this )
{
@@ -386,7 +432,31 @@
*
* @return <code>true</code> if the file exists
*/
- public boolean exists( String pid )
+ public boolean exists( final String pid )
+ {
+ if ( System.getSecurityManager() != null )
+ {
+ return _privilegedExists( pid );
+ }
+
+ return _exists( pid );
+ }
+
+
+ private boolean _privilegedExists( final String pid )
+ {
+ final Object result = AccessController.doPrivileged( new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return Boolean.valueOf( _exists( pid ) );
+ }
+ } );
+ return ( ( Boolean ) result ).booleanValue();
+ }
+
+
+ private boolean _exists( final String pid )
{
synchronized ( this )
{
@@ -407,7 +477,85 @@
*/
public Dictionary load( String pid ) throws IOException
{
- return load( getFile( pid ) );
+ final File cfgFile = getFile( pid );
+
+ if ( System.getSecurityManager() != null )
+ {
+ return _privilegedLoad( cfgFile );
+ }
+
+ return _load( cfgFile );
+ }
+
+
+ private Dictionary _privilegedLoad( final File cfgFile ) throws IOException
+ {
+ try
+ {
+ Object result = AccessController.doPrivileged( new PrivilegedExceptionAction()
+ {
+ public Object run() throws IOException
+ {
+ return _load( cfgFile );
+ }
+ } );
+
+ return ( Dictionary ) result;
+ }
+ catch ( PrivilegedActionException pae )
+ {
+ throw ( IOException ) pae.getCause();
+ }
+ }
+
+
+ /**
+ * Loads the contents of the <code>cfgFile</code> into a new
+ * <code>Dictionary</code> object.
+ *
+ * @param cfgFile
+ * The file from which to load the data.
+ * @return A new <code>Dictionary</code> object providing the file contents.
+ * @throws java.io.FileNotFoundException
+ * If the given file does not exist.
+ * @throws IOException
+ * If an error occurrs reading the configuration file.
+ */
+ Dictionary _load( File cfgFile ) throws IOException
+ {
+ // this method is not part of the API of this class but is made
+ // package private to prevent the creation of a synthetic method
+ // for use by the DictionaryEnumeration._seek method
+
+ // synchronize this instance to make at least sure, the file is
+ // not at the same time accessed by another thread (see store())
+ // we have to synchronize the complete load time as the store
+ // method might want to replace the file while we are reading and
+ // still have the file open. This might be a problem e.g. in Windows
+ // environments, where files may not be removed which are still open
+ synchronized ( this )
+ {
+ InputStream ins = null;
+ try
+ {
+ ins = new FileInputStream( cfgFile );
+ return ConfigurationHandler.read( ins );
+ }
+ finally
+ {
+ if ( ins != null )
+ {
+ try
+ {
+ ins.close();
+ }
+ catch ( IOException ioe )
+ {
+ // ignore
+ }
+ }
+ }
+ }
}
@@ -421,7 +569,40 @@
*
* @throws IOException If an error occurrs writing the configuration data.
*/
- public void store( String pid, Dictionary props ) throws IOException
+ public void store( final String pid, final Dictionary props ) throws IOException
+ {
+ if ( System.getSecurityManager() != null )
+ {
+ _privilegedStore( pid, props );
+ }
+ else
+ {
+ _store( pid, props );
+ }
+ }
+
+
+ private void _privilegedStore( final String pid, final Dictionary props ) throws IOException
+ {
+ try
+ {
+ AccessController.doPrivileged( new PrivilegedExceptionAction()
+ {
+ public Object run() throws IOException
+ {
+ _store( pid, props );
+ return null;
+ }
+ } );
+ }
+ catch ( PrivilegedActionException pae )
+ {
+ throw ( IOException ) pae.getCause();
+ }
+ }
+
+
+ private void _store( final String pid, final Dictionary props ) throws IOException
{
OutputStream out = null;
File tmpFile = null;
@@ -480,51 +661,6 @@
/**
- * Loads the contents of the <code>cfgFile</code> into a new
- * <code>Dictionary</code> object.
- *
- * @param cfgFile The file from which to load the data.
- *
- * @return A new <code>Dictionary</code> object providing the file contents.
- *
- * @throws java.io.FileNotFoundException If the given file does not exist.
- * @throws IOException If an error occurrs reading the configuration file.
- */
- private Dictionary load( File cfgFile ) throws IOException
- {
- // synchronize this instance to make at least sure, the file is
- // not at the same time accessed by another thread (see store())
- // we have to synchronize the complete load time as the store
- // method might want to replace the file while we are reading and
- // still have the file open. This might be a problem e.g. in Windows
- // environments, where files may not be removed which are still open
- synchronized ( this )
- {
- InputStream ins = null;
- try
- {
- ins = new FileInputStream( cfgFile );
- return ConfigurationHandler.read( ins );
- }
- finally
- {
- if ( ins != null )
- {
- try
- {
- ins.close();
- }
- catch ( IOException ioe )
- {
- // ignore
- }
- }
- }
- }
- }
-
-
- /**
* Creates an abstract path name for the <code>pid</code> encoding it as
* follows:
* <ul>
@@ -539,8 +675,12 @@
*
* @return The abstract path name, which the parent directory path created.
*/
- private File getFile( String pid )
+ File getFile( String pid )
{
+ // this method is not part of the API of this class but is made
+ // package private to prevent the creation of a synthetic method
+ // for use by the DictionaryEnumeration._seek method
+
return new File( location, encodePid( pid ) + FILE_EXT );
}
@@ -553,7 +693,7 @@
* This enumeration loads configuration lazily with a look ahead of one
* dictionary.
*/
- private class DictionaryEnumeration implements Enumeration
+ class DictionaryEnumeration implements Enumeration
{
private Stack dirStack;
private File[] fileList;
@@ -567,7 +707,7 @@
fileList = null;
idx = 0;
- dirStack.push( location );
+ dirStack.push( getLocation() );
next = seek();
}
@@ -593,6 +733,30 @@
private Dictionary seek()
{
+ if ( System.getSecurityManager() != null )
+ {
+ return _privilegedSeek();
+ }
+
+ return _seek();
+ }
+
+
+ protected Dictionary _privilegedSeek()
+ {
+ Object result = AccessController.doPrivileged( new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return _seek();
+ }
+ } );
+ return ( Dictionary ) result;
+ }
+
+
+ protected Dictionary _seek()
+ {
while ( ( fileList != null && idx < fileList.length ) || !dirStack.isEmpty() )
{
if ( fileList == null || idx >= fileList.length )
@@ -609,7 +773,7 @@
{
try
{
- Dictionary dict = load( cfgFile );
+ Dictionary dict = _load( cfgFile );
// use the dictionary if it has no PID or the PID
// derived file name matches the source file name
@@ -635,4 +799,5 @@
return null;
}
}
+
}
diff --git a/configadmin/src/test/java/org/apache/felix/cm/integration/helper/FailureActivator.java b/configadmin/src/test/java/org/apache/felix/cm/integration/helper/FailureActivator.java
new file mode 100644
index 0000000..ed8a3d2
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/integration/helper/FailureActivator.java
@@ -0,0 +1,137 @@
+/*
+ * 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.felix.cm.integration.helper;
+
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+public class FailureActivator implements BundleActivator
+{
+ private static final String PID = "myPID";
+
+ private ServiceTracker tracker;
+
+ private ServiceRegistration factory;
+
+ private static String servicePid;
+
+ private ManagedServiceFactory m_factory = new ManagedServiceFactory()
+ {
+ private final Object m_lock = new Object();
+ private Exception e;
+
+
+ public void deleted( String pid )
+ {
+ System.out.println( "Deleted " + pid );
+ }
+
+
+ public String getName()
+ {
+ return "A testing factory";
+ }
+
+
+ public void updated( String pid, Dictionary dict ) throws ConfigurationException
+ {
+ synchronized ( m_lock )
+ {
+ System.out.println( this + " Updated " + pid + " with " + dict );
+ if ( e == null )
+ {
+ e = new Exception(pid);
+ }
+ else
+ {
+ System.out.println( "******************************" );
+ System.out.println( "******************************" );
+ System.out.println( "Error: updated more than once." );
+ System.out.println( "first:" );
+ e.printStackTrace( System.out );
+ System.out.println( "******************************" );
+ System.out.println( "second:" );
+ new Exception( pid ).printStackTrace( System.out );
+ System.out.println( "******************************" );
+ System.out.println( "******************************" );
+ }
+ }
+ }
+ };
+
+
+ public void start( final BundleContext context )
+ {
+ // Register our service factory
+ Properties props = new Properties();
+ props.put( Constants.SERVICE_PID, PID );
+ factory = context.registerService( ManagedServiceFactory.class.getName(), m_factory, props );
+
+ // Create a new thread to update the config admin (this way the
+ // activator can return)
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ FailureActivator.this.tracker = new ServiceTracker( context, context.createFilter( "(" + Constants.OBJECTCLASS
+ + "=" + ConfigurationAdmin.class.getName() + ")" ), null );
+ tracker.open();
+ ConfigurationAdmin configAdmin = ( ConfigurationAdmin ) tracker.waitForService( 5000 );
+ Configuration config = configAdmin.createFactoryConfiguration( PID, null );
+ servicePid = config.getPid();
+ Properties serviceProps = new Properties();
+ serviceProps.put( "key", "value" );
+ config.update( serviceProps );
+ }
+ catch ( Exception e )
+ {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ }
+
+
+ public void stop( BundleContext arg0 ) throws Exception
+ {
+ factory.unregister();
+
+ // remove the configuration again to clean up
+ ConfigurationAdmin configAdmin = ( ConfigurationAdmin ) tracker.waitForService( 5000 );
+ Configuration config = configAdmin.getConfiguration( servicePid, null );
+ config.delete();
+
+ tracker.close();
+ }
+}