/*
* 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.json;
import static org.forgerock.json.JsonValue.field;
import static org.forgerock.json.JsonValue.object;
import static org.forgerock.json.resource.Responses.newActionResponse;
import java.nio.file.Paths;
import java.util.Map;
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.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.forgerock.openidm.maintenance.upgrade.UpdateException;
import org.forgerock.openidm.maintenance.upgrade.UpdateManager;
import org.forgerock.openidm.router.IDMConnectionFactory;
import org.forgerock.services.context.Context;
import org.forgerock.services.context.RootContext;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.DeleteRequest;
import org.forgerock.json.resource.InternalServerErrorException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchRequest;
import org.forgerock.json.resource.QueryRequest;
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.Requests;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.openidm.core.IdentityServer;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.services.context.SecurityContext;
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 = UpdateService.PID, policy = ConfigurationPolicy.IGNORE, metatype = true,
description = "OpenIDM Product Update Management Service", immediate = true)
@Service
@Properties({
@Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME),
@Property(name = Constants.SERVICE_DESCRIPTION, value = "Product Update Management Service"),
@Property(name = ServerConstants.ROUTER_PREFIX, value = "/maintenance/update/*")
})
public class UpdateService implements RequestHandler {
private final static Logger logger = LoggerFactory.getLogger(UpdateService.class);
public static final String PID = "org.forgerock.openidm.maintenance.update";
private static final String ARCHIVE_NAME = "archive";
private static final String ARCHIVE_DIRECTORY = "/bin/update/";
@Reference(policy=ReferencePolicy.STATIC)
private UpdateManager updateManager;
/** The connection factory */
@Reference(policy = ReferencePolicy.STATIC)
private IDMConnectionFactory connectionFactory;
@Activate
void activate(ComponentContext compContext) throws Exception {
logger.debug("Activating Update service {}", compContext.getProperties());
// Ensure archive directory exists
if (!Paths.get(IdentityServer.getInstance().getInstallLocation() + ARCHIVE_DIRECTORY).toFile().exists()) {
Paths.get(IdentityServer.getInstance().getInstallLocation() + ARCHIVE_DIRECTORY).toFile().mkdirs();
}
logger.info("Update service started.");
}
@Deactivate
void deactivate(ComponentContext compContext) {
logger.debug("Deactivating Service {}", compContext.getProperties());
logger.info("Update service stopped.");
}
private enum Action {
available,
preview,
update,
getLicense,
restart,
lastUpdateId
}
/**
* Update action support
*/
@Override
public Promise<ActionResponse, ResourceException> handleAction(Context context, ActionRequest request) {
switch (request.getActionAsEnum(Action.class)) {
case available:
return handleListAvailable();
case preview:
return handlePreviewUpdate(request.getAdditionalParameters());
case update:
return handleInstallUpdate(request.getAdditionalParameters(),
context.asContext(SecurityContext.class).getAuthenticationId());
case getLicense:
return handleLicense(request.getAdditionalParameters());
case restart:
updateManager.restartNow();
return newActionResponse(json(object())).asPromise();
case lastUpdateId:
return newActionResponse(json(object(field("lastUpdateId", updateManager.getLastUpdateId()))))
.asPromise();
default:
return new NotSupportedException(request.getAction() + " is not supported").asPromise();
}
}
private Promise<ActionResponse, ResourceException> handleListAvailable() {
try {
return newActionResponse(updateManager.listAvailableUpdates()).asPromise();
} catch (UpdateException e) {
return new InternalServerErrorException(e.getMessage(), e).asPromise();
}
}
private Promise<ActionResponse, ResourceException> handlePreviewUpdate(Map<String, String> parameters) {
try {
if (!parameters.containsKey(ARCHIVE_NAME)) {
return new BadRequestException("Archive name not specified.").asPromise();
}
return newActionResponse(updateManager.report(
Paths.get(IdentityServer.getInstance().getInstallLocation() + ARCHIVE_DIRECTORY +
parameters.get(ARCHIVE_NAME)),
IdentityServer.getInstance().getInstallLocation().toPath())).asPromise();
} catch (UpdateException e) {
return new InternalServerErrorException(e.getMessage(), e).asPromise();
}
}
private Promise<ActionResponse, ResourceException> handleInstallUpdate(Map<String, String> parameters,
String userName) {
try {
if (!parameters.containsKey(ARCHIVE_NAME)) {
return new BadRequestException("Archive name not specified.").asPromise();
}
try {
ActionResponse response = connectionFactory.getConnection().action(new RootContext(),
Requests.newActionRequest("/maintenance", "status"));
if (!response.getJsonContent().get("maintenanceEnabled").asBoolean().equals(Boolean.TRUE)) {
throw new UpdateException("Must be in maintenance mode prior to installing an update.");
}
} catch (ResourceException e) {
throw new UpdateException("Unable to check maintenance mode status.", e);
}
return newActionResponse(updateManager.upgrade(
Paths.get(IdentityServer.getInstance().getInstallLocation() + ARCHIVE_DIRECTORY +
parameters.get(ARCHIVE_NAME)),
IdentityServer.getInstance().getInstallLocation().toPath(), userName)).asPromise();
} catch (UpdateException e) {
return new InternalServerErrorException(e.getMessage(), e).asPromise();
}
}
private Promise<ActionResponse, ResourceException> handleLicense(Map<String, String> parameters) {
try {
return newActionResponse(updateManager.getLicense(
Paths.get(IdentityServer.getInstance().getInstallLocation() + ARCHIVE_DIRECTORY +
parameters.get(ARCHIVE_NAME)))).asPromise();
} catch (UpdateException e) {
return new InternalServerErrorException(e.getMessage(), e).asPromise();
}
}
/**
* Service does not allow creating entries.
*/
@Override
public Promise<ResourceResponse, ResourceException> handleCreate(Context context, CreateRequest request) {
return new NotSupportedException("Not allowed on update service").asPromise();
}
/**
* Service does not support deleting entries..
*/
@Override
public Promise<ResourceResponse, ResourceException> handleDelete(Context context, DeleteRequest request) {
return new NotSupportedException("Not allowed on update service").asPromise();
}
/**
* Service does not support changing entries.
*/
@Override
public Promise<ResourceResponse, ResourceException> handlePatch(Context context, PatchRequest request) {
return new NotSupportedException("Not allowed on update 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 update 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 update service").asPromise();
}
/**
* Service does not support changing entries.
*/
@Override
public Promise<ResourceResponse, ResourceException> handleUpdate(Context context, UpdateRequest request) {
return new NotSupportedException("Not allowed on update service").asPromise();
}
}