/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2011-2015 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.impl; import static org.forgerock.http.filter.TransactionIdInboundFilter.SYSPROP_TRUST_TRANSACTION_HEADER; import static org.forgerock.services.context.ClientContext.newInternalClientContext; import java.util.List; 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.Modified; 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.Service; import org.forgerock.http.header.MalformedHeaderException; import org.forgerock.http.header.TransactionIdHeader; import org.forgerock.json.resource.AbstractConnectionWrapper; import org.forgerock.json.resource.Connection; import org.forgerock.json.resource.ConnectionFactory; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.http.HttpContext; import org.forgerock.openidm.core.ServerConstants; import org.forgerock.openidm.router.IDMConnectionFactory; import org.forgerock.services.TransactionId; import org.forgerock.services.context.Context; import org.forgerock.services.context.TransactionIdContext; import org.forgerock.util.promise.Promise; import org.forgerock.util.promise.Promises; import org.osgi.framework.Constants; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides internal routing for a top-level object set. * <p> * Provides access to both internal and external Connections to suit the business-logic needs * of the consumer. * <p> * As a traditional {@link ConnectionFactory}, the {@link ConnectionFactory#getConnection()} * and {@link ConnectionFactory#getConnectionAsync()} methods retrieve a <strong>internal</strong> * connection to the underlying ResourceProvider (generally the {@link org.forgerock.json.resource.Router}), * which decorates the context chain with an internal {@link org.forgerock.services.context.ClientContext} * to indicate to routed ResourceProviders that the request was made internally. * <p> * When used as a {@link IDMConnectionFactory}, the {@link IDMConnectionFactory#getExternalConnection()} * and {@link IDMConnectionFactory#getExternalConnectionAsync()} methods retrieve a <strong>external</strong> * connection to the underlying ResourceProvider. This allows an internal call to propagate a request as * being external without the {@link Context} chain being decorated. This may be useful when providing * endpoints to authenticated users that relay (or proxy) requests to other parts of the system where * we want to apply the same business logic as if these requests had been directly over HTTP. */ @Component(name = JsonResourceRouterService.PID, policy = ConfigurationPolicy.OPTIONAL, metatype = true, configurationFactory = false, immediate = true) @Service(value = { ConnectionFactory.class, IDMConnectionFactory.class }) @Properties({ @Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME), @Property(name = Constants.SERVICE_DESCRIPTION, value = "OpenIDM internal JSON resource router"), @Property(name = ServerConstants.ROUTER_PREFIX, value = "/") }) public class JsonResourceRouterService implements IDMConnectionFactory { // Public Constants public static final String PID = "org.forgerock.openidm.internal"; /** Setup logging for the {@link JsonResourceRouterService}. */ private final static Logger logger = LoggerFactory.getLogger(JsonResourceRouterService.class); /** Event name prefix for monitoring the router */ public final static String EVENT_ROUTER_PREFIX = "openidm/internal/router/"; @Reference(target = "(service.pid=org.forgerock.openidm.router)") private ConnectionFactory connectionFactory = null; /** internal connection factory for internal routing */ private ConnectionFactory internal = null; @Activate void activate(ComponentContext context) { internal = newInternalConnectionFactory(connectionFactory); logger.info("Reconciliation service activated."); } @Modified void modified(ComponentContext context) { activate(context); logger.info("Reconciliation service modified."); } @Deactivate void deactivate(ComponentContext context) { logger.info("Reconciliation service deactivated."); } // ----- Implementation of ConnectionFactory ----- // delegate to this object's internal ConnectionFactory which decorates the router ConnectionFactory @Override public Connection getConnection() throws ResourceException { return internal.getConnection(); } @Override public Promise<Connection, ResourceException> getConnectionAsync() { return internal.getConnectionAsync(); } @Override public void close() { internal.close(); } private ConnectionFactory newInternalConnectionFactory(final ConnectionFactory connectionFactory) { return new ConnectionFactory() { @Override public void close() { connectionFactory.close(); } @Override public Connection getConnection() throws ResourceException { return new AbstractConnectionWrapper<Connection>(connectionFactory.getConnection()) { @Override protected Context transform(Context context) { return newInternalClientContext(newTransactionIdContext(context)); } }; } @Override public Promise<Connection, ResourceException> getConnectionAsync() { try { return Promises.newResultPromise(getConnection()); } catch (ResourceException e) { return e.asPromise(); } } }; } // ----- IDMConnectionFactory implementation ----- // delegate to the object's *external* ConnectionFactory that comes from the router @Override public Connection getExternalConnection() throws ResourceException { return connectionFactory.getConnection(); } @Override public Promise<Connection, ResourceException> getExternalConnectionAsync() { return connectionFactory.getConnectionAsync(); } private Context newTransactionIdContext(final Context context) { return context.containsContext(TransactionIdContext.class) ? context : new TransactionIdContext(context, createTransactionId(context)); } private TransactionId createTransactionId(final Context context) { try { if (context.containsContext(HttpContext.class) && isTransactionIdHeaderEnabled()) { final List<String> header = context.asContext(HttpContext.class).getHeaders().get(TransactionIdHeader.NAME); if (header != null && !header.isEmpty()) { return TransactionIdHeader.valueOf(header.get(0)).getTransactionId(); } } } catch (MalformedHeaderException ex) { logger.trace("The TransactionId header is malformed.", ex); } return new TransactionId(); } private boolean isTransactionIdHeaderEnabled() { return Boolean.getBoolean(SYSPROP_TRUST_TRANSACTION_HEADER); } }