# OSGi service proxy ![](https://i.imgur.com/jicpvVz.png =500x) > The OSGi Alliance, "[OSGi Core Release 6](https://docs.osgi.org/download/r6/osgi.core-6.0.0.pdf)", June 2014, pp 385-387. > [OSGi Core Release 7 (55.3.1 Proxying)](https://docs.osgi.org/specification/osgi.core/7.0.0/framework.servicehooks.html#d0e45668) ###### tags: `張宸翊` `OM2M` ## ServiceTracker 功能 : 對服務做追蹤,例如服務何時被註冊、何時被移除、何時被修改等。 * ```addingService``` 服務註冊時 * ```modifiedService``` 服務修改時 * ```removedService``` 服務移除時 Example ([完整程式碼link](https://hackmd.io/G2fwvsJ8Q76-HSEdfJKtPA#Activatorjava)) ```java= ServiceTracker<Object, Object>(bundleContext, CseService.class.getName(), null) { public void removedService(ServiceReference<Object> reference, Object service) { try { adapter.setCse(null); } catch (IllegalArgumentException e) { System.out.println("Error removing SclService"); } } public Object addingService(ServiceReference<Object> reference) { CseService cse = (CseService) this.context.getService(reference); try { adapter.setCse(cse); } catch (Exception e) { System.out.println("Error adding SclService"); } return cse; } }; ``` ## OSGi service hook A kind of OSGi service. * ```EventListenerHook``` 服務註冊、移除、修改時觸發 * ```FindHook``` 服務請求時觸發 * ```ListenerHook``` 服務監聽增加或刪除時觸發 > [參考連結](https://docs.osgi.org/specification/osgi.core/7.0.0/framework.servicehooks.html) ## Proxying Proxying an existing service for a specific bundle requires the following steps: * Hide the existing service $X$ * Hiding service X can be implemented with a combination of the **Event Listener Hook** and the **Find Hook**. * Register a proxy $X'$ with the same properties as $X$ 1. **Event Listener Hook** : It can be used to hide any service events from the target bundle. 2. **Find Hook** : It can be used to remove $X$ from the delivered results of the ```getServiceReference(s)``` methods. * remove only * The **Event Listener Hook** and **Find Hook** both allow the interceptor to remove elements from a collection and not add elements. Bundle A directly uses Service $X$, in the proxying case, the Proxy Bundle hides the original and provides an alternative. ![](https://i.imgur.com/cCQ6v5i.png) ## 範例程式 ### 開發環境 | | version | | ---- |:------------------------- | | OS | windows 10 2004 19041.264 | | JAVA | 1.8.0_251 | | IDE | Eclipse 2019-06 (4.12.0) | ### 說明 :::info 目前有一個Bundle B,提供了一個Service X給Bundle A 我們希望原始Service X不要讓Bundle A拿到,而是取得Service X'(實際名稱與Service X一樣,用X'只是比較好理解) ::: ### 1. Create a Plug-in Project 先建立一個新的project 「File → New → Project...」 ![](https://i.imgur.com/77WxOsQ.png =600x) 接著選「Plug-in Development → Plug-in Project → Next」 ![](https://i.imgur.com/s0y7a61.png =600x) ![](https://i.imgur.com/mcPUTrZ.png =600x) ![](https://i.imgur.com/cX0GnNW.png =600x) * 重複以上步驟,Project name填入不同的名稱,產生三個project (ServiceProxy_TEST、BundleA、BundleB) ![](https://i.imgur.com/1WiNQoV.png) ### 2-1. BundleA ![](https://i.imgur.com/TK8m74L.png) :::info Bundle A 希望取得 ServiceX 來用 ::: :::spoiler BundleA > Activator.java ```java= package bundlea; import bundleb.ServiceX; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; public class Activator implements BundleActivator { private static BundleContext context; static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { System.out.println("Bundle A start"); Activator.context = bundleContext; ServiceReference<?> reference = context.getServiceReference(ServiceX.class.getName()); if(null != reference){ ServiceX service = (ServiceX) context.getService(reference); if(null != service){ System.out.println("Services From "+service.SayHello("Bundle A")); } } else { System.out.println(bundleContext.getBundle().getSymbolicName()+" 無法取得服務"); } } public void stop(BundleContext bundleContext) throws Exception { Activator.context = null; } } ``` ::: :::spoiler BundleA > MANIFEST.MF ```= Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: BundleA Bundle-SymbolicName: BundleA Bundle-Version: 1.0.0.qualifier Bundle-Activator: bundlea.Activator Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Automatic-Module-Name: BundleA Import-Package: bundleb, org.osgi.framework;version="1.3.0", org.osgi.util.tracker;version="1.5.2" Bundle-ActivationPolicy: lazy ``` ::: ### 2-2. BundleB ![](https://i.imgur.com/6XDMKtN.png) * bundleb右鍵 → New → File → File name : HelloNCKU.java → Finish * bundleb右鍵 → New → File → File name : ServiceX.java → Finish :::info Bundle B 提供了一個 Service X (HelloNCKU.java),我們立用 2-3 中的 FindHook 將它取得的 Service X (HelloNCKU.java),改為 Service X (HelloDCNLab.java) ::: :::spoiler BundleB > Activator.java ```java= package bundleb; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; public class Activator implements BundleActivator { private static BundleContext context; static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; ServiceRegistration<?> reference = bundleContext.registerService(ServiceX.class, new HelloNCKU(), null); System.out.println("Bundle B 提供一個 Service X"); System.out.println(reference); } public void stop(BundleContext bundleContext) throws Exception { Activator.context = null; } } ``` ::: :::spoiler BundleB > HelloNCKU.java ```java= package bundleb; public class HelloNCKU implements ServiceX{ @Override public String SayHello(String name) { // TODO Auto-generated method stub System.out.printf("Helle NCKU %s\n",name); return this.getClass().getName(); } } ``` ::: :::spoiler BundleB > ServiceX.java ```java= package bundleb; public interface ServiceX { public String SayHello(String name); } ``` ::: :::spoiler BundleB > MANIFEST.MF ```= Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: BundleB Bundle-SymbolicName: BundleB Bundle-Version: 1.0.0.qualifier Bundle-Activator: bundleb.Activator Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Automatic-Module-Name: BundleB Import-Package: org.osgi.framework;version="1.3.0", org.osgi.util.tracker;version="1.5.2" Bundle-ActivationPolicy: lazy Export-Package: bundleb ``` ::: ### 2-3. ServiceProxy_TEST ![](https://i.imgur.com/EXARlhQ.png) * serviceproxy_test右鍵 → New → File → File name : HelloDCNLab.java → Finish * serviceproxy_test右鍵 → New → File → File name : ProxyServiceX.java → Finish :::info 當 Bundle B 希望取得 Service X 時,find會被呼叫,在這個時候,將原始的 Service X (HelloNCKU.java) 移除,Bundle B 就無法取得原本的 Service X (HelloNCKU.java),改取得 Service X (HelloDCNLab.java) ::: :::spoiler ServiceProxy_TEST > Activator.java ```java= package serviceproxy_test; import java.util.Dictionary; import java.util.Hashtable; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import bundleb.ServiceX; public class Activator implements BundleActivator { private static BundleContext context; static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; HelloDCNLab h = new HelloDCNLab(); Dictionary<String,Object>properties = new Hashtable<String,Object>(); properties.put("proxied","true"); ServiceRegistration<?> reference = bundleContext.registerService(ServiceX.class, h, properties); System.out.println("ServiceProxy_TEST 提供一個 Service X"); System.out.println(reference); ProxyServiceX PS = new ProxyServiceX(context, "BundleA", "BundleB"); PS.open(); } public void stop(BundleContext bundleContext) throws Exception { Activator.context = null; } } ``` ::: :::spoiler ServiceProxy_TEST > HelloDCNLab.java ```java= package serviceproxy_test; import bundleb.ServiceX; public class HelloDCNLab implements ServiceX{ @Override public String SayHello(String name) { // TODO Auto-generated method stub System.out.printf("Helle DCNLab %s\n",name); return this.getClass().getName(); } } ``` ::: :::spoiler ServiceProxy_TEST > ProxyServiceX.java ```java= package serviceproxy_test; import java.util.Collection; import java.util.Iterator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.hooks.service.FindHook; public class ProxyServiceX implements FindHook { final BundleContext context; final String FindBundleName, removeFrom; public ProxyServiceX(BundleContext context, String FindBundleName, String removeFrom) { this.context = context; this.FindBundleName = FindBundleName; this.removeFrom = removeFrom; } public void open() { // TODO Auto-generated method stub context.registerService(new String[]{FindHook.class.getName()},this,null); } /* * find 是 FindHook需要實作的function,當有其他bundle使用getServiceReference,find會被osgi呼叫 * Once registered, these services will receive their event callbacks. * In the find hook, the target Service Reference is removed from the results * if the bundle that called the getServiceReference(s) method is the target bundle. */ @Override public void find(BundleContext Context, String arg1, String arg2, boolean arg3, Collection<ServiceReference<?>> references) { // TODO Auto-generated method stub if(Context.getBundle().getSymbolicName().equals(FindBundleName)) { System.out.println("============= [FindHook find] =============START"); Iterator<ServiceReference<?>> iterator = references.iterator(); while (iterator.hasNext()) { ServiceReference<?> sr = (ServiceReference<?>) iterator.next(); System.out.println("[find] "+FindBundleName +" getService : "+ sr); System.out.println("This service is from : "+sr.getBundle().getSymbolicName()); if (sr.getBundle().getSymbolicName().equals(removeFrom)) { iterator.remove(); System.out.println("remove " + sr); } else { System.out.println("do not remove " + sr); } } System.out.println("============= [FindHook find] =============END"); } } } ``` ::: :::spoiler ServiceProxy_TEST > MANIFEST.MF ```= Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: ServiceProxy_TEST Bundle-SymbolicName: ServiceProxy_TEST Bundle-Version: 1.0.0.qualifier Bundle-Activator: serviceproxy_test.Activator Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Automatic-Module-Name: ServiceProxy_TEST Import-Package: bundleb, org.osgi.framework;version="1.3.0", org.osgi.framework.hooks.service;version="1.1.0" Bundle-ActivationPolicy: lazy ``` ::: ### 3-1. 測試執行 1. Run → RunConfigurations ![](https://i.imgur.com/51msKkI.png) 2. 先 Deselect All → 然後將剛剛新增的那三個bundle打勾 → Add Required Bundles → 設定Start Level 3. 將 org.eclipse.equinox.console、org.apache.felix.gogo.shell 也打勾 4. 最後 Apply → Run ![](https://i.imgur.com/64prkiX.png) 5. Result ![](https://i.imgur.com/dJB91Lh.png) ### 3-2. 測試執行 - 不使用Service Proxy 6. 將ServiceProxy_TEST > Activator.java line 29 註解 ![](https://i.imgur.com/Or2u5au.png) 7. (如果有做3-1 step1~4) Run → Run As → OSGi Framework 8. Result ![](https://i.imgur.com/KC3627C.png) --- [範例程式碼 2020/06/10](https://drive.google.com/file/d/1h0DSCqX7lag8Z07IDgPNEBFd8sbf8xoM/view?usp=sharing) > 補充參考資料 [OSGi 系列(七)之服务的监听、跟踪、声明等](https://www.cnblogs.com/binarylei/p/8542060.html) [OSGi Service Hook to Log All Service Invocations Using Dynamic Proxy](https://dzone.com/articles/osgi-service-hook-log-all) [OSGi Core Release 7 (55.3.1 Proxying)](https://docs.osgi.org/specification/osgi.core/7.0.0/framework.servicehooks.html#d0e45668)