package org.gcube.smartgears.handler.resourceregistry; import static org.gcube.common.events.Observes.Kind.resilient; import static org.gcube.smartgears.handler.resourceregistry.Constants.ESERVICE_PROPERTY; import static org.gcube.smartgears.handler.resourceregistry.Constants.RESOURCE_MANAGEMENT; import static org.gcube.smartgears.handlers.ProfileEvents.addToContext; import static org.gcube.smartgears.handlers.ProfileEvents.removeFromContext; import static org.gcube.smartgears.lifecycle.application.ApplicationLifecycle.activation; import static org.gcube.smartgears.lifecycle.application.ApplicationLifecycle.failure; import static org.gcube.smartgears.lifecycle.application.ApplicationLifecycle.stop; import static org.gcube.smartgears.utils.Utils.rethrowUnchecked; import java.net.URI; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.servlet.ServletRegistration; import javax.xml.bind.annotation.XmlRootElement; import org.gcube.common.authorization.client.proxy.AuthorizationProxy; import org.gcube.common.authorization.library.provider.SecurityTokenProvider; import org.gcube.common.events.Observes; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.informationsystem.base.impl.properties.HeaderImpl; import org.gcube.informationsystem.base.reference.properties.Header; import org.gcube.informationsystem.model.impl.properties.PropagationConstraintImpl; import org.gcube.informationsystem.model.impl.relations.ConsistsOfImpl; import org.gcube.informationsystem.model.reference.entities.Resource; import org.gcube.informationsystem.model.reference.properties.PropagationConstraint; import org.gcube.informationsystem.model.reference.properties.PropagationConstraint.AddConstraint; import org.gcube.informationsystem.model.reference.properties.PropagationConstraint.RemoveConstraint; import org.gcube.informationsystem.model.reference.relations.ConsistsOf; import org.gcube.informationsystem.resourceregistry.api.exceptions.AvailableInAnotherContextException; import org.gcube.informationsystem.resourceregistry.api.exceptions.NotFoundException; import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException; import org.gcube.informationsystem.resourceregistry.client.ResourceRegistryClient; import org.gcube.informationsystem.resourceregistry.client.ResourceRegistryClientFactory; import org.gcube.informationsystem.resourceregistry.publisher.ResourceRegistryPublisher; import org.gcube.informationsystem.resourceregistry.publisher.ResourceRegistryPublisherFactory; import org.gcube.resourcemanagement.model.impl.entities.facets.AccessPointFacetImpl; import org.gcube.resourcemanagement.model.impl.entities.facets.ServiceStateFacetImpl; import org.gcube.resourcemanagement.model.impl.entities.facets.SoftwareFacetImpl; import org.gcube.resourcemanagement.model.impl.entities.resources.EServiceImpl; import org.gcube.resourcemanagement.model.impl.properties.ValueSchemaImpl; import org.gcube.resourcemanagement.model.impl.relations.consistsof.IsIdentifiedByImpl; import org.gcube.resourcemanagement.model.impl.relations.isrelatedto.ActivatesImpl; import org.gcube.resourcemanagement.model.reference.entities.facets.AccessPointFacet; import org.gcube.resourcemanagement.model.reference.entities.facets.ServiceStateFacet; import org.gcube.resourcemanagement.model.reference.entities.facets.SoftwareFacet; import org.gcube.resourcemanagement.model.reference.entities.resources.EService; import org.gcube.resourcemanagement.model.reference.entities.resources.HostingNode; import org.gcube.resourcemanagement.model.reference.properties.ValueSchema; import org.gcube.resourcemanagement.model.reference.relations.consistsof.IsIdentifiedBy; import org.gcube.resourcemanagement.model.reference.relations.isrelatedto.Activates; import org.gcube.smartgears.configuration.application.ApplicationConfiguration; import org.gcube.smartgears.configuration.container.ContainerConfiguration; import org.gcube.smartgears.context.Property; import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.handlers.application.ApplicationLifecycleEvent; import org.gcube.smartgears.handlers.application.ApplicationLifecycleHandler; import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; import org.gcube.smartgears.lifecycle.application.ApplicationState; import org.gcube.smartgears.lifecycle.container.ContainerLifecycle; import org.gcube.smartgears.provider.ProviderFactory; import org.gcube.smartgears.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Manages the {@link EService} {@link Resource} of the application. *

