/*
* Copyright (c) 2010 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ovirt.engine.api.common.resource;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.text.MessageFormat;
import java.util.concurrent.Executor;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.Status;
import org.ovirt.engine.api.common.util.LinkHelper;
import org.ovirt.engine.api.common.util.ReapedMap;
import org.ovirt.engine.api.common.util.StatusUtils;
import org.ovirt.engine.api.model.Action;
import org.ovirt.engine.api.model.BaseResource;
import org.ovirt.engine.api.model.CreationStatus;
import org.ovirt.engine.api.model.Fault;
import org.ovirt.engine.api.resource.ActionResource;
public abstract class AbstractActionableResource<R extends BaseResource> extends AbstractUpdatableResource<R> {
private static final long REAP_AFTER = 2 * 60 * 60 * 1000L; // 2 hours
protected Executor executor;
protected ReapedMap<String, ActionResource> actions;
protected UriInfoProvider uriProvider;
public AbstractActionableResource(String id) {
this(id, new SimpleExecutor());
}
public AbstractActionableResource(String id, Executor executor) {
super(id);
this.executor = executor;
actions = new ReapedMap<String, ActionResource>(REAP_AFTER);
}
public AbstractActionableResource(String id, Executor executor, UriInfoProvider uriProvider) {
super(id);
this.executor = executor;
this.uriProvider = uriProvider;
actions = new ReapedMap<String, ActionResource>(REAP_AFTER);
}
protected UriInfo getUriInfo() {
return uriProvider.getUriInfo();
}
public UriInfoProvider getUriProvider() {
return uriProvider;
}
protected R getModel() {
R parent = newModel();
parent.setId(getId());
return parent;
}
/**
* Perform an action, managing asynchrony and returning an appropriate
* response.
*
* @param uriInfo wraps the URI for the current request
* @param action represents the pending action
* @param task fulfils the action
* @return
*/
protected Response doAction(UriInfo uriInfo, final AbstractActionTask task) {
Action action = task.action;
Response.Status status = null;
final ActionResource actionResource = new BaseActionResource<R>(uriInfo, task.action, getModel());
if (action.isSetAsync() && action.isAsync()) {
action.setStatus(StatusUtils.create(CreationStatus.PENDING));
actions.put(action.getId(), actionResource);
executor.execute(new Runnable() {
public void run() {
perform(task);
actions.reapable(actionResource.getAction().getId());
}
});
status = Status.ACCEPTED;
} else {
// no need for self link in action if synchronous (as no querying
// will ever be needed)
//
perform(task);
if (!action.getStatus().getState().equals(CreationStatus.FAILED.value())) {
status = Status.OK;
} else {
status = Status.BAD_REQUEST;
}
}
return Response.status(status).entity(action).build();
}
public ActionResource getActionSubresource(String action, String oid) {
// redirect back to the target VM if action no longer cached
// REVISIT: ultimately we should look at redirecting
// to the event/audit log
//
ActionResource exists = actions.get(oid);
return exists != null
? exists
: new ActionResource() {
@Override
public Response get() {
R tmp = newModel();
tmp.setId(getId());
tmp = LinkHelper.addLinks(getUriInfo(), tmp);
Response.Status status = Response.Status.MOVED_PERMANENTLY;
return Response.status(status).location(URI.create(tmp.getHref())).build();
}
@Override
public Action getAction() {
return null;
}
};
}
public Executor getExecutor() {
return executor;
}
public void setExecutor(Executor executor) {
this.executor = executor;
}
private void perform(AbstractActionTask task) {
task.action.setStatus(StatusUtils.create(CreationStatus.IN_PROGRESS));
if (task.action.getGracePeriod() != null) {
try {
Thread.sleep(task.action.getGracePeriod().getExpiry());
} catch (Exception e) {
// ignore
}
}
task.run();
}
public static abstract class AbstractActionTask implements Runnable {
protected Action action;
protected String reason;
public AbstractActionTask(Action action) {
this(action, "");
}
public AbstractActionTask(Action action, String reason) {
this.action = action;
this.reason = reason;
}
public void run() {
try {
execute();
if (!action.getStatus().getState().equals(org.ovirt.engine.api.model.CreationStatus.FAILED.value())) {
action.setStatus(StatusUtils.create(org.ovirt.engine.api.model.CreationStatus.COMPLETE));
}
} catch (Throwable t) {
String message = t.getMessage() != null ? t.getMessage() : t.getClass().getName();
setFault(MessageFormat.format(t.getCause() != null ? t.getCause().getMessage()
:
reason,
message),
t);
}
}
protected abstract void execute();
protected void setFault(String reason, Throwable t) {
Fault fault = new Fault();
fault.setReason(reason);
fault.setDetail(t.getMessage());
action.setFault(fault);
action.setStatus(StatusUtils.create(org.ovirt.engine.api.model.CreationStatus.FAILED));
}
protected static String trace(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
}
}
protected static class DoNothingTask extends AbstractActionTask {
public DoNothingTask(Action action) {
super(action);
}
public void execute(){
}
}
/**
* Fallback executor, starts a new thread for each task.
*/
protected static class SimpleExecutor implements Executor {
public void execute(Runnable task) {
new Thread(task).start();
}
}
}