AbstractRouterRegistry.java revision fa9b7593e9e156060babbf3a9b06de8d6b5da5a3
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013 ForgeRock AS. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.forgerock.openidm.router;
import java.util.Dictionary;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.Connection;
import org.forgerock.json.resource.Context;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.Resources;
import org.forgerock.json.resource.Route;
import org.forgerock.json.resource.Router;
import org.forgerock.json.resource.ServerContext;
import org.forgerock.json.resource.ServiceUnavailableException;
import org.forgerock.json.resource.SingletonResourceProvider;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.openidm.util.ContextUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A NAME does ...
*
* @author Laszlo Hordos
*/
public abstract class AbstractRouterRegistry implements ServiceFactory<RouterRegistryService>,
ServiceTrackerCustomizer<Object, RouteEntryImpl> {
/**
* Setup logging for the {@link AbstractRouterRegistry}.
*/
final static Logger logger = LoggerFactory.getLogger(AbstractRouterRegistry.class);
protected final BundleContext context;
protected final ServiceTracker<Router, Router> routerTracker;
protected ServiceTracker<Object, RouteEntry> resourceTracker;
protected final AtomicReference<Router> internalRouter = new AtomicReference<Router>();
protected final AtomicBoolean isActive = new AtomicBoolean(Boolean.FALSE);
protected final CopyOnWriteArraySet<RouteEntry> routeCache =
new CopyOnWriteArraySet<RouteEntry>();
// the routerTracker.getTrackingCount when the router were last got
protected int routerCount;
protected AbstractRouterRegistry(BundleContext context) {
this(context, null);
}
protected AbstractRouterRegistry(BundleContext context,
ServiceTracker<Router, Router> routerTracker) {
if (null == context) {
throw new NullPointerException("Failure the BundleContext value is null.");
}
this.context = context;
this.routerTracker = routerTracker;
String flt =
"(|(" + Constants.OBJECTCLASS + "=" + CollectionResourceProvider.class.getName()
+ ")" + "(" + Constants.OBJECTCLASS + "="
+ SingletonResourceProvider.class.getName() + ")" + "("
+ Constants.OBJECTCLASS + "=" + RequestHandler.class.getName() + "))";
try {
resourceTracker = new ServiceTracker(context, FrameworkUtil.createFilter(flt), this);
Filter f = FrameworkUtil.createFilter("(|(url=/repo/{partition}))");
} catch (InvalidSyntaxException e) {
// not expected (filter is tested valid)
}
activate();
}
protected abstract void activate();
protected void deactivate() {
isActive.compareAndSet(true, false);
for (final RouteEntry entry : routeCache) {
//Entries in cache has circular references to the cache set to remove themselves
entry.removeRoute();
}
resourceTracker.close();
}
/*
* protected RouteEntry
* bindCollectionResourceProvider(CollectionResourceProvider service, Object
* uriTemplate){ return null;};
*
* protected RouteEntry
* updatedCollectionResourceProvider(CollectionResourceProvider service,
* Object uriTemplate){ return null;};
*
* protected RouteEntry
* unbindCollectionResourceProvider(CollectionResourceProvider service,
* Object uriTemplate){ return null;};
*
* protected RouteEntry
* bindSingletonResourceProvider(SingletonResourceProvider service, Object
* uriTemplate){ return null;};
*
* protected RouteEntry
* updatedSingletonResourceProvider(SingletonResourceProvider service,
* Object uriTemplate){ return null;};
*
* protected RouteEntry
* unbindSingletonResourceProvider(SingletonResourceProvider service, Object
* uriTemplate){ return null;};
*
* protected RouteEntry bindRequestHandler(RequestHandler service, Object
* uriTemplate){ return null;};
*
* protected RouteEntry updatedRequestHandler(RequestHandler service, Object
* uriTemplate){ return null;};
*
* protected RouteEntry unbindRequestHandler(RequestHandler service, Object
* uriTemplate){ return null;};
*/
public RouteEntryImpl addRoute(final Bundle source, RouteBuilder routeBuilder) {
return new RouteEntryImpl(context, source, internalRouter, routeBuilder);
}
public RouteEntryImpl addRouteCustom(final Bundle source, RouteBuilder routeBuilder) {
final RouteEntryImpl self =
new RouteEntryImpl(context, source, internalRouter, routeBuilder) {
@Override
public boolean removeRoute() {
routeCache.remove(this);
return super.removeRoute();
}
};
routeCache.add(self);
return self;
}
public RouteEntryImpl addRoute(ServiceReference<Object> reference, Object service) {
try {
RouteBuilder newRoutes =
RouteBuilder.instance().bind(service,
reference.getProperty(ServerConstants.ROUTER_PREFIX)).seal();
if (newRoutes.isNotEmpty()) {
return addRoute(reference.getBundle(), newRoutes);
}
} catch (NullPointerException e) {
logger.debug("There was not service to register in {}", reference);
}
return null;
}
// ----- Implementation of ServiceFactory interface
@Override
public RouterRegistryService getService(Bundle bundle,
ServiceRegistration<RouterRegistryService> registration) {
return new RouterRegistryServiceImpl(bundle, this);
}
@Override
public void ungetService(Bundle bundle,
ServiceRegistration<RouterRegistryService> registration, RouterRegistryService service) {
((RouterRegistryServiceImpl) service).dispose();
}
// ----- Implementation of ServiceTrackerCustomizer interface
public RouteEntryImpl addingService(ServiceReference<Object> reference) {
Object service = context.getService(reference);
RouteEntryImpl result = null;
if (service instanceof CollectionResourceProvider) {
result = addRoute(reference, service);
} else if (service instanceof SingletonResourceProvider) {
result = addRoute(reference, service);
} else if (service instanceof RequestHandler) {
result = addRoute(reference, service);
}
if (null == result) {
context.ungetService(reference);
}
return result;
}
public void modifiedService(ServiceReference<Object> reference, RouteEntryImpl service) {
service.removeRoute();
Object newService = context.getService(reference);
RouteEntry result = null;
if (newService instanceof CollectionResourceProvider) {
result = addRoute(reference, newService);
} else if (newService instanceof SingletonResourceProvider) {
result = addRoute(reference, newService);
} else if (newService instanceof RequestHandler) {
result = addRoute(reference, newService);
}
if (null == result) {
context.ungetService(reference);
}
}
public void removedService(ServiceReference<Object> reference, RouteEntryImpl service) {
context.ungetService(reference);
service.removeRoute();
}
// ----- Protected methods meant to be overwritten
protected Router getInternalRouter() {
// The getTrackingCount() is synchronized, avoid it if it's possible
if (internalRouter.get() == null
|| (routerTracker != null && routerTracker.getTrackingCount() > routerCount)) {
if (routerTracker == null) {
internalRouter.set(new Router());
} else {
ServiceReference[] refs = routerTracker.getServiceReferences();
if (refs == null || refs.length == 0) {
internalRouter.set(new Router());
} else {
for (int i = 0; i < refs.length; i++) {
Router service = routerTracker.getService(refs[i]);
if (service != null) {
internalRouter.set(service);
routerCount = routerTracker.getTrackingCount();
break;
}
}
}
}
}
return internalRouter.get();
}
protected static class RouterRegistryServiceImpl implements RouterRegistryService {
protected Bundle bundle;
protected AbstractRouterRegistry registry;
public RouterRegistryServiceImpl(Bundle bundle, AbstractRouterRegistry registry) {
this.bundle = bundle;
this.registry = registry;
}
void dispose() {
registry = null;
bundle = null;
}
// ----- Implementation of RouterRegistryService interface
@Override
public RouteEntry addRoute(RouteBuilder routeBuilder) {
RouteEntry entry = null;
if (registry.isActive.get()) {
entry = registry.addRouteCustom(bundle, routeBuilder);
}
return entry;
}
@Override
public Connection getConnection(String connectionId) throws ResourceException {
//TODO This is NOT Authenticated!!!
return Resources.newInternalConnection(registry.getInternalRouter());
}
@Override
public String getConnectionId(Connection connection) throws ResourceException {
return "OPENIDM_ROOT";
}
// // TODO Consider to move this to the parent class and avoid the
// closed
// // connection if disposed
// // ----- Implementation of RequestHandler interface
//
// public void handleAction(ServerContext context, ActionRequest
// request,
// ResultHandler<JsonValue> handler) {
// if (null != registry) {
// registry.getInternalRouter().handleAction(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
//
// public void handleCreate(ServerContext context, CreateRequest
// request,
// ResultHandler<Resource> handler) {
// if (null != registry) {
// registry.getInternalRouter().handleCreate(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
//
// public void handleDelete(ServerContext context, DeleteRequest
// request,
// ResultHandler<Resource> handler) {
// if (null != registry) {
// registry.getInternalRouter().handleDelete(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
//
// public void handlePatch(ServerContext context, PatchRequest request,
// ResultHandler<Resource> handler) {
// if (null != registry) {
// registry.getInternalRouter().handlePatch(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
//
// public void handleQuery(ServerContext context, QueryRequest request,
// QueryResultHandler handler) {
// if (null != registry) {
// registry.getInternalRouter().handleQuery(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
//
// public void handleRead(ServerContext context, ReadRequest request,
// ResultHandler<Resource> handler) {
// if (null != registry) {
// registry.getInternalRouter().handleRead(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
//
// public void handleUpdate(ServerContext context, UpdateRequest
// request,
// ResultHandler<Resource> handler) {
// if (null != registry) {
// registry.getInternalRouter().handleUpdate(context, request, handler);
// } else {
// handler.handleError(new
// ServiceUnavailableException("Connection has been disposed!"));
// }
// }
}
}
class RouteServiceImpl implements RouteService {
protected Bundle bundle;
protected final AtomicReference<Router> internalRouter;
RouteServiceImpl(Bundle bundle, final AtomicReference<Router> router) {
this.bundle = bundle;
this.internalRouter = router;
}
void dispose() {
bundle = null;
}
@Override
public ServerContext createServerContext() throws ResourceException {
return createServerContext(ContextUtil.createInternalSecurityContext(bundle
.getBundleContext()));
}
@Override
public ServerContext createServerContext(Context parentContext) throws ResourceException {
final Router r = internalRouter.get();
if (null != r) {
return new ServerContext(parentContext, Resources.newInternalConnection(r));
} else {
throw new ServiceUnavailableException();
}
}
}
class RouteEntryImpl extends RouteServiceImpl implements RouteEntry {
protected Route[] registeredRoutes;
protected ServiceRegistration factoryServiceRegistration;
RouteEntryImpl(BundleContext parent, Bundle bundle, final AtomicReference<Router> router,
RouteBuilder builder) {
super(bundle, router);
final Router r = router.get();
if (r == null) {
throw new NullPointerException("Router is required to register the routes to");
}
registeredRoutes = builder.register(r);
if (registeredRoutes.length > 0) {
Dictionary<String, Object> props = builder.getProperties();
props.put(Constants.SERVICE_PID, RouteService.class.getName());
factoryServiceRegistration =
parent.registerService(RouteService.class.getName(), new RouteServiceFactory(
router), props);
}
}
void dispose() {
removeRoute();
registeredRoutes = null;
bundle = null;
super.dispose();
}
public boolean removeRoute() {
boolean isModified = false;
if (null != factoryServiceRegistration) {
factoryServiceRegistration.unregister();
}
final Router r = internalRouter.get();
if (r != null) {
isModified = r.removeRoute(registeredRoutes);
}
return isModified;
}
}
class RouteServiceFactory implements ServiceFactory<RouteService> {
protected final AtomicReference<Router> internalRouter;
RouteServiceFactory(final AtomicReference<Router> internalRouter) {
this.internalRouter = internalRouter;
}
public RouteService getService(Bundle bundle, ServiceRegistration<RouteService> registration) {
return new RouteServiceImpl(bundle, internalRouter);
}
public void ungetService(Bundle bundle, ServiceRegistration<RouteService> registration,
RouteService service) {
((RouteServiceImpl) service).dispose();
}
}