| /* |
| * 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.framework; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.*; |
| |
| import org.apache.felix.framework.util.MapToDictionary; |
| import org.apache.felix.framework.util.StringMap; |
| import org.apache.felix.framework.util.Util; |
| import org.apache.felix.framework.wiring.BundleCapabilityImpl; |
| import org.osgi.framework.*; |
| import org.osgi.framework.BundleReference; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.framework.wiring.BundleWire; |
| |
| class ServiceRegistrationImpl implements ServiceRegistration |
| { |
| // Service registry. |
| private final ServiceRegistry m_registry; |
| // Bundle providing the service. |
| private final Bundle m_bundle; |
| // Interfaces associated with the service object. |
| private final String[] m_classes; |
| // Service Id associated with the service object. |
| private final Long m_serviceId; |
| // Service object. |
| private volatile Object m_svcObj; |
| // Service factory interface. |
| private volatile ServiceFactory m_factory; |
| // Associated property dictionary. |
| private volatile Map m_propMap = new StringMap(false); |
| // Re-usable service reference. |
| private final ServiceReferenceImpl m_ref; |
| // Flag indicating that we are unregistering. |
| private volatile boolean m_isUnregistering = false; |
| |
| public ServiceRegistrationImpl( |
| ServiceRegistry registry, Bundle bundle, |
| String[] classes, Long serviceId, |
| Object svcObj, Dictionary dict) |
| { |
| m_registry = registry; |
| m_bundle = bundle; |
| m_classes = classes; |
| m_serviceId = serviceId; |
| m_svcObj = svcObj; |
| m_factory = (m_svcObj instanceof ServiceFactory) |
| ? (ServiceFactory) m_svcObj : null; |
| |
| initializeProperties(dict); |
| |
| // This reference is the "standard" reference for this |
| // service and will always be returned by getReference(). |
| m_ref = new ServiceReferenceImpl(); |
| } |
| |
| protected synchronized boolean isValid() |
| { |
| return (m_svcObj != null); |
| } |
| |
| protected synchronized void invalidate() |
| { |
| m_svcObj = null; |
| } |
| |
| public synchronized ServiceReference getReference() |
| { |
| // Make sure registration is valid. |
| if (!isValid()) |
| { |
| throw new IllegalStateException( |
| "The service registration is no longer valid."); |
| } |
| return m_ref; |
| } |
| |
| public void setProperties(Dictionary dict) |
| { |
| Map oldProps; |
| synchronized (this) |
| { |
| // Make sure registration is valid. |
| if (!isValid()) |
| { |
| throw new IllegalStateException( |
| "The service registration is no longer valid."); |
| } |
| // Remember old properties. |
| oldProps = m_propMap; |
| // Set the properties. |
| initializeProperties(dict); |
| } |
| // Tell registry about it. |
| m_registry.servicePropertiesModified(this, new MapToDictionary(oldProps)); |
| } |
| |
| public void unregister() |
| { |
| synchronized (this) |
| { |
| if (!isValid() || m_isUnregistering) |
| { |
| throw new IllegalStateException("Service already unregistered."); |
| } |
| m_isUnregistering = true; |
| } |
| m_registry.unregisterService(m_bundle, this); |
| synchronized (this) |
| { |
| m_svcObj = null; |
| m_factory = null; |
| } |
| } |
| |
| // |
| // Utility methods. |
| // |
| |
| /** |
| * This method determines if the class loader of the service object |
| * has access to the specified class. |
| * @param clazz the class to test for reachability. |
| * @return <tt>true</tt> if the specified class is reachable from the |
| * service object's class loader, <tt>false</tt> otherwise. |
| **/ |
| private boolean isClassAccessible(Class clazz) |
| { |
| // We need to see if the class loader of the service object has |
| // access to the specified class; however, we may not have a service |
| // object. If we only have service factory, then we will assume two |
| // different scenarios: |
| // 1. The service factory is provided by the bundle providing the |
| // service. |
| // 2. The service factory is NOT provided by the bundle providing |
| // the service. |
| // For case 1, we will use the class loader of the service factory |
| // to find the class. For case 2, we will assume we have an extender |
| // at work here and always return true, since we have no real way of |
| // knowing the wiring of the provider unless we actually get the |
| // service object, which defeats the lazy aspect of service factories. |
| |
| // Case 2. |
| if ((m_factory != null) |
| && (m_factory.getClass().getClassLoader() instanceof BundleReference) |
| && !((BundleReference) m_factory.getClass() |
| .getClassLoader()).getBundle().equals(m_bundle)) |
| { |
| return true; |
| } |
| |
| // Case 1. |
| Class sourceClass = (m_factory != null) ? m_factory.getClass() : m_svcObj.getClass(); |
| return Util.loadClassUsingClass(sourceClass, clazz.getName(), Felix.m_secureAction) == clazz; |
| } |
| |
| Object getProperty(String key) |
| { |
| return m_propMap.get(key); |
| } |
| |
| private String[] getPropertyKeys() |
| { |
| Set s = m_propMap.keySet(); |
| return (String[]) s.toArray(new String[s.size()]); |
| } |
| |
| private Bundle[] getUsingBundles() |
| { |
| return m_registry.getUsingBundles(m_ref); |
| } |
| |
| /** |
| * This method provides direct access to the associated service object; |
| * it generally should not be used by anyone other than the service registry |
| * itself. |
| * @return The service object associated with the registration. |
| **/ |
| Object getService() |
| { |
| return m_svcObj; |
| } |
| |
| Object getService(Bundle acqBundle) |
| { |
| // If the service object is a service factory, then |
| // let it create the service object. |
| if (m_factory != null) |
| { |
| Object svcObj = null; |
| try |
| { |
| if (System.getSecurityManager() != null) |
| { |
| svcObj = AccessController.doPrivileged( |
| new ServiceFactoryPrivileged(acqBundle, null)); |
| } |
| else |
| { |
| svcObj = getFactoryUnchecked(acqBundle); |
| } |
| } |
| catch (PrivilegedActionException ex) |
| { |
| if (ex.getException() instanceof ServiceException) |
| { |
| throw (ServiceException) ex.getException(); |
| } |
| else |
| { |
| throw new ServiceException( |
| "Service factory exception: " + ex.getException().getMessage(), |
| ServiceException.FACTORY_EXCEPTION, ex.getException()); |
| } |
| } |
| return svcObj; |
| } |
| else |
| { |
| return m_svcObj; |
| } |
| } |
| |
| void ungetService(Bundle relBundle, Object svcObj) |
| { |
| // If the service object is a service factory, then |
| // let it release the service object. |
| if (m_factory != null) |
| { |
| try |
| { |
| if (System.getSecurityManager() != null) |
| { |
| AccessController.doPrivileged( |
| new ServiceFactoryPrivileged(relBundle, svcObj)); |
| } |
| else |
| { |
| ungetFactoryUnchecked(relBundle, svcObj); |
| } |
| } |
| catch (Exception ex) |
| { |
| m_registry.getLogger().log( |
| m_bundle, |
| Logger.LOG_ERROR, |
| "ServiceRegistrationImpl: Error ungetting service.", |
| ex); |
| } |
| } |
| } |
| |
| private void initializeProperties(Dictionary dict) |
| { |
| // Create a case-insensitive map for the properties. |
| Map props = new StringMap(false); |
| |
| if (dict != null) |
| { |
| // Make sure there are no duplicate keys. |
| Enumeration keys = dict.keys(); |
| while (keys.hasMoreElements()) |
| { |
| Object key = keys.nextElement(); |
| if (props.get(key) == null) |
| { |
| props.put(key, dict.get(key)); |
| } |
| else |
| { |
| throw new IllegalArgumentException("Duplicate service property: " + key); |
| } |
| } |
| } |
| |
| // Add the framework assigned properties. |
| props.put(Constants.OBJECTCLASS, m_classes); |
| props.put(Constants.SERVICE_ID, m_serviceId); |
| |
| // Update the service property map. |
| m_propMap = props; |
| } |
| |
| private Object getFactoryUnchecked(Bundle bundle) |
| { |
| Object svcObj = null; |
| try |
| { |
| svcObj = m_factory.getService(bundle, this); |
| } |
| catch (Throwable th) |
| { |
| throw new ServiceException( |
| "Service factory exception: " + th.getMessage(), |
| ServiceException.FACTORY_EXCEPTION, th); |
| } |
| if (svcObj != null) |
| { |
| for (int i = 0; i < m_classes.length; i++) |
| { |
| Class clazz = Util.loadClassUsingClass( |
| svcObj.getClass(), m_classes[i], Felix.m_secureAction); |
| if ((clazz == null) || !clazz.isAssignableFrom(svcObj.getClass())) |
| { |
| if (clazz == null) |
| { |
| throw new ServiceException( |
| "Service cannot be cast due to missing class: " + m_classes[i], |
| ServiceException.FACTORY_ERROR); |
| } |
| else |
| { |
| throw new ServiceException( |
| "Service cannot be cast: " + m_classes[i], |
| ServiceException.FACTORY_ERROR); |
| } |
| } |
| } |
| } |
| else |
| { |
| throw new ServiceException( |
| "Service factory returned null.", ServiceException.FACTORY_ERROR); |
| } |
| return svcObj; |
| } |
| |
| private void ungetFactoryUnchecked(Bundle bundle, Object svcObj) |
| { |
| m_factory.ungetService(bundle, this, svcObj); |
| } |
| |
| /** |
| * This simple class is used to ensure that when a service factory |
| * is called, that no other classes on the call stack interferes |
| * with the permissions of the factory itself. |
| **/ |
| private class ServiceFactoryPrivileged implements PrivilegedExceptionAction |
| { |
| private Bundle m_bundle = null; |
| private Object m_svcObj = null; |
| |
| public ServiceFactoryPrivileged(Bundle bundle, Object svcObj) |
| { |
| m_bundle = bundle; |
| m_svcObj = svcObj; |
| } |
| |
| public Object run() throws Exception |
| { |
| if (m_svcObj == null) |
| { |
| return getFactoryUnchecked(m_bundle); |
| } |
| else |
| { |
| ungetFactoryUnchecked(m_bundle, m_svcObj); |
| } |
| return null; |
| } |
| } |
| |
| // |
| // ServiceReference implementation |
| // |
| |
| class ServiceReferenceImpl extends BundleCapabilityImpl implements ServiceReference |
| { |
| private final ServiceReferenceMap m_map; |
| |
| private ServiceReferenceImpl() |
| { |
| super(null, null, Collections.EMPTY_MAP, Collections.EMPTY_MAP); |
| m_map = new ServiceReferenceMap(); |
| } |
| |
| ServiceRegistrationImpl getRegistration() |
| { |
| return ServiceRegistrationImpl.this; |
| } |
| |
| // |
| // Capability methods. |
| // |
| |
| @Override |
| public BundleRevision getRevision() |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public String getNamespace() |
| { |
| return "service-reference"; |
| } |
| |
| @Override |
| public Map<String, String> getDirectives() |
| { |
| return Collections.EMPTY_MAP; |
| } |
| |
| @Override |
| public Map<String, Object> getAttributes() |
| { |
| return m_map; |
| } |
| |
| @Override |
| public List<String> getUses() |
| { |
| return Collections.EMPTY_LIST; |
| } |
| |
| // |
| // ServiceReference methods. |
| // |
| |
| public Object getProperty(String s) |
| { |
| return ServiceRegistrationImpl.this.getProperty(s); |
| } |
| |
| public String[] getPropertyKeys() |
| { |
| return ServiceRegistrationImpl.this.getPropertyKeys(); |
| } |
| |
| public Bundle getBundle() |
| { |
| // The spec says that this should return null if |
| // the service is unregistered. |
| return (isValid()) ? m_bundle : null; |
| } |
| |
| public Bundle[] getUsingBundles() |
| { |
| return ServiceRegistrationImpl.this.getUsingBundles(); |
| } |
| |
| public String toString() |
| { |
| String[] ocs = (String[]) getProperty("objectClass"); |
| String oc = "["; |
| for(int i = 0; i < ocs.length; i++) |
| { |
| oc = oc + ocs[i]; |
| if (i < ocs.length - 1) |
| oc = oc + ", "; |
| } |
| oc = oc + "]"; |
| return oc; |
| } |
| |
| public boolean isAssignableTo(Bundle requester, String className) |
| { |
| // Always return true if the requester is the same as the provider. |
| if (requester == m_bundle) |
| { |
| return true; |
| } |
| |
| // Boolean flag. |
| boolean allow = true; |
| // Get the package. |
| String pkgName = |
| Util.getClassPackage(className); |
| BundleRevision requesterRevision = ((BundleImpl) requester).getCurrentRevision(); |
| // Get package wiring from service requester. |
| BundleWire requesterWire = Util.getWire(requesterRevision, pkgName); |
| // Get package wiring from service provider. |
| BundleRevision providerRevision = ((BundleImpl) m_bundle).getCurrentRevision(); |
| BundleWire providerWire = Util.getWire(providerRevision, pkgName); |
| |
| // There are four situations that may occur here: |
| // 1. Neither the requester, nor provider have wires for the package. |
| // 2. The requester does not have a wire for the package. |
| // 3. The provider does not have a wire for the package. |
| // 4. Both the requester and provider have a wire for the package. |
| // For case 1, if the requester does not have access to the class at |
| // all, we assume it is using reflection and do not filter. If the |
| // requester does have access to the class, then we make sure it is |
| // the same class as the service. For case 2, we do not filter if the |
| // requester is the exporter of the package to which the provider of |
| // the service is wired. Otherwise, as in case 1, if the requester |
| // does not have access to the class at all, we do not filter, but if |
| // it does have access we check if it is the same class accessible to |
| // the providing revision. For case 3, the provider will not have a wire |
| // if it is exporting the package, so we determine if the requester |
| // is wired to it or somehow using the same class. For case 4, we |
| // simply compare the exporting revisions from the package wiring to |
| // determine if we need to filter the service reference. |
| |
| // Case 1: Both requester and provider have no wire. |
| if ((requesterWire == null) && (providerWire == null)) |
| { |
| // If requester has no access then true, otherwise service |
| // registration must have same class as requester. |
| try |
| { |
| Class requestClass = |
| ((BundleWiringImpl) requesterRevision.getWiring()) |
| .getClassByDelegation(className); |
| allow = getRegistration().isClassAccessible(requestClass); |
| } |
| catch (Exception ex) |
| { |
| // Requester has no access to the class, so allow it, since |
| // we assume the requester is using reflection. |
| allow = true; |
| } |
| } |
| // Case 2: Requester has no wire, but provider does. |
| else if ((requesterWire == null) && (providerWire != null)) |
| { |
| // Allow if the requester is the exporter of the provider's wire. |
| if (providerWire.getProviderWiring().getRevision().equals(requesterRevision)) |
| { |
| allow = true; |
| } |
| // Otherwise, check if the requester has access to the class and, |
| // if so, if it is the same class as the provider. |
| else |
| { |
| try |
| { |
| // Try to load class from requester. |
| Class requestClass =((BundleWiringImpl) |
| requesterRevision.getWiring()).getClassByDelegation(className); |
| try |
| { |
| // If requester has access to the class, verify it is the |
| // same class as the provider. |
| allow = (((BundleWiringImpl) |
| providerRevision.getWiring()) |
| .getClassByDelegation(className) == requestClass); |
| } |
| catch (Exception ex) |
| { |
| allow = false; |
| } |
| } |
| catch (Exception ex) |
| { |
| // Requester has no access to the class, so allow it, since |
| // we assume the requester is using reflection. |
| allow = true; |
| } |
| } |
| } |
| // Case 3: Requester has a wire, but provider does not. |
| else if ((requesterWire != null) && (providerWire == null)) |
| { |
| // If the provider is the exporter of the requester's package, then check |
| // if the requester is wired to the latest version of the provider, if so |
| // then allow else don't (the provider has been updated but not refreshed). |
| if (((BundleImpl) m_bundle).hasRevision( |
| requesterWire.getProviderWiring().getRevision())) |
| { |
| allow = providerRevision.equals(requesterWire.getProviderWiring().getRevision()); |
| } |
| // If the provider is not the exporter of the requester's package, |
| // then try to use the service registration to see if the requester's |
| // class is accessible. |
| else |
| { |
| try |
| { |
| // Load the class from the requesting bundle. |
| Class requestClass = ((BundleWiringImpl) |
| requesterRevision.getWiring()) |
| .getClassByDelegation(className); |
| // Get the service registration and ask it to check |
| // if the service object is assignable to the requesting |
| // bundle's class. |
| allow = getRegistration().isClassAccessible(requestClass); |
| } |
| catch (Exception ex) |
| { |
| // Filter to be safe. |
| allow = false; |
| } |
| } |
| } |
| // Case 4: Both requester and provider have a wire. |
| else |
| { |
| // Include service reference if the wires have the |
| // same source revision. |
| allow = providerWire.getProviderWiring().getRevision() |
| .equals(requesterWire.getProviderWiring().getRevision()); |
| } |
| |
| return allow; |
| } |
| |
| public int compareTo(Object reference) |
| { |
| ServiceReference other = (ServiceReference) reference; |
| |
| Long id = (Long) getProperty(Constants.SERVICE_ID); |
| Long otherId = (Long) other.getProperty(Constants.SERVICE_ID); |
| |
| if (id.equals(otherId)) |
| { |
| return 0; // same service |
| } |
| |
| Object rankObj = getProperty(Constants.SERVICE_RANKING); |
| Object otherRankObj = other.getProperty(Constants.SERVICE_RANKING); |
| |
| // If no rank, then spec says it defaults to zero. |
| rankObj = (rankObj == null) ? new Integer(0) : rankObj; |
| otherRankObj = (otherRankObj == null) ? new Integer(0) : otherRankObj; |
| |
| // If rank is not Integer, then spec says it defaults to zero. |
| Integer rank = (rankObj instanceof Integer) |
| ? (Integer) rankObj : new Integer(0); |
| Integer otherRank = (otherRankObj instanceof Integer) |
| ? (Integer) otherRankObj : new Integer(0); |
| |
| // Sort by rank in ascending order. |
| if (rank.compareTo(otherRank) < 0) |
| { |
| return -1; // lower rank |
| } |
| else if (rank.compareTo(otherRank) > 0) |
| { |
| return 1; // higher rank |
| } |
| |
| // If ranks are equal, then sort by service id in descending order. |
| return (id.compareTo(otherId) < 0) ? 1 : -1; |
| } |
| } |
| |
| private class ServiceReferenceMap implements Map |
| { |
| public int size() |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public boolean isEmpty() |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public boolean containsKey(Object o) |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public boolean containsValue(Object o) |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public Object get(Object o) |
| { |
| return ServiceRegistrationImpl.this.getProperty((String) o); |
| } |
| |
| public Object put(Object k, Object v) |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public Object remove(Object o) |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public void putAll(Map map) |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public void clear() |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public Set<Object> keySet() |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public Collection<Object> values() |
| { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public Set<Entry<Object, Object>> entrySet() |
| { |
| return Collections.EMPTY_SET; |
| } |
| } |
| } |