resource-registry-handlers/src/main/java/org/gcube/smartgears/handler/resourceregistry/EServiceManager.java

540 lines
20 KiB
Java

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.
* <p>
* The manager:
* <ul>
* <li>
* creates the {@link EService} {@link Resource} and the facets it
* {@link ConsistsOf} when the application starts for the first time;</li>
* <li>
* update the {@link ServiceStateFacet} when the application becomes active, and
* at any lifecycle change thereafter;</li>
* </ul>
* </p>
*
* @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<String> 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<String> 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<HostingNode, EService> 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<HostingNode, EService> 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<ServiceStateFacet> 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<serviceStateFacets.size(); i++){
try {
logger.warn("You should not be here. There are more than one {}. Anyway deleting it : {}", ServiceStateFacet.class.getSimpleName(), serviceStateFacets.get(i));
resourceRegistryPublisher.deleteFacet(serviceStateFacets.get(i));
}catch (Exception e) {
logger.warn("Unable to delete {} which should not exists : {}", ServiceStateFacet.class.getSimpleName(), serviceStateFacets.get(i));
}
}
}else {
serviceStateFacet = new ServiceStateFacetImpl();
serviceStateFacet.setValue(state);
serviceStateFacet = resourceRegistryPublisher.createFacet(serviceStateFacet);
ConsistsOf<EService, ServiceStateFacet> consistsOf = new ConsistsOfImpl<EService, ServiceStateFacet>(
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<String> 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<EService, SoftwareFacet> isIdentifiedBy = new IsIdentifiedByImpl<EService, SoftwareFacet>(
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;
}
}