/*
* 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 legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2013-2015 ForgeRock AS.
*/
package org.forgerock.openidm.servlet.internal;
import static org.forgerock.openidm.servletregistration.ServletRegistration.SERVLET_FILTER_AUGMENT_SECURITY_CONTEXT;
import static org.forgerock.openidm.servletregistration.ServletRegistration.SERVLET_FILTER_SCRIPT_EXTENSIONS;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.script.ScriptException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.ReferenceStrategy;
import org.apache.felix.scr.annotations.Service;
import org.forgerock.http.Filter;
import org.forgerock.http.Handler;
import org.forgerock.http.HttpApplication;
import org.forgerock.http.HttpApplicationException;
import org.forgerock.http.filter.TransactionIdInboundFilter;
import org.forgerock.http.handler.Handlers;
import org.forgerock.http.io.Buffer;
import org.forgerock.http.servlet.HttpFrameworkServlet;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.ConnectionFactory;
import org.forgerock.json.resource.http.CrestHttp;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.openidm.servletregistration.ServletFilterRegistrator;
import org.forgerock.openidm.servletregistration.ServletRegistration;
import org.forgerock.script.ScriptEntry;
import org.forgerock.script.ScriptRegistry;
import org.forgerock.util.Factory;
import org.osgi.framework.Constants;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A NAME does ...
*
*/
@Component(name = "org.forgerock.openidm.api-servlet",
immediate = true,
policy = ConfigurationPolicy.IGNORE)
@Service
@Properties({
@Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME),
@Property(name = Constants.SERVICE_DESCRIPTION, value = "OpenIDM Common REST HttpServlet"),
@Property(name = EventConstants.EVENT_TOPIC, value = { "org/forgerock/openidm/servlet/*" })
})
public class ServletComponent implements EventHandler {
public static final String PID = "org.forgerock.openidm.router";
private static final String SERVLET_ALIAS = "/openidm";
/** Setup logging for the {@link ServletComponent}. */
private final static Logger logger = LoggerFactory.getLogger(ServletComponent.class);
/** The (external) ConnectionFactory */
@Reference(policy = ReferencePolicy.DYNAMIC, target = "(service.pid=org.forgerock.openidm.router)")
protected ConnectionFactory connectionFactory;
@Reference(policy = ReferencePolicy.STATIC, target = "(service.pid=org.forgerock.openidm.auth.config)")
private Filter authFilter;
@Reference
private ServletRegistration servletRegistration;
/** Script Registry service. */
@Reference(policy = ReferencePolicy.DYNAMIC)
protected ScriptRegistry scriptRegistry;
// Optional scripts to augment/populate the security context
private List<ScriptEntry> augmentSecurityScripts = new CopyOnWriteArrayList<ScriptEntry>();
// Register script extensions configured
@Reference(
name = "reference_Servlet_ServletFilterRegistrator",
referenceInterface = ServletFilterRegistrator.class,
bind = "bindRegistrator",
unbind = "unbindRegistrator",
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
policy = ReferencePolicy.DYNAMIC,
strategy = ReferenceStrategy.EVENT
)
protected Map<ServletFilterRegistrator, ScriptEntry> filterRegistratorMap =
new HashMap<ServletFilterRegistrator, ScriptEntry>();
protected synchronized void bindRegistrator(ServletFilterRegistrator registrator, Map<String, Object> properties) {
JsonValue scriptConfig = registrator.getConfiguration()
.get(SERVLET_FILTER_SCRIPT_EXTENSIONS)
.get(SERVLET_FILTER_AUGMENT_SECURITY_CONTEXT);
if (!scriptConfig.isNull() && !scriptConfig.expect(Map.class).isNull()) {
try {
ScriptEntry augmentScript = scriptRegistry.takeScript(scriptConfig);
filterRegistratorMap.put(registrator, augmentScript);
augmentSecurityScripts.add(augmentScript);
logger.debug("Registered script {}", augmentScript);
} catch (ScriptException e) {
logger.debug("{} when attempting to registered script {}", e.toString(), scriptConfig, e);
}
}
}
protected synchronized void unbindRegistrator(ServletFilterRegistrator registrator, Map<String, Object> properties) {
ScriptEntry augmentScript = filterRegistratorMap.remove(registrator);
if (augmentScript != null) {
augmentSecurityScripts.remove(augmentScript);
logger.debug("Deregistered script {}", augmentScript);
}
}
private HttpServlet servlet;
@Activate
protected void activate(ComponentContext context) throws ServletException, NamespaceException {
logger.debug("Try registering servlet at {}", SERVLET_ALIAS);
final Handler handler = CrestHttp.newHttpHandler(
connectionFactory, new IDMSecurityContextFactory(augmentSecurityScripts));
servlet = new HttpFrameworkServlet(
new HttpApplication() {
@Override
public Handler start() throws HttpApplicationException {
return Handlers.chainOf(handler, authFilter);
}
@Override
public Factory<Buffer> getBufferFactory() {
return null;
}
@Override
public void stop() {
}
});
servletRegistration.registerServlet(SERVLET_ALIAS, servlet, new Hashtable());
logger.info("Registered servlet at {}", SERVLET_ALIAS);
}
@Deactivate
protected synchronized void deactivate(ComponentContext context) {
servletRegistration.unregisterServlet(servlet);
}
// ----- Implementation of EventHandler
@Override
public void handleEvent(Event event) {
// TODO: receive the OpenIDM started event and enable the full HTTP
// service
if (event.getTopic().equals("org/forgerock/openidm/servlet/ACTIVATE")) {
try {
activate(null);
} catch (Exception e) {
logger.error("Error activating api-servlet", e);
}
} else if (event.getTopic().equals("org/forgerock/openidm/servlet/DEACTIVATE")) {
deactivate(null);
}
}
}