/* * 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 2015 ForgeRock AS. */ package org.forgerock.openidm.maintenance.impl; import static org.forgerock.json.JsonValue.field; import static org.forgerock.json.JsonValue.json; import static org.forgerock.json.JsonValue.object; import static org.forgerock.json.resource.Responses.newActionResponse; import java.util.concurrent.Semaphore; 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.Service; import org.forgerock.json.resource.*; import org.forgerock.services.context.Context; import org.forgerock.openidm.core.ServerConstants; import org.forgerock.util.promise.Promise; import org.osgi.framework.Constants; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Basis and entry point to initiate the product maintenance and upgrade mechanisms over REST */ @Component(name = MaintenanceService.PID, policy = ConfigurationPolicy.IGNORE, metatype = true, description = "OpenIDM Product Upgrade Management Service", immediate = true) @Service @Properties({ @Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME), @Property(name = Constants.SERVICE_DESCRIPTION, value = "Product Maintenance Management Service"), @Property(name = ServerConstants.ROUTER_PREFIX, value = "/maintenance/*") }) public class MaintenanceService implements RequestHandler, Filter { private final static Logger logger = LoggerFactory.getLogger(MaintenanceService.class); public static final String PID = "org.forgerock.openidm.maintenance"; private final Filter passthroughFilter = new PassthroughFilter(); private final Filter maintenanceFilter = new MaintenanceFilter(); /** * A boolean indicating if maintenance mode is currently enabled */ private boolean maintenanceEnabled = false; /** * A lock used in the enabling and disabling of maintenance mode. */ private Semaphore maintenanceModeLock = new Semaphore(1); @Activate void activate(ComponentContext compContext) throws Exception { logger.debug("Activating Maintenance service {}", compContext.getProperties()); logger.info("Maintenance service started."); } @Deactivate void deactivate(ComponentContext compContext) { logger.debug("Deactivating Service {}", compContext.getProperties()); logger.info("Maintenance service stopped."); maintenanceEnabled = false; } private enum Action { status, enable, disable } /** * Maintenance action support */ @Override public Promise<ActionResponse, ResourceException> handleAction(Context context, ActionRequest request) { try { switch (request.getActionAsEnum(Action.class)) { case status: return handleMaintenanceStatus(); case enable: enableMaintenanceMode(); return handleMaintenanceStatus(); case disable: disableMaintenanceMode(); return handleMaintenanceStatus(); default: return new NotSupportedException(request.getAction() + " is not supported").asPromise(); } } catch (ResourceException e) { return new InternalServerErrorException("Error processing Action request", e).asPromise(); } } /** * Enables maintenance mode by disabling the currently active (or unsatisfied) components contained in * the list of maintenance mode components. * * @throws ResourceException if an error occurs when attempting to enable maintenance mode */ private void enableMaintenanceMode() throws ResourceException { if (maintenanceModeLock.tryAcquire()) { if (!maintenanceEnabled) { maintenanceEnabled = true; } maintenanceModeLock.release(); } else { throw new InternalServerErrorException("Cannot enable maintenance mode, change is already in progress"); } } /** * Disables maintenance mode by enabling the currently disabled components contained in the list of * maintenance mode components. * * @throws ResourceException if an error occurs when attempting to enable maintenance mode */ private void disableMaintenanceMode() throws ResourceException { if (maintenanceModeLock.tryAcquire()) { if (maintenanceEnabled) { maintenanceEnabled = false; } maintenanceModeLock.release(); } else { throw new InternalServerErrorException("Cannot disable maintenance mode, change is already in progress"); } } private Promise<ActionResponse, ResourceException> handleMaintenanceStatus() { return newActionResponse(json(object(field("maintenanceEnabled", maintenanceEnabled)))).asPromise(); } /** * Service does not allow creating entries. */ @Override public Promise<ResourceResponse, ResourceException> handleCreate(Context context, CreateRequest request) { return new NotSupportedException("Not allowed on maintenance service").asPromise(); } /** * Service does not support deleting entries.. */ @Override public Promise<ResourceResponse, ResourceException> handleDelete(Context context, DeleteRequest request) { return new NotSupportedException("Not allowed on maintenance service").asPromise(); } /** * Service does not support changing entries. */ @Override public Promise<ResourceResponse, ResourceException> handlePatch(Context context, PatchRequest request) { return new NotSupportedException("Not allowed on maintenance service").asPromise(); } /** * Service does not support querying entries yet. */ @Override public Promise<QueryResponse, ResourceException> handleQuery(Context context, QueryRequest request, QueryResourceHandler handler) { return new NotSupportedException("Not allowed on maintenance service").asPromise(); } /** * Service does not support reading entries yet. */ @Override public Promise<ResourceResponse, ResourceException> handleRead(Context context, ReadRequest request) { return new NotSupportedException("Not allowed on maintenance service").asPromise(); } /** * Service does not support changing entries. */ @Override public Promise<ResourceResponse, ResourceException> handleUpdate(Context context, UpdateRequest request) { return new NotSupportedException("Not allowed on maintenance service").asPromise(); } // ----- Implementation of Filter private Filter getFilter() { return maintenanceEnabled ? maintenanceFilter : passthroughFilter; } /** * Delegate filterAction to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<ActionResponse, ResourceException> filterAction(Context context, ActionRequest actionRequest, RequestHandler requestHandler) { return getFilter().filterAction(context, actionRequest, requestHandler); } /** * Delegate filterCreate to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<ResourceResponse, ResourceException> filterCreate(Context context, CreateRequest createRequest, RequestHandler requestHandler) { return getFilter().filterCreate(context, createRequest, requestHandler); } /** * Delegate filterDelete to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<ResourceResponse, ResourceException> filterDelete(Context context, DeleteRequest deleteRequest, RequestHandler requestHandler) { return getFilter().filterDelete(context, deleteRequest, requestHandler); } /** * Delegate filterPatch to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<ResourceResponse, ResourceException> filterPatch(Context context, PatchRequest patchRequest, RequestHandler requestHandler) { return getFilter().filterPatch(context, patchRequest, requestHandler); } /** * Delegate filterQuery to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<QueryResponse, ResourceException> filterQuery(Context context, QueryRequest queryRequest, QueryResourceHandler queryResourceHandler, RequestHandler requestHandler) { return getFilter().filterQuery(context, queryRequest, queryResourceHandler, requestHandler); } /** * Delegate filterRead to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<ResourceResponse, ResourceException> filterRead(Context context, ReadRequest readRequest, RequestHandler requestHandler) { return getFilter().filterRead(context, readRequest, requestHandler); } /** * Delegate filterUpdate to appropriate filter given maintenance mode. * * {@inheritDoc} */ @Override public Promise<ResourceResponse, ResourceException> filterUpdate(Context context, UpdateRequest updateRequest, RequestHandler requestHandler) { return getFilter().filterUpdate(context, updateRequest, requestHandler); } }