/*
* Copyright 2012 Glencoe Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package omero.cmd;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import ome.api.local.LocalAdmin;
import ome.conditions.InternalException;
import ome.system.EventContext;
import ome.system.ServiceFactory;
import ome.util.SqlAction;
import omero.ServerError;
import omero.cmd.HandleI.Cancel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hibernate.Session;
/**
* Helper object for all omero.cmd implementations.
*
* Since the objects must subclass from an Ice implementation
* repeated implementations which cannot be inherited are
* provided here.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 4.4.0
*/
public class Helper {
private final Logger log;
private final AtomicReference<Response> rsp = new AtomicReference<Response>();
private final Request request;
private final Status status;
private final ServiceFactory sf;
private final Session session;
private final SqlAction sql;
private int assertSteps = 0;
private int assertResponses = 0;
private boolean stepsSet = false;
public Helper(Request request, Status status, SqlAction sql,
Session session, ServiceFactory sf) {
synchronized (status) {
if (status.flags == null) {
status.flags = new ArrayList<State>();
}
}
this.request = request;
this.status = status;
this.sql = sql;
this.session = session;
this.sf = sf;
this.log = LoggerFactory.getLogger(
this.request.toString().replaceAll("@", ".@"));
}
/**
* Run {@link IRequest#init(Helper)} on a sub-command by creating a new
* Helper instance.
* @param req the request
* @param substatus its status for its new helper
* @return the new helper
*/
public Helper subhelper(Request req, Status substatus) {
return new Helper(req, substatus, sql, session, sf);
}
private void requireStepsSet() {
if (!stepsSet) {
cancel(new ERR(), new InternalException("Steps unset!"), "steps-unset");
}
}
public void setSteps(int steps) {
if (stepsSet) {
cancel(new ERR(), new InternalException("Steps set!"), "steps-set");
}
status.steps = steps;
stepsSet = true;
if (steps == 0) {
cancel(new ERR(), null, "no-steps");
}
}
public int getSteps() {
requireStepsSet();
return status.steps;
}
public Status getStatus() {
return status;
}
public Response getResponse() {
return rsp.get();
}
/**
* Set the response if there is not currently run, in which case true
* is returned. Otherwise, false.
* @param rsp the response
* @return if the setting was successful
*/
public boolean setResponseIfNull(Response rsp) {
return this.rsp.compareAndSet(null, rsp);
}
//
// Data access
// =========================================================================
//
public ServiceFactory getServiceFactory() {
return sf;
}
public Session getSession() {
return session;
}
public SqlAction getSql() {
return sql;
}
//
// Logging
// =========================================================================
//
public void debug(String fmt, Object...args) {
if (log.isDebugEnabled()) {
log.debug(String.format(fmt, args));
}
}
public void info(String fmt, Object...args) {
if (log.isInfoEnabled()) {
log.info(String.format(fmt, args));
}
}
public void warn(String fmt, Object...args) {
if (log.isWarnEnabled()) {
log.warn(String.format(fmt, args));
}
}
public void error(String fmt, Object...args) {
error(null, fmt, args);
}
public void error(Throwable t, String fmt, Object...args) {
if (log.isErrorEnabled()) {
if (t != null) {
log.error(String.format(fmt, args), t);
} else {
log.error(String.format(fmt, args));
}
}
}
//
// REPORTING
// =========================================================================
//
/**
* Converts pairs of values from the varargs list into a map. Any leftover
* value will be ignored.
* @param paramList a list of parameters
* @return a map with entries constructed from consecutive pairs of the parameters
*/
public Map<String, String> params(final String... paramList) {
Map<String, String> params = new HashMap<String, String>();
for (int i = 0; i < paramList.length-1; i = i + 2) {
if ((i+1)==paramList.length) {
break; // Handle the odd remaining item
}
params.put(paramList[i], paramList[i + 1]);
}
return params;
}
/**
* Calls {@link #fail(ERR, Throwable, String, Map)} with the output of
* {@link #params(String...)}.
*/
public void fail(ERR err, Throwable t, final String name, final String... paramList) {
fail(err, t, name, params(paramList));
}
/**
* Sets the status.flags and the ERR properties appropriately and also
* stores the message and stacktrace of the throwable in the parameters.
*/
public void fail(ERR err, Throwable t, final String name,
final Map<String, String> params) {
status.flags.add(State.FAILURE);
err.category = request.ice_id();
err.name = name;
if (err.parameters == null) {
err.parameters = params;
} else {
err.parameters.putAll(params);
}
if (t != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
String st = sw.toString();
if (t instanceof ServerError) {
String msg = ((ServerError) t).message;
err.parameters.put("message", msg);
} else {
String msg = t.getMessage();
err.parameters.put("message", msg);
}
err.parameters.put("stacktrace", st);
}
rsp.set(err);
}
/**
* Calls {@link #cancel(ERR, Throwable, String, Map)} with
* the output of {@link #params(String...)}.
*
* See the statement about the return value.
*/
public Cancel cancel(ERR err, Throwable t, final String name,
final String... paramList) {
return cancel(err, t, name, params(paramList));
}
/**
* Like {@link #fail(ERR, Throwable, String, String...)} throws a
* {@link Cancel} exception.
*
* A {@link Cancel} is thrown, even though one is also specified as the
* return value. This permits:
* <pre>
* } catch (Throwable t) {
* throw helper.cancel(new ERR(), t);
* }
* </pre>
* since omitting the "throw" within the catch clauses forces one to enter
* a number of "return null; // Never reached" lines.
*/
public Cancel cancel(ERR err, Throwable t, final String name,
final Map<String, String> params) {
fail(err, t, name, params);
status.flags.add(State.CANCELLED);
Cancel cancel = new Cancel(name);
if (t != null) {
cancel.initCause(t);
}
info("Cancelled");
throw cancel;
}
/**
* Throws an exception if the current step is not the expected value. The
* boolean returns value has been added so it can be used as:
* <pre>
* public boolean step(int i) {
* if (helper.assertStep(0, i)) {
* return null;
* }
* </pre>
* @param i the expected step number
* @param step the actual step number
*/
public void assertStep(int i, int step) {
if (step != i) {
cancel(new ERR(), null, "bad step",
"actual_step", "" + step,
"expected_step", "" + i);
}
}
/**
* Checks the given steps against the number of times that this method
* has been called on this instance and then increments the call count.
* @param step the new step number
*/
public void assertStep(int step) {
assertStep(this.assertSteps, step);
this.assertSteps++;
}
/**
* Checks the given steps against the number of times that this method
* has been called on this instance and then increments the call count.
* @param step the step number for the new response
*/
public void assertResponse(int step) {
assertStep(this.assertResponses, step);
this.assertResponses++;
}
/**
* Returns true if this is the last step for the given request.
* @param step the step number
* @return if the given step is the last
*/
public boolean isLast(int step) {
return step == (status.steps - 1);
}
/**
* Provides an {@link EventContext} instance without reloading the session,
* via {@link LocalAdmin#getEventContextQuiet()}.
* @return the event context
*/
public EventContext getEventContext() {
final LocalAdmin admin = (LocalAdmin) sf.getAdminService();
final EventContext ec = admin.getEventContextQuiet();
return ec;
}
}