blob: b79323e2ca1713b06f6a03451dcb178f0c83a558 [file] [log] [blame]
/*
* 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.status.impl.webconsole;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
/**
* The ResourceBundleManager manages resource bundle instance per OSGi Bundle.
* It contains a local cache, for bundles, but when a bundle is being unistalled,
* its resources stored in the cache are cleaned up.
*/
public class ResourceBundleManager implements BundleListener
{
private final BundleContext bundleContext;
private final Map<Long, ResourceBundle> resourceBundleCaches;
/**
* Creates a new object and adds self as a bundle listener
*
* @param bundleContext the bundle context of the Web Console.
*/
public ResourceBundleManager( final BundleContext bundleContext )
{
this.bundleContext = bundleContext;
this.resourceBundleCaches = new HashMap<Long, ResourceBundle>();
bundleContext.addBundleListener( this );
}
/**
* Removes the bundle lister.
*/
public void dispose()
{
bundleContext.removeBundleListener( this );
}
/**
* This method is used to retrieve a /cached/ instance of the i18n resource associated
* with a given bundle.
*
* @param provider the bundle, provider of the resources
* @param locale the requested locale.
*/
public ResourceBundle getResourceBundle( final Bundle provider ) {
ResourceBundle cache;
final Long key = new Long( provider.getBundleId() );
synchronized ( resourceBundleCaches ) {
cache = resourceBundleCaches.get( key );
if ( cache == null && !resourceBundleCaches.containsKey(key)) {
cache = this.loadResourceBundle(provider);
resourceBundleCaches.put( key, cache );
}
}
return cache;
}
// ---------- BundleListener
/**
* @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
*/
public final void bundleChanged( BundleEvent event )
{
if ( event.getType() == BundleEvent.STOPPED )
{
final Long key = new Long( event.getBundle().getBundleId() );
synchronized ( resourceBundleCaches )
{
resourceBundleCaches.remove( key );
}
}
}
private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
private ResourceBundle loadResourceBundle(final Bundle bundle) {
final String path = "_" + DEFAULT_LOCALE.toString(); //$NON-NLS-1$
final URL source = ( URL ) getResourceBundleEntries(bundle).get( path );
if ( source != null ) {
try {
return new PropertyResourceBundle( source.openStream() );
} catch ( final IOException ignore ) {
// ignore
}
}
return null;
}
// TODO : Instead of getting all property files, we could just get the one for the default locale
private synchronized Map getResourceBundleEntries(final Bundle bundle)
{
String file = ( String ) bundle.getHeaders().get( Constants.BUNDLE_LOCALIZATION );
if ( file == null )
{
file = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
}
// remove leading slash
if ( file.startsWith( "/" ) ) //$NON-NLS-1$
{
file = file.substring( 1 );
}
// split path and base name
int slash = file.lastIndexOf( '/' );
String fileName = file.substring( slash + 1 );
String path = ( slash <= 0 ) ? "/" : file.substring( 0, slash ); //$NON-NLS-1$
HashMap resourceBundleEntries = new HashMap();
Enumeration locales = bundle.findEntries( path, fileName + "*.properties", false ); //$NON-NLS-1$
if ( locales != null )
{
while ( locales.hasMoreElements() )
{
URL entry = ( URL ) locales.nextElement();
// calculate the key
String entryPath = entry.getPath();
final int start = entryPath.lastIndexOf( '/' ) + 1 + fileName.length(); // path, slash and base name
final int end = entryPath.length() - 11; // .properties suffix
entryPath = entryPath.substring( start, end );
// the default language is "name.properties" thus the entry
// path is empty and must default to "_"+DEFAULT_LOCALE
if (entryPath.length() == 0) {
entryPath = "_" + DEFAULT_LOCALE; //$NON-NLS-1$
}
// only add this entry, if the "language" is not provided
// by the main bundle or an earlier bound fragment
if (!resourceBundleEntries.containsKey( entryPath )) {
resourceBundleEntries.put( entryPath, entry );
}
}
}
return resourceBundleEntries;
}
}