# OSGi service proxy

> 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.

## 範例程式
### 開發環境
| | 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...」

接著選「Plug-in Development → Plug-in Project → Next」



* 重複以上步驟,Project name填入不同的名稱,產生三個project (ServiceProxy_TEST、BundleA、BundleB)

### 2-1. BundleA

:::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

* 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

* 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

2. 先 Deselect All → 然後將剛剛新增的那三個bundle打勾 → Add Required Bundles → 設定Start Level
3. 將 org.eclipse.equinox.console、org.apache.felix.gogo.shell 也打勾
4. 最後 Apply → Run

5. Result

### 3-2. 測試執行 - 不使用Service Proxy
6. 將ServiceProxy_TEST > Activator.java line 29 註解

7. (如果有做3-1 step1~4) Run → Run As → OSGi Framework
8. Result

---
[範例程式碼 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)