commit a8cdc9e6de8e18a66c3241d4a949855fb3797557 Author: Lucio Lelii Date: Fri Sep 30 16:36:48 2016 +0000 branch for release 4.1 git-svn-id: http://svn.research-infrastructures.eu/d4science/gcube/branches/common/common-smartgears-app/2.0@132260 82a268e6-3cf1-43bd-a215-b396298e98cf diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..a996918 --- /dev/null +++ b/.classpath @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..5dcd236 --- /dev/null +++ b/.project @@ -0,0 +1,36 @@ + + + common-smartgears-app + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..116f861 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,7 @@ +#Sun Aug 04 12:32:12 CEST 2013 +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6249222 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..cd18c7b --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,5 @@ +#Sun Aug 04 12:32:12 CEST 2013 +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..cd2a88f --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,6 @@ + + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..1b22d70 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..04cad8c --- /dev/null +++ b/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/distro/LICENSE b/distro/LICENSE new file mode 100644 index 0000000..2d9616a --- /dev/null +++ b/distro/LICENSE @@ -0,0 +1 @@ +${gcube.license} \ No newline at end of file diff --git a/distro/README b/distro/README new file mode 100644 index 0000000..bd2025c --- /dev/null +++ b/distro/README @@ -0,0 +1,66 @@ +The gCube System - ${name} +-------------------------------------------------- + +${description} + +${gcube.description} + +${gcube.funding} + + +Version +-------------------------------------------------- + +${version} (${buildDate}) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Fabio Simeoni (fabio.simeoni@fao.org), FAO of the UN, Italy +* Lucio Lelii (lucio.lelii@isti.cnr.it), CNR, Italy + + +Maintainers +----------- + +* Lucio Lelii (lucio.lelii@isti.cnr.it), CNR, Italy + + +Download information +-------------------------------------------------- + +Source code is available from SVN: + ${scm.url} + +Binaries can be downloaded from the gCube website: + ${gcube.website} + + +Installation +-------------------------------------------------- + +Installation documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/Smartgears + + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/Smartgears + + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + ${gcube.issueTracking} + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. \ No newline at end of file diff --git a/distro/changelog.xml b/distro/changelog.xml new file mode 100644 index 0000000..5d8e834 --- /dev/null +++ b/distro/changelog.xml @@ -0,0 +1,8 @@ + + + Added support for initialization using security token + + + First Release + + \ No newline at end of file diff --git a/distro/descriptor.xml b/distro/descriptor.xml new file mode 100644 index 0000000..e395580 --- /dev/null +++ b/distro/descriptor.xml @@ -0,0 +1,32 @@ + + servicearchive + + tar.gz + + / + + + ${distroDirectory} + / + true + + README + LICENSE + changelog.xml + profile.xml + + 755 + true + + + + + target/${build.finalName}.${project.packaging} + /${artifactId} + + + + \ No newline at end of file diff --git a/distro/profile.xml b/distro/profile.xml new file mode 100644 index 0000000..91c49e4 --- /dev/null +++ b/distro/profile.xml @@ -0,0 +1,26 @@ + + + + Service + + ${description} + Common + ${artifactId} + 1.0.0 + + + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + + ${build.finalName}.jar + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..244b81c --- /dev/null +++ b/pom.xml @@ -0,0 +1,115 @@ + + 4.0.0 + + org.gcube.tools + maven-parent + 1.0.0 + + + + org.gcube.core + common-smartgears-app + 2.0.0-SNAPSHOT + Smartgears Application + + + + distro + + + + scm:svn:http://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/${project.artifactId} + scm:svn:https://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/${project.artifactId} + http://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/${project.artifactId} + + + + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + org.reflections + reflections + 0.9.9-RC1 + + + + org.gcube.core + common-smartgears + [2.0.0-SNAPSHOT,3.0.0-SNAPSHOT) + provided + + + + junit + junit + 4.11 + test + + + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.5 + + + copy-profile + install + + copy-resources + + + target + + + ${distroDirectory} + true + + profile.xml + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2 + + + ${distroDirectory}/descriptor.xml + + + + + servicearchive + install + + single + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/smartgears/ApplicationManager.java b/src/main/java/org/gcube/smartgears/ApplicationManager.java new file mode 100644 index 0000000..ec75d0d --- /dev/null +++ b/src/main/java/org/gcube/smartgears/ApplicationManager.java @@ -0,0 +1,10 @@ +package org.gcube.smartgears; + + +public interface ApplicationManager { + + public void onInit(); + + public void onShutdown(); + +} diff --git a/src/main/java/org/gcube/smartgears/ApplicationManagerProvider.java b/src/main/java/org/gcube/smartgears/ApplicationManagerProvider.java new file mode 100644 index 0000000..6832994 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/ApplicationManagerProvider.java @@ -0,0 +1,84 @@ +package org.gcube.smartgears; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; + +import javassist.util.proxy.MethodHandler; +import javassist.util.proxy.ProxyFactory; +import javassist.util.proxy.ProxyObject; + +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.smartgears.annotations.ManagedBy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApplicationManagerProvider { + + private static Logger logger = LoggerFactory.getLogger(ApplicationManagerProvider.class); + + static Map>> appManagerMap = new HashMap>>(); + + private static Map> proxyClassMap = new HashMap>(); + + public static synchronized ApplicationManager get(){ + final Class applicationManagerClass = retrieveManagerClass(); + return get(applicationManagerClass); + } + + public static synchronized ApplicationManager get(final Class applicationManagerClass){ + Object obj; + try { + Class _class = getProxyClass(applicationManagerClass); + obj = _class.newInstance(); + } catch (Exception e) { + throw new RuntimeException("error creating proxy ", e); + } + + MethodHandler handler = new MethodHandler() { + @Override + public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { + logger.debug("context is {}", ScopeProvider.instance.get()); + logger.debug("applicationManagerClass is {}",applicationManagerClass.getCanonicalName()); + Future appManagerFuture = appManagerMap.get(applicationManagerClass.getCanonicalName()).get(ScopeProvider.instance.get()); + logger.debug("appmanager future is null? {}", appManagerFuture==null); + logger.debug("thisMethod is null? {}", thisMethod==null); + return thisMethod.invoke(appManagerFuture.get(), args); + } + }; + ((ProxyObject)obj).setHandler(handler); + + return applicationManagerClass.cast(obj); + } + + private static Class getProxyClass(Class applicationManagerClass){ + if (proxyClassMap.containsKey(applicationManagerClass.getCanonicalName())) + return proxyClassMap.get(applicationManagerClass.getCanonicalName()); + + ProxyFactory proxyfactory = new ProxyFactory(); + proxyfactory.setSuperclass(applicationManagerClass); + Class proxyClass=proxyfactory.createClass(); + proxyClassMap.put(applicationManagerClass.getCanonicalName(), proxyClass); + return proxyClass; + + } + + private static Class retrieveManagerClass(){ + String classname = Thread.currentThread().getStackTrace()[3].getClassName(); + logger.trace("managed servlet caller is {}",classname); + ManagedBy annotation; + try { + annotation = Class.forName(classname).getAnnotation(ManagedBy.class); + } catch (ClassNotFoundException e) { + throw new RuntimeException("error initializing ApplicationManager",e); + } + + if (annotation == null){ + logger.error(" {} is not managed by an ApplicationManager", classname); + throw new RuntimeException(classname+" is not managed by an ApplicationManager"); + } + + return annotation.value(); + } +} diff --git a/src/main/java/org/gcube/smartgears/ContextListener.java b/src/main/java/org/gcube/smartgears/ContextListener.java new file mode 100644 index 0000000..d9ec729 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/ContextListener.java @@ -0,0 +1,70 @@ +package org.gcube.smartgears; + +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +import org.gcube.smartgears.annotations.ManagedBy; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.reflections.Reflections; +import org.reflections.scanners.TypeAnnotationsScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@WebListener +public class ContextListener implements ServletContextListener { + + private static Logger log = LoggerFactory.getLogger(ContextListener.class); + + RegisterApplicationManagerObserver observer; + + + @Override + public void contextInitialized(ServletContextEvent sce) { + + ApplicationContext context = (ApplicationContext) sce.getServletContext().getAttribute(Constants.context_attribute); + + if (context==null) { + String msg = sce.getServletContext().getContextPath()+" is a gCube-aware application but is not managed as a gCube resource: missing or invalid context attribute "+Constants.context_attribute; + throw new RuntimeException(msg); + } + + log.info("configuring context provider for {}",context.name()); + ContextProvider.set(context); + + + retrieveAndRegisterManagers(context); + } + + + private void retrieveAndRegisterManagers(ApplicationContext context) { + Reflections reflection = new ConfigurationBuilder().addUrls(ClasspathHelper.forClassLoader()).setScanners(new TypeAnnotationsScanner()).build(); + + Set> toInitialize = reflection.getTypesAnnotatedWith(ManagedBy.class); + Set> managers = new HashSet>(); + for (Class initializer: toInitialize ){ + ManagedBy manageBy = initializer.getAnnotation(ManagedBy.class); + log.info("ApplicationManager added {} @ {}", manageBy.value().getSimpleName(), context.name()); + managers.add(manageBy.value()); + } + if (managers.size()>0){ + observer = new RegisterApplicationManagerObserver(managers, context.configuration().startTokens()); + context.events().subscribe(observer); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + if (observer!=null){ + ApplicationContext context = (ApplicationContext) sce.getServletContext().getAttribute(Constants.context_attribute); + context.events().unsubscribe(observer); + observer.onStop(context); + } + } + +} diff --git a/src/main/java/org/gcube/smartgears/ContextProvider.java b/src/main/java/org/gcube/smartgears/ContextProvider.java new file mode 100644 index 0000000..4398a32 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/ContextProvider.java @@ -0,0 +1,101 @@ +package org.gcube.smartgears; + +import javax.servlet.ServletContext; + +import org.gcube.common.events.Hub; +import org.gcube.smartgears.configuration.application.ApplicationConfiguration; +import org.gcube.smartgears.context.Properties; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.gcube.smartgears.context.container.ContainerContext; +import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; +import org.gcube.smartgears.persistence.Persistence; + +/** + * Embedded in an application, makes available its context as a gCube resource. + * + * @author Fabio Simeoni + * + */ +public class ContextProvider { + + private static ApplicationContextProxy context = new ApplicationContextProxy(); + + /** + * Returns the application context. + * @return the context. + */ + public static ApplicationContext get() { + return context; + } + + /** + * Sets the application context. + * @param context the context; + * + * @throws IllegalStateException if the context has not been set because the resource is not managed as a gCube resource + */ + public static void set(ApplicationContext context) { + + if (context==null) + throw new IllegalStateException("no context set for this application: are you sure the application is managed as a gCube resource?"); + + ContextProvider.context.delegate = context; + } + + + static class ApplicationContextProxy implements ApplicationContext{ + + private ApplicationContext delegate; + + @Override + public String name() { + return delegate.name(); + } + + @Override + public ApplicationConfiguration configuration() { + return delegate.configuration(); + } + + @Override + public T profile(Class type) { + return delegate.profile(type); + } + + @Override + public ApplicationLifecycle lifecycle() { + return delegate.lifecycle(); + } + + @Override + public Hub events() { + return delegate.events(); + } + + @Override + public Persistence persistence() { + return delegate.persistence(); + } + + @Override + public ServletContext application() { + return delegate.application(); + } + + @Override + public ContainerContext container() { + return delegate.container(); + } + + @Override + public Properties properties() { + return delegate.properties(); + } + + @Override + public String id() { + return delegate.id(); + } + + } +} diff --git a/src/main/java/org/gcube/smartgears/RegisterApplicationManagerObserver.java b/src/main/java/org/gcube/smartgears/RegisterApplicationManagerObserver.java new file mode 100644 index 0000000..3c133df --- /dev/null +++ b/src/main/java/org/gcube/smartgears/RegisterApplicationManagerObserver.java @@ -0,0 +1,195 @@ +package org.gcube.smartgears; + +import static org.gcube.common.authorization.client.Constants.authorizationService; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.events.Observes; +import org.gcube.common.events.Observes.Kind; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RegisterApplicationManagerObserver { + + private static Logger log = LoggerFactory.getLogger(RegisterApplicationManagerObserver.class); + + private static ExecutorService service = Executors.newCachedThreadPool(); + + + private Set> managersClass ; + + private Map>> instanciatedManagerPerScope = new HashMap>>(); + + public RegisterApplicationManagerObserver( + Set> managersClass, Collection startingTokens) { + super(); + this.managersClass = managersClass; + for (String startingToken : startingTokens ) + this.onRegistation(startingToken); + + } + + @Observes(value=Constants.token_registered, kind=Kind.safe) + public synchronized void onRegistation(final String securityToken){ + log.info("token registered called with token {}", securityToken); + List> futureList = new ArrayList>(); + + try { + final String context = authorizationService().get(securityToken).getContext(); + + + for (Class appManager: managersClass){ + Future appManagerFuture = service.submit(new InitAppManager(securityToken, context, appManager)); + log.info("intializing app in context {} with token {} ",context, securityToken); + + futureList.add(appManagerFuture); + if (ApplicationManagerProvider.appManagerMap.containsKey(appManager.getCanonicalName())) + ApplicationManagerProvider.appManagerMap.get(appManager.getCanonicalName()).put(context, appManagerFuture); + else { + Map> tokenFutureMap = new HashMap>(); + tokenFutureMap.put(context, appManagerFuture); + ApplicationManagerProvider.appManagerMap.put(appManager.getCanonicalName(), tokenFutureMap); + } + } + if (!futureList.isEmpty()) + instanciatedManagerPerScope.put(context, futureList); + } catch (ObjectNotFound e1) { + log.error("it should never happen (token has just been created)",e1); + throw new RuntimeException("it should never happen (token has just been created",e1); + } catch (Exception e1) { + log.error("something failed getting token",e1); + throw new RuntimeException("something failed getting token",e1); + } + } + + @Observes(value=Constants.token_removed, kind=Kind.critical) + public synchronized void onRemove(final String securityToken){ + + try { + final String context = authorizationService().get(securityToken).getContext(); + + for (Future appManager: instanciatedManagerPerScope.get(context)){ + service.execute(new ShutDownAppManager(securityToken, context, appManager)); + ApplicationManagerProvider.appManagerMap.get(appManager).remove(context); + } + + instanciatedManagerPerScope.remove(context); + SecurityTokenProvider.instance.reset(); + } catch (ObjectNotFound e1) { + log.error("it should never happen (token has just been created)",e1); + throw new RuntimeException("it should never happen (token has just been created",e1); + } catch (Exception e1) { + log.error("something failed getting token",e1); + throw new RuntimeException("something failed getting token",e1); + } + } + + public synchronized void onStop(ApplicationContext appContext){ + + for (String token :appContext.configuration().startTokens()){ + try { + String context = authorizationService().get(token).getContext(); + for (Future appManagerEntry: instanciatedManagerPerScope.get(context)){ + try{ + log.info("stoppping {} in context {} ",appContext.name(), context); + + SecurityTokenProvider.instance.set(token); + ScopeProvider.instance.set(context); + try { + appManagerEntry.get().onShutdown(); + log.info("manager {} correctly suhtdown on context {}",appContext.name(), context); + } catch (Exception e){ + log.warn("problem calling onShutdown for context {}", context, e); + } + }catch(Exception e){ + log.error("error retrieving token on shutdown on context {}", context,e); + throw new RuntimeException("error retrieving token on shutdown",e); + }finally{ + ScopeProvider.instance.reset(); + SecurityTokenProvider.instance.reset(); + } + } + } catch (ObjectNotFound e1) { + log.error("token not found : {}",token,e1); + } catch (Exception e1) { + log.error("something failed getting token {}",token,e1); + } + } + unregister(); + } + + public void unregister(){ + service.shutdownNow(); + } + + public class InitAppManager implements Callable{ + + private Class managerClass; + private String securityToken; + private String context; + + public InitAppManager(String securityToken, String context, Class managerClass){ + this.managerClass = managerClass; + this.securityToken = securityToken; + this.context = context; + } + + @Override + public ApplicationManager call() throws Exception { + SecurityTokenProvider.instance.set(securityToken); + ScopeProvider.instance.set(context); + ApplicationManager manager = managerClass.newInstance(); + try { + log.info("calling on onInit of {} on token {}",manager.getClass().getCanonicalName(), securityToken); + manager.onInit(); + } catch (Exception e) { + log.warn("error on onInit of {} on token {}",manager.getClass().getCanonicalName(), securityToken, e); + } finally{ + ScopeProvider.instance.reset(); + SecurityTokenProvider.instance.reset(); + } + return manager; + } + } + + public class ShutDownAppManager implements Runnable{ + + private Future appManager; + private String securityToken; + private String context; + + public ShutDownAppManager(String securityToken, String context, Future appManager){ + this.appManager = appManager; + this.securityToken = securityToken; + this.context = context; + } + + @Override + public void run() { + SecurityTokenProvider.instance.set(securityToken); + ScopeProvider.instance.set(context); + try { + log.info("calling on ShutDown of {} on token {}",appManager.getClass().getCanonicalName(), securityToken); + appManager.get().onShutdown(); + } catch (Exception e) { + log.warn("error on onShutdown of {} on token {}",appManager.getClass().getCanonicalName(), securityToken, e); + } finally{ + ScopeProvider.instance.reset(); + SecurityTokenProvider.instance.reset(); + } + } + } +} diff --git a/src/main/java/org/gcube/smartgears/annotations/ManagedBy.java b/src/main/java/org/gcube/smartgears/annotations/ManagedBy.java new file mode 100644 index 0000000..1da5066 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/annotations/ManagedBy.java @@ -0,0 +1,15 @@ +package org.gcube.smartgears.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.gcube.smartgears.ApplicationManager; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ManagedBy { + + public Class value(); +} diff --git a/src/test/java/org/gcube/smartgears/stateful/ResourceBinder.java b/src/test/java/org/gcube/smartgears/stateful/ResourceBinder.java new file mode 100644 index 0000000..aef5694 --- /dev/null +++ b/src/test/java/org/gcube/smartgears/stateful/ResourceBinder.java @@ -0,0 +1,12 @@ +package org.gcube.smartgears.stateful; + +import org.junit.Test; + +public class ResourceBinder { + + @Test + public void bind() throws Exception{ + + } + +}