/******************************************************************************* * Copyright (c) 2007, 2016 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.core; import java.io.IOException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; import java.util.Map; import org.eclipse.tcf.internal.core.Token; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IErrorReport; import org.eclipse.tcf.protocol.IService; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.protocol.JSON; import org.eclipse.tcf.protocol.Protocol; /** * This is utility class that helps to implement sending a command and receiving * command result over TCF communication channel. The class uses JSON to encode * command arguments and to decode result data. * * The class also provides support for TCF standard error report encoding. * * Clients are expected to subclass <code>Command</code> and override <code>done</code> method. * * Note: most clients don't need to handle protocol commands directly and * can use service APIs instead. Service API does all command encoding/decoding * for a client. * * Typical usage example: * * public IToken getContext(String id, final DoneGetContext done) { * return new Command(channel, IService.this, "getContext", new Object[]{ id }) { * @Override * public void done(Exception error, Object[] args) { * Context ctx = null; * if (error == null) { * assert args.length == 2; * error = toError(args[0]); * if (args[1] != null) ctx = new Context(args[1]); * } * done.doneGetContext(token, error, ctx); * } * }.token; * } */ public abstract class Command implements IChannel.ICommandListener { private final IService service; private final String command; private final Object[] args; public final IToken token; private boolean done; private static final SimpleDateFormat timestamp_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); private static int arg_size_limit = Integer.MAX_VALUE; /** * Constructs a Command message on the given channel, belong to the given service, with the given command name, and with the given array of arguments * @param channel * @param service * @param command * @param args */ public Command(IChannel channel, IService service, String command, Object[] args) { this.service = service; this.command = command; this.args = args; IToken t = null; try { boolean zero_copy = ((AbstractChannel)channel).isZeroCopySupported(); t = channel.sendCommand(service, command, JSON.toJSONSequence(args, zero_copy), this); } catch (Throwable y) { t = new Token(); final Exception x = y instanceof Exception ? (Exception)y : new Exception(y); Protocol.invokeLater(new Runnable() { public void run() { assert !done; done = true; done(x, null); } }); } token = t; } /** * l */ public void progress(IToken token, byte[] data) { assert this.token == token; } public void result(IToken token, byte[] data) { assert this.token == token; Exception error = null; Object[] args = null; try { args = JSON.parseSequence(data); } catch (Exception e) { error = e; } assert !done; done = true; done(error, args); } public void terminated(IToken token, Exception error) { assert this.token == token; assert !done; done = true; done(error, null); } public abstract void done(Exception error, Object[] args); private int getArgSizeLimit() { if (arg_size_limit == Integer.MAX_VALUE) { arg_size_limit = 100; String name = "org.eclipse.tcf.core.errmsg_size_limit"; try { String s = System.getProperty(name); if (s != null) arg_size_limit = Integer.parseInt(s); } catch (NumberFormatException x) { Protocol.log("Invalid value of system property " + name, x); } } return arg_size_limit; } public String getCommandString(int arg_size_limit) { if (arg_size_limit <= 0) arg_size_limit = getArgSizeLimit(); StringBuffer buf = new StringBuffer(); buf.append(service.getName()); buf.append(' '); buf.append(command); if (args != null) { for (int i = 0; i < args.length; i++) { buf.append(i == 0 ? " " : ", "); try { String s = JSON.toJSON(args[i]); if (s.length() > arg_size_limit) { buf.append(s.substring(0, arg_size_limit)); buf.append("..."); } else { buf.append(s); } } catch (IOException x) { buf.append("***"); buf.append(x.getMessage()); buf.append("***"); } } } return buf.toString(); } public String getCommandString() { return getCommandString(Integer.MAX_VALUE); } @SuppressWarnings({ "unchecked" }) public static String toErrorString(Object data) { if (data == null) return null; Map<String,Object> map = (Map<String,Object>)data; String fmt = (String)map.get(IErrorReport.ERROR_FORMAT); if (fmt != null) { Collection<Object> c = (Collection<Object>)map.get(IErrorReport.ERROR_PARAMS); if (c != null) return new MessageFormat(fmt).format(c.toArray()); return fmt; } Number code = (Number)map.get(IErrorReport.ERROR_CODE); if (code != null) { if (code.intValue() == IErrorReport.TCF_ERROR_OTHER) { String alt_org = (String)map.get(IErrorReport.ERROR_ALT_ORG); Number alt_code = (Number)map.get(IErrorReport.ERROR_ALT_CODE); if (alt_org != null && alt_code != null) { return alt_org + " Error " + alt_code; } } return "TCF Error " + code; } return "Invalid error report format"; } static void appendErrorProps(StringBuffer bf, Map<String,Object> map) { Number time = (Number)map.get(IErrorReport.ERROR_TIME); Number code = (Number)map.get(IErrorReport.ERROR_CODE); String service = (String)map.get(IErrorReport.ERROR_SERVICE); Number severity = (Number)map.get(IErrorReport.ERROR_SEVERITY); Number alt_code = (Number)map.get(IErrorReport.ERROR_ALT_CODE); String alt_org = (String)map.get(IErrorReport.ERROR_ALT_ORG); if (time != null) { bf.append(" Time: "); bf.append(timestamp_format.format(new Date(time.longValue()))); bf.append('\n'); } if (severity != null) { bf.append(" Severity: "); switch (severity.intValue()) { case IErrorReport.SEVERITY_ERROR: bf.append("Error"); break; case IErrorReport.SEVERITY_FATAL: bf.append("Fatal"); break; case IErrorReport.SEVERITY_WARNING: bf.append("Warning"); break; default: bf.append("Unknown"); break; } bf.append('\n'); } bf.append(" Error text: "); bf.append(toErrorString(map)); bf.append('\n'); bf.append(" Error code: "); bf.append(code); bf.append('\n'); if (service != null) { bf.append(" Service: "); bf.append(service); bf.append('\n'); } if (alt_code != null) { bf.append(" Alt code: "); bf.append(alt_code); bf.append('\n'); if (alt_org != null) { bf.append(" Alt org: "); bf.append(alt_org); bf.append('\n'); } } } public Exception toError(Object data) { return toError(data, true); } @SuppressWarnings("unchecked") public Exception toError(Object data, boolean include_command_text) { if (data == null) return null; Map<String,Object> map = (Map<String,Object>)data; StringBuffer bf = new StringBuffer(); bf.append("TCF error report:\n"); if (include_command_text) { bf.append(" Command: "); bf.append(getCommandString(0)); bf.append('\n'); } appendErrorProps(bf, map); return new ErrorReport(bf.toString(), map); } }