* The manager: *

*

* * @author Luca Frosini */ @XmlRootElement(name = RESOURCE_MANAGEMENT) public class EServiceManager extends ApplicationLifecycleHandler { private static final Logger logger = LoggerFactory .getLogger(EServiceManager.class); private ApplicationContext applicationContext; private AuthorizationProxy authorizationProxy; private ScheduledFuture periodicUpdates; private static List servletExcludes = Arrays.asList("default","jsp"); public EServiceManager() { super(); this.authorizationProxy = ProviderFactory.provider() .authorizationProxy(); } private void setContextFromToken(String token) { if (token == null || token.compareTo("") == 0) { SecurityTokenProvider.instance.reset(); ScopeProvider.instance.reset(); } else { SecurityTokenProvider.instance.set(token); String scope = getContextName(token); ScopeProvider.instance.set(scope); } } @Override public void onStart(ApplicationLifecycleEvent.Start e) { try{ logger.info("onStart started"); this.applicationContext = e.context(); init(); registerObservers(); schedulePeriodicUpdates(); logger.info("onStart finished"); }catch(Throwable re){ logger.error("onStart failed", re); } } private void init() { ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); String previousToken = SecurityTokenProvider.instance.get(); try { Thread.currentThread().setContextClassLoader( EServiceManager.class.getClassLoader()); EService eService = null; Set startTokens = applicationContext.configuration().startTokens(); for (String token : startTokens) { setContextFromToken(token); if(eService != null){ ResourceRegistryPublisher resourceRegistryPublisher = ResourceRegistryPublisherFactory.create(); addToContext(resourceRegistryPublisher); } eService= getEService(); share(eService); } } catch (Throwable e) { rethrowUnchecked(e); } finally { setContextFromToken(previousToken); Thread.currentThread().setContextClassLoader(contextCL); } logger.info("init for EService executed"); } private void share(EService eService) { logger.trace("sharing EService for {}", applicationContext.name()); applicationContext.properties().add( new Property(ESERVICE_PROPERTY, eService)); } // helpers private void registerObservers() { applicationContext.events().subscribe(new Object() { @Observes({ activation, stop, failure }) void onChanged(ApplicationLifecycle lc) { String state = getState(lc); logger.debug("Moving app {} to {}", applicationContext.name(), state); ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); String previousToken = SecurityTokenProvider.instance.get(); if(previousToken==null) previousToken = applicationContext.configuration().startTokens().iterator().next(); setContextFromToken(previousToken); try { Thread.currentThread().setContextClassLoader(EServiceManager.class.getClassLoader()); createOrUpdateServiceStateFacet(state); } catch (Exception e) { logger.error("Failed to update Service State", e); } finally { Thread.currentThread().setContextClassLoader(contextCL); } } @Observes(value = addToContext) void addTo(String token) { ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); String previousToken = SecurityTokenProvider.instance.get(); try { Thread.currentThread().setContextClassLoader(EServiceManager.class.getClassLoader()); setContextFromToken(token); ResourceRegistryPublisher resourceRegistryPublisher = ResourceRegistryPublisherFactory.create(); addToContext(resourceRegistryPublisher); } catch (Exception e) { logger.error("Failed to add HostingNode to current context ({})", getCurrentContextName(), e); } finally { setContextFromToken(previousToken); Thread.currentThread().setContextClassLoader(contextCL); } } @Observes(value = removeFromContext) void removeFrom(String token) { ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); String previousToken = SecurityTokenProvider.instance.get(); try { Thread.currentThread().setContextClassLoader(EServiceManager.class.getClassLoader()); setContextFromToken(token); ResourceRegistryPublisher resourceRegistryPublisher = ResourceRegistryPublisherFactory.create(); removeFromContext(resourceRegistryPublisher); } catch (Exception e) { logger.error("Failed to remove HostingNode from current context ({})", getCurrentContextName(), e); } finally { setContextFromToken(previousToken); Thread.currentThread().setContextClassLoader(contextCL); } } }); } private String getState(ApplicationLifecycle lc){ return lc.state().remoteForm().toLowerCase(); } private void schedulePeriodicUpdates() { // register to cancel updates applicationContext.events().subscribe( new Object() { // we register it in response to lifecycle events so that we can // stop and resume along with application @Observes(value = { activation }, kind = resilient) synchronized void restartPeriodicUpdates( final ApplicationLifecycle lc) { // already running if (periodicUpdates != null) { return; } if (lc.state() == ApplicationState.active) { logger.info( "scheduling periodic updates of application {} EService", applicationContext.name()); } else { logger.info( "resuming periodic updates of application {} EService", applicationContext.name()); } final Runnable updateTask = new Runnable() { public void run() { try { String state = getState(lc); createOrUpdateServiceStateFacet(state); } catch (Exception e) { logger.error("Cannot complete periodic update of EService", e); } } }; periodicUpdates = Utils.scheduledServicePool .scheduleAtFixedRate( updateTask, Constants.application_republish_frequency_in_minutes, Constants.application_republish_frequency_in_minutes, TimeUnit.MINUTES); } @Observes(value = { stop, failure }, kind = resilient) synchronized void cancelPeriodicUpdates(ContainerLifecycle ignore) { if (periodicUpdates != null) { logger.trace( "stopping periodic updates of application {} EService", applicationContext.name()); try { periodicUpdates.cancel(true); periodicUpdates = null; } catch (Exception e) { logger.warn( "could not stop periodic updates of application {} EService", applicationContext.name(), e); } } } }); } private Activates createActivatesRelation(EService eService, ResourceRegistryPublisher resourceRegistryPublisher) throws ResourceRegistryException{ HostingNode hostingNode = applicationContext.container().properties().lookup(Constants.HOSTING_NODE_PROPERTY).value(HostingNode.class); addToContext(resourceRegistryPublisher); PropagationConstraint propagationConstraint = new PropagationConstraintImpl(); propagationConstraint.setRemoveConstraint(RemoveConstraint.cascade); propagationConstraint.setAddConstraint(AddConstraint.propagate); Activates activates = new ActivatesImpl<>(hostingNode, eService, propagationConstraint); try { activates = resourceRegistryPublisher.createIsRelatedTo(activates); } catch (NotFoundException e) { logger.error("THIS IS REALLY STRANGE. YOU SHOULD NE BE HERE. Error while creating {}.", activates, e); throw e; } catch (ResourceRegistryException e) { logger.error("Error while creating {}", activates, e); throw e; } hostingNode.attachResource(activates); shareHostingNode(hostingNode); return activates; } private EService getEService() throws ResourceRegistryException { EService eService = null; ResourceRegistryClient resourceRegistryClient = ResourceRegistryClientFactory.create(); ResourceRegistryPublisher resourceRegistryPublisher = ResourceRegistryPublisherFactory.create(); UUID eServiceUUID = UUID.fromString(this.applicationContext.id()); try { resourceRegistryClient.exists(EService.class, eServiceUUID); eService = resourceRegistryClient.getInstance(EService.class, eServiceUUID); } catch (NotFoundException e) { eService = instantiateEService(eServiceUUID); eService = createActivatesRelation(eService, resourceRegistryPublisher).getTarget(); } catch (AvailableInAnotherContextException e) { addToContext(resourceRegistryPublisher); eService = resourceRegistryClient.getInstance(EService.class, eServiceUUID); } catch (ResourceRegistryException e) { throw e; } return eService; } private void shareHostingNode(HostingNode hostingNode) { logger.trace("sharing {} {}", HostingNode.NAME, Resource.NAME); applicationContext.container().properties().add( new Property(Constants.HOSTING_NODE_PROPERTY, hostingNode)); } private String getCurrentContextName() { String token = SecurityTokenProvider.instance.get(); return getContextName(token); } private String getContextName(String token) { try { return this.authorizationProxy.get(token).getContext(); } catch (Exception e) { logger.error("Error retrieving token {}, it should never happen", token); return null; } } private void addToContext(ResourceRegistryPublisher resourceRegistryPublisher) throws ResourceRegistryException { HostingNode hostingNode = applicationContext.container().properties().lookup(Constants.HOSTING_NODE_PROPERTY).value(HostingNode.class); resourceRegistryPublisher.addResourceToCurrentContext(hostingNode); logger.info("{} successfully added to current context ({})", hostingNode, getCurrentContextName()); shareHostingNode(hostingNode); } private void removeFromContext(ResourceRegistryPublisher resourceRegistryPublisher) throws ResourceRegistryException { HostingNode hostingNode = applicationContext.container().properties().lookup(Constants.HOSTING_NODE_PROPERTY).value(HostingNode.class); resourceRegistryPublisher.removeResourceFromCurrentContext(hostingNode); logger.info("{} successfully removed from current context ({})", hostingNode, getCurrentContextName()); shareHostingNode(hostingNode); } private void createOrUpdateServiceStateFacet(String state) throws ResourceRegistryException { ResourceRegistryPublisher resourceRegistryPublisher = ResourceRegistryPublisherFactory .create(); String selectedToken = applicationContext.configuration().startTokens().toArray(new String[0])[0]; setContextFromToken(selectedToken); EService eService = getEService(); ServiceStateFacet serviceStateFacet = null; List serviceStateFacets = eService.getFacets(ServiceStateFacet.class); if(serviceStateFacets !=null && serviceStateFacets.size()>=1){ serviceStateFacet = serviceStateFacets.get(0); serviceStateFacet.setValue(state); serviceStateFacet = resourceRegistryPublisher.updateFacet(serviceStateFacet); for(int i=1; i consistsOf = new ConsistsOfImpl( eService, serviceStateFacet, null); consistsOf = resourceRegistryPublisher.createConsistsOf(consistsOf); } // Newly created ServiceStateFacet must be added to all context ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( EServiceManager.class.getClassLoader()); Set startTokens = applicationContext.configuration().startTokens(); for (String token : startTokens) { setContextFromToken(token); addToContext(resourceRegistryPublisher); } } catch (ResourceRegistryException e) { throw e; } finally { setContextFromToken(null); Thread.currentThread().setContextClassLoader(contextCL); } //UUID eServiceUUID = eService.getHeader().getUUID(); //eService = resourceRegistryClient.getInstance(EService.class, eServiceUUID); share(eService); } private static String getBaseAddress(ApplicationContext context){ ApplicationConfiguration configuration = context.configuration(); ContainerConfiguration container = context.container().configuration(); String baseAddress; if (configuration.proxied()){ String protocol = configuration.proxyAddress().protocol(); String port = configuration.proxyAddress().port()!=null?":"+configuration.proxyAddress().port():""; baseAddress=String.format("%s://%s%s%s", protocol , configuration.proxyAddress().hostname(), port,context.application().getContextPath()); } else { String protocol = container.protocol(); int port = container.port(); baseAddress=String.format("%s://%s:%d%s", protocol , container.hostname(), port,context.application().getContextPath()); } return baseAddress; } private EService instantiateEService(UUID uuid) { logger.info("Creating EService for {}", applicationContext.name()); ApplicationConfiguration applicationConfiguration = applicationContext .configuration(); EService eService = new EServiceImpl(); Header header = new HeaderImpl(uuid); eService.setHeader(header); SoftwareFacet softwareFacet = new SoftwareFacetImpl(); softwareFacet.setDescription(applicationConfiguration.description()); softwareFacet.setGroup(applicationConfiguration.serviceClass()); softwareFacet.setName(applicationConfiguration.name()); softwareFacet.setVersion(applicationConfiguration.version()); IsIdentifiedBy isIdentifiedBy = new IsIdentifiedByImpl( eService, softwareFacet, null); eService.addFacet(isIdentifiedBy); String baseAddress = EServiceManager.getBaseAddress(applicationContext); for (ServletRegistration servlet : applicationContext.application() .getServletRegistrations().values()) { if (!servletExcludes.contains(servlet.getName())) { for (String mapping : servlet.getMappings()) { String address = baseAddress+(mapping.endsWith("*")?mapping.substring(0,mapping.length()-2):mapping); AccessPointFacet accessPointFacet = new AccessPointFacetImpl(); accessPointFacet.setEntryName(servlet.getName()); accessPointFacet.setEndpoint(URI.create(address)); ValueSchema valueSchema = new ValueSchemaImpl(); valueSchema.setValue("gcube-token"); accessPointFacet.setAuthorization(valueSchema); eService.addFacet(accessPointFacet); } } } ServiceStateFacet serviceStateFacet = new ServiceStateFacetImpl(); String state = getState(applicationContext.lifecycle()); serviceStateFacet.setValue(state.toLowerCase()); eService.addFacet(serviceStateFacet); return eService; } @Override public String toString() { return RESOURCE_MANAGEMENT; } }