/**
* Copyright 2015-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed 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 net.roboconf.dm.internal.api.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import net.roboconf.core.model.runtime.Preference;
import net.roboconf.core.model.runtime.Preference.PreferenceKeyCategory;
import net.roboconf.core.utils.Utils;
import net.roboconf.dm.management.api.IPreferencesMngr;
/**
* Unlike other APIs, this one is managed by iPojo.
* <p>
* This allows administrators to define preferences through usual
* CFG files. It also provides a way for the REST API to access and retrieve preferences.
* </p>
*
* @author Vincent Zurczak - Linagora
*/
public class PreferencesMngrImpl implements IPreferencesMngr {
// Constants
static final String PID = "net.roboconf.dm.preferences";
static final Defaults DEFAULTS = new Defaults();
// Injected by iPojo
private ConfigurationAdmin configAdmin;
// Internal fields
private final Logger logger = Logger.getLogger( getClass().getName());
private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<> ();
/**
* Constructor.
*/
public PreferencesMngrImpl() {
this.cache.putAll( DEFAULTS.keyToDefaultValue );
}
/**
* Invoked by iPojo when one or several properties were updated from Config Admin.
* @param properties
*/
@Override
public void updateProperties( Dictionary<?,?> properties ) {
// Ignore iPojo properties
final List<String> propertiesToSkip = Arrays.asList(
"component",
"felix.fileinstall.filename" );
Map<String,String> map = new HashMap<> ();
for( Enumeration<?> en = properties.keys(); en.hasMoreElements(); ) {
Object key = en.nextElement();
String keyAsString = String.valueOf( key );
if( propertiesToSkip.contains( keyAsString ))
continue;
// "null" are not acceptable values in dictionaries
// (OSGi often use Hash tables)
Object value = properties.get( key );
map.put( keyAsString, String.valueOf( value ));
}
this.cache.clear();
this.cache.putAll( map );
this.logger.fine( "Preferences were updated in bulk mode." );
}
/**
* Invoked by iPojo when the component is started.
*/
public void start() {
this.logger.info( "The DM preferences were started." );
}
/**
* Invoked by iPojo when the component is stopped.
*/
public void stop() {
this.logger.info( "The DM preferences were stopped." );
}
/**
* @param configAdmin the configAdmin to set
*/
public void setConfigAdmin( ConfigurationAdmin configAdmin ) {
this.configAdmin = configAdmin;
}
@Override
public String get( String key ) {
return this.cache.get( key );
}
@Override
public String get( String key, String defaultValue ) {
return this.cache.containsKey( key ) ? this.cache.get( key ) : defaultValue;
}
@Override
@SuppressWarnings( "unchecked" )
public void save( String key, String value ) throws IOException {
// Log
this.logger.fine( "Preference with key '" + key + "' is being updated." );
this.logger.finest( "New preference value: " + key + " = " + value );
// Update the cache right away
String realValue = value == null ? "" : value;
this.cache.put( key, realValue );
// Update the config admin property.
// This will invoke (indirectly) the "updateProperties" method.
// It will simply update the cache, it does not cost a lot.
if( this.configAdmin != null ) {
Configuration config = this.configAdmin.getConfiguration( PID, null );
// Do NOT be too smart here.
// Felix's implementation returns a new object every time
// one invoke "config.getProperties()". So, we do not have a direct
// access to the properties and "config.update()" does not work.
//
// So, we store the result of "config.getProperties()" in a variable.
// We update this temporary map. And then, we pass the map as a parameter
// to "config.update()". Manual tests with Karaf have shown it does not work
// otherwise.
Dictionary<Object,Object> props = config.getProperties();
props.put( key, realValue );
config.update( props );
} else {
this.logger.warning( "Config Admin is not available." );
}
// We need to mix both approaches here so that this API works in
// both OSGi and non-OSGi environments.
}
@Override
public void addToList( String key, String value ) throws IOException {
Collection<String> values = getPreferencesAsCollection( key );
values.add( value );
save( key, Utils.format( values, ", " ));
}
@Override
public void removeFromList( String key, String value ) throws IOException {
Collection<String> values = getPreferencesAsCollection( key );
values.remove( value );
save( key, Utils.format( values, ", " ));
}
@Override
public Collection<String> getAsCollection( String key ) {
return getPreferencesAsCollection( key );
}
@Override
public String delete( String key ) {
return this.cache.remove( key );
}
@Override
public Properties getJavaxMailProperties() {
Properties result = new Properties();
for( Map.Entry<String,String> entry : this.cache.entrySet()) {
if( ! entry.getKey().startsWith( "mail." ))
continue;
result.setProperty( entry.getKey(), entry.getValue());
}
return result;
}
@Override
public List<Preference> getAllPreferences() {
List<Preference> result = new ArrayList<> ();
for( Map.Entry<String,String> entry : this.cache.entrySet()) {
PreferenceKeyCategory category = DEFAULTS.keyToCategory.get( entry.getKey());
Preference p = new Preference( entry.getKey(), entry.getValue(), category );
result.add( p );
}
return result;
}
private Set<String> getPreferencesAsCollection( String key ) {
String listAsString = get( key, "" );
List<String> list = Utils.filterEmptyValues( Utils.splitNicely( listAsString, "," ));
return new LinkedHashSet<>( list );
}
}