001/* 002 * $RCSfile: Target.java,v $ 003 * $Revision: 1.1.2.27 $ $Date: 2014/10/10 20:09:34 $ 004 * 005 * Copyright 2009-2014 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 006 * Cloud Software Group, Inc. Confidential Information 007 */ 008package com.kabira.platform.management; 009 010import com.kabira.platform.KeyFieldValueList; 011import com.kabira.platform.KeyManager; 012import com.kabira.platform.KeyQuery; 013import com.kabira.platform.LockMode; 014import com.kabira.platform.ManagedObject; 015import com.kabira.platform.ObjectNotUniqueError; 016import com.kabira.platform.OutParameter; 017import com.kabira.platform.swbuiltin.AdminReport; 018import com.kabira.platform.switchadmin.CommandStatus; 019import com.kabira.platform.switchadmin.JavaCommand; 020import com.kabira.ktvm.transaction.DeadlockError; 021 022import java.nio.charset.Charset; 023import java.io.PrintWriter; 024import java.io.StringWriter; 025import java.lang.annotation.Annotation; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.util.HashMap; 029import java.util.logging.Logger; 030 031/** 032 * System management targets allow developers to extend the system 033 * management framework. System management targets are accessible from 034 * JMX, the command line using the "administrator" command, and from 035 * the adminstrative web interface. 036 * System management targets are defined by extending Target. 037 * Note that Target is a managed object, and is always called by the system 038 * management framework with an active transaction. 039 * <h2> 040 * Management target implementation 041 * </h2> 042 * Targets may return result sets, which consist of a list of column names and 043 * any number of rows. All rows must contain the same number of m_columns. 044 * setColumnNames() must be called before addRow(). setColumnNames may be 045 * called only once, or a TargetError 046 * will be thrown. After setColumnNames() is called, addRow() may be called 047 * any number of times. The number of m_columns in each row must match the 048 * number of column names, or TargetError will be thrown. 049 * Targets are registered by calling Target.register(). A new instance of the 050 * Target is created for every command invocation. Targets implementing 051 * asynchronous commands may store command-context-specific data in private 052 * members. 053 * 054 * <h2>Execution JVM</h2> 055 * 056 * All commands associated with an administrative target 057 * execute in the JVM in which the target was registered 058 * using the register() method. 059 * 060 * <h2>Synchronous Commands</h2> 061 * 062 * Any command which calls commandComplete() or commandFailed() before returning 063 * is a synchronous command. 064 * 065 * <h2>Asynchronous commands</h2> 066 * 067 * Commands may return without completing. In this case, the 068 * command will be held open until commandComplete() or commandFailed() is 069 * called. The commandComplete() or commandFailed() method must be 070 * called on the same Target instance on which the asynchronous command was 071 * initiated. 072 * Note that asynchronous commands which are abandoned 073 * (e.g. the client never collects the results) will cause a leak in the 074 * underlying framework. 075 * 076 * <h2>Transactions</h2> 077 * 078 * Command methods are always called with a valid transaction. 079 * The transaction is committed after the method returns, unless an exception is 080 * thrown, in which case the transaction is rolled back. Asynchronous commands 081 * must start a transaction to call back into the Target to complete the 082 * command. 083 * 084 * <h2>Security Configuration</h2> 085 * 086 * Management targets use configuration to define access 087 * control on the methods exposed as commands on the target. 088 * 089 * Here's an example of a security configuration file for ExampleTarget: 090 * <pre> 091 * configuration "ExampleTargetSecurity" version "1" type "security" 092 * { 093 * configure security 094 * { 095 * configure AccessControl 096 * { 097 * Rule 098 * { 099 * name = "com.kabira.platform.management.ExampleTarget"; 100 * lockAllElements = true; 101 * accessRules = { }; 102 * } 103 * Rule 104 * { 105 * name = "com.kabira.platform.management.ExampleTarget.examplecommand"; 106 * 107 * accessRules = 108 * { 109 * { 110 * roleName = "switchmonitor"; 111 * permission = Execute; 112 * } 113 * }; 114 * }; 115 * }; 116 * }; 117 * }; 118 * </pre> 119 */ 120 121public class Target extends JavaCommand 122{ 123 AdminReport adminReport = null; 124 private CommandStatus m_commandStatus = CommandStatus.CommandInProgress; 125 private long m_columns = 0; 126 private static final Charset ResultsCharset = Charset.forName("ISO-8859-1"); 127 128 /** 129 * Register a Target. 130 * <p> 131 * All commands associated with the target being registered will 132 * execute in the JVM in which register was called. 133 * @param targetClass the class of the target, e.g. MyTarget.class 134 * @throws TargetError The target failed registration audit. 135 * 136 */ 137 public static void register(Class<?> targetClass) 138 throws TargetError 139 { 140 String targetName = getTargetName(targetClass); 141 142 Target.audit(targetClass, targetName); 143 144 // 145 // Always unregister the target name before registering it, since 146 // a Factory created in a previous instance of the engine will not 147 // be accessible. 148 // 149 unregister(targetName); 150 151 try 152 { 153 Factory factory = new Factory(targetClass, targetName); 154 } 155 catch (ObjectNotUniqueError ex) 156 { 157 // two callers got here at once. swallow this 158 // error. the other caller won. 159 } 160 } 161 162 /** 163 * Unregister a previously-registered target. 164 * @param targetName the name of the target 165 */ 166 public static void unregister(String targetName) 167 { 168 KeyManager<Factory> keyManager = new KeyManager<Factory>(); 169 KeyQuery<Factory> keyQuery = keyManager.createKeyQuery( 170 Factory.class, "ByName"); 171 KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); 172 173 // 174 // Define the query to find the Factory 175 // 176 keyFieldValueList.add("name", targetName); 177 keyQuery.defineQuery(keyFieldValueList); 178 179 Factory factory = keyQuery.getSingleResult(LockMode.WRITELOCK); 180 181 if (factory != null) 182 { 183 ManagedObject.delete(factory); 184 } 185 } 186 187 /** 188 * Indicate that the command has completed successfully. 189 */ 190 @SuppressWarnings("deprecation") 191 protected final void commandComplete() 192 { 193 byte [] results = null; 194 195 m_commandStatus = CommandStatus.CommandCompleted; 196 197 if (adminReport != null) 198 { 199 results = adminReport.getReport(); 200 201 ManagedObject.delete(adminReport); 202 adminReport = null; 203 } 204 else 205 { 206 results = new byte[0]; 207 } 208 209 update(m_commandStatus, results, ""); 210 } 211 212 /** 213 * Indicate that the command has failed. 214 * The Transaction will be 215 * committed. To roll back the transaction, throw an exception from the 216 * method implementing the command. 217 * @param reason The reason the command failed. 218 */ 219 protected final void commandFailed(String reason) 220 { 221 m_commandStatus = CommandStatus.CommandFailed; 222 223 byte [] results = new byte[0]; 224 225 update(m_commandStatus, results, reason); 226 } 227 228 /** 229 * Set the names of the m_columns for the result set. 230 * @param names The names of the m_columns for the result set 231 * @throws TargetError The method was called more than once 232 */ 233 protected final void setColumnNames(String[] names) 234 throws TargetError 235 { 236 if (adminReport != null) 237 { 238 throw new TargetError("setColumnNames called twice"); 239 } 240 if (names.length == 0) 241 { 242 throw new TargetError("zero column names provided"); 243 } 244 245 adminReport = new AdminReport(); 246 247 adminReport.setHeader(names); 248 249 m_columns = names.length; 250 } 251 252 /** 253 * Add a row to the result set 254 * @param values The list of values for this row 255 * @throws TargetError The number of values does not match 256 * the number of column names, or setColumnNames() has not been called yet. 257 */ 258 protected final void addRow(String[] values) 259 throws TargetError 260 { 261 if (adminReport == null) 262 { 263 throw new TargetError( 264 "addRow called without calling setColumnNames"); 265 } 266 267 if (values.length != m_columns) 268 { 269 270 throw new TargetError( 271 "addRow: number of values (" + values.length + 272 ") does not match number of columns (" + m_columns + 273 ")"); 274 } 275 276 adminReport.addRecord(values); 277 } 278 279 /** 280 * Execute a command. This is an internal method which should never 281 * be called. 282 * @param parameters the command parameters 283 */ 284 // 285 // unchecked conversion warnings are suppressed for Enum.valueOf(), 286 // which cannot be fixed because generics are not covariant. 287 // 288 @SuppressWarnings("unchecked") 289 @Override 290 protected final void execute(final String[] parameters) 291 { 292 if (commandName.equals("help")) 293 { 294 help(); 295 return; 296 } 297 298 // 299 // Check that comand is valid before checking access permissions 300 // (FLUENCY-5445). 301 // 302 Method method = null; 303 304 for (Method m : this.getClass().getDeclaredMethods()) 305 { 306 if ((m.getAnnotation(Command.class) != null) && 307 (m.getName().equals(commandName))) 308 { 309 method = m; 310 break; 311 } 312 } 313 314 if (method == null) 315 { 316 commandFailed("command '" + commandName + 317 "' not found in target '" + targetName + "'"); 318 return; 319 } 320 321 // 322 // Check to see if the current user has permission to execute 323 // this command 324 // 325 Boolean ok = false; 326 ok = checkAccess(this.getClass().getName() + "." + commandName); 327 if (!ok) 328 { 329 String msg = "An access violation occurred while " + 330 "attempting to execute command [" + commandName + 331 "] on target [" + targetName + 332 "]. User " + getCurrentPrincipalName() + 333 " does not have execute privileges for this command."; 334 throw new SecurityException(msg); 335 } 336 337 Annotation[][] all = method.getParameterAnnotations(); 338 Class<?> pt[] = method.getParameterTypes(); 339 Object[] args = new Object[pt.length]; 340 341 HashMap<String, String> paramap = new HashMap<String, String>(); 342 343 for (int pi = 0; pi < parameters.length;) 344 { 345 paramap.put(parameters[pi++], parameters[pi++]); 346 } 347 348 // 349 // for each of the method's parameters, find the supplied parameter 350 // or use the default (if it isn't required) 351 // 352 for (int i = 0; i < pt.length; i++) 353 { 354 Parameter pp = null; 355 Default defaultValue = null; 356 357 // 358 // find the Parameter annotation for this parameter 359 // 360 for (Annotation a : all[i]) 361 { 362 if (a.annotationType() == Parameter.class) 363 { 364 pp = (Parameter) a; 365 } 366 } 367 368 String name = pp.name(); 369 String value = paramap.get(name); 370 371 if (value != null) 372 { 373 paramap.remove(name); 374 } 375 else 376 { 377 if (pp.defaultValue().provided()) 378 { 379 value = pp.defaultValue().value(); 380 } 381 else if (pp.required() == true) 382 { 383 commandFailed("required parameter " + 384 pp.name() + " missing"); 385 return; 386 } 387 } 388 389 Class type = pt[i]; 390 391 try 392 { 393 if (value == null) 394 { 395 args[i] = null; 396 } 397 else if (type == String.class) 398 { 399 args[i] = value; 400 } 401 else if (type.isEnum()) 402 { 403 // 404 // unchecked warnings if not suppressed 405 // 406 args[i] = Enum.valueOf(type, value); 407 } 408 else if (type == Boolean.class) 409 { 410 args[i] = Boolean.valueOf(value); 411 } 412 else if (type == Character.class) 413 { 414 args[i] = Character.valueOf(value.charAt(0)); 415 } 416 else if (type == Integer.class) 417 { 418 args[i] = Integer.parseInt(value); 419 } 420 else if (type == Byte.class) 421 { 422 args[i] = Byte.parseByte(value); 423 } 424 else if (type == Short.class) 425 { 426 args[i] = Short.parseShort(value); 427 } 428 else if (type == Long.class) 429 { 430 args[i] = Long.parseLong(value); 431 } 432 else if (type == Float.class) 433 { 434 args[i] = Float.parseFloat(value); 435 } 436 else 437 { 438 assert (type == Double.class); 439 args[i] = Double.parseDouble(value); 440 } 441 } 442 catch (NumberFormatException ex) 443 { 444 commandFailed("Invalid numeric format '" + value + 445 "' for parameter '" + name + ": " + 446 ex.getMessage()); 447 return; 448 } 449 catch (IllegalArgumentException ex) 450 { 451 commandFailed("Illegal argument '" + value + 452 "' for parameter '" + name + ": " + 453 ex.getMessage()); 454 return; 455 } 456 } 457 458 if (!paramap.isEmpty()) 459 { 460 commandFailed("invalid parameter(s): " + paramap.toString()); 461 return; 462 } 463 464 try 465 { 466 method.setAccessible(true); 467 method.invoke(this, args); 468 } 469 catch (InvocationTargetException ex) 470 { 471 String reason = "InvocationTargetException: "; 472 473 Throwable cause = ex.getCause(); 474 475 if (cause != null) 476 { 477 if (cause instanceof DeadlockError) 478 { 479 throw (java.lang.Error)cause; 480 } 481 482 reason = cause.getMessage(); 483 484 StringWriter stringWriter = new StringWriter(); 485 PrintWriter printWriter = new PrintWriter(stringWriter); 486 cause.printStackTrace(printWriter); 487 488 reason += stringWriter.toString(); 489 } 490 else 491 { 492 reason += ex.getMessage(); 493 } 494 495 setAbortFlag(); 496 commandFailed(reason); 497 } 498 catch (Exception ex) 499 { 500 ex.printStackTrace(); 501 setAbortFlag(); 502 commandFailed(ex.getMessage()); 503 } 504 } 505 506 /** 507 * Retrieve the name of the currently active Principal. This operation 508 * may be called during command execution to get the name of the Principal 509 * that invoked the command. 510 */ 511 protected final String getActivePrincipalName() 512 { 513 String result = getCurrentPrincipalName(); 514 515 if ((result == null) || (result.length() == 0)) 516 { 517 return null; 518 } 519 520 return result; 521 } 522 523 private static String getTargetName(Class<?> targetClass) 524 { 525 ManagementTarget adminTarget = 526 targetClass.getAnnotation(ManagementTarget.class); 527 528 if (adminTarget == null) 529 { 530 throw new TargetError("Missing ManagementTarget annotation on type " 531 + targetClass.getName() + ". "); 532 } 533 return adminTarget.name(); 534 } 535 536 private static void audit(Class<?> targetClass, String targetName) 537 throws TargetError 538 { 539 String error = new String(); 540 541 for (Method m : targetClass.getMethods()) 542 { 543 if (m.getAnnotation(Command.class) == null) 544 { 545 continue; 546 } 547 548 Annotation[][] all = m.getParameterAnnotations(); 549 Class pt[] = m.getParameterTypes(); 550 551 for (int i = 0; i < pt.length; i++) 552 { 553 Parameter pp = null; 554 Default defaultValue = null; 555 556 Class<?> type = pt[i]; 557 558 if ((type != String.class) && 559 (!type.isEnum()) && 560 (type != Boolean.class) && 561 (type != Character.class) && 562 (type != Integer.class) && 563 (type != Byte.class) && 564 (type != Short.class) && 565 (type != Long.class) && 566 (type != Float.class) && 567 (type != Double.class)) 568 { 569 error += "Unsupported parameter type " + 570 type.getName() + " in method " + m.getName() + ". "; 571 } 572 573 // 574 // find the Parameter annotation for this parameter 575 // 576 for (Annotation a : all[i]) 577 { 578 if (a.annotationType() == Parameter.class) 579 { 580 pp = (Parameter) a; 581 } 582 } 583 if (pp == null) 584 { 585 error += "Missing @Parameter annotation on parameter " + 586 "of type " + type.getName() + " in method " + 587 m.getName() + ". "; 588 } 589 } 590 } 591 592 if (!error.isEmpty()) 593 { 594 throw new TargetError("Admin target " + targetName + 595 " failed audit: " + error); 596 } 597 } 598 599 private void help() 600 { 601 String help = new String(); 602 603 help += " valid commands and parameters for target \""; 604 help += targetName; 605 help += "\":\n\n"; 606 607 for (Method m : this.getClass().getDeclaredMethods()) 608 { 609 if (m.getName().charAt(0) == '$') 610 { 611 continue; 612 } 613 614 Command command = m.getAnnotation(Command.class); 615 616 if (command == null) 617 { 618 continue; 619 } 620 621 help += " " + m.getName() + " " + targetName + "\n"; 622 623 Annotation[][] all = m.getParameterAnnotations(); 624 Class pt[] = m.getParameterTypes(); 625 626 for (int i = 0; i < pt.length; i++) 627 { 628 Parameter pp = null; 629 Class<?> type = pt[i]; 630 631 // 632 // find the Parameter annotation for this parameter 633 // 634 for (Annotation a : all[i]) 635 { 636 if (a.annotationType() == Parameter.class) 637 { 638 pp = (Parameter) a; 639 } 640 } 641 642 String ps = pp.name() + "=<" + 643 type.getName().replace('.', ' ').replaceAll(".* ", ""); 644 645 if (pp.defaultValue().provided()) 646 { 647 ps += ", default = " + pp.defaultValue().value(); 648 } 649 ps += ">"; 650 651 if (!pp.required()) 652 { 653 ps = "[ " + ps + " ]"; 654 } 655 656 help += "\t" + ps + "\n"; 657 if (!pp.description().equals("")) 658 { 659 help += "\t\t" + pp.description() + "\n"; 660 } 661 help += "\n"; 662 } 663 664 if (!command.description().equals("")) 665 { 666 help += "\t" + command.description() + "\n"; 667 } 668 help += "\n"; 669 } 670 671 ManagementTarget adminTarget = 672 this.getClass().getAnnotation(ManagementTarget.class); 673 674 if (adminTarget != null) 675 { 676 help += "\nDescription:\n\n" + adminTarget.description() + "\n"; 677 } 678 679 update(CommandStatus.CommandCompleted, 680 help.getBytes(ResultsCharset), ""); 681 } 682}