1 25 package org.objectweb.jonas.management.j2eemanagement; 26 27 import java.io.ByteArrayOutputStream ; 28 import java.io.File ; 29 import java.io.IOException ; 30 import java.util.ArrayList ; 31 import java.util.Collections ; 32 import java.util.HashMap ; 33 import java.util.Iterator ; 34 import java.util.List ; 35 import java.util.Map ; 36 37 import javax.management.InstanceNotFoundException ; 38 import javax.management.JMException ; 39 import javax.management.ListenerNotFoundException ; 40 import javax.management.MBeanRegistration ; 41 import javax.management.MBeanServer ; 42 import javax.management.MBeanServerConnection ; 43 import javax.management.MBeanServerNotification ; 44 import javax.management.MalformedObjectNameException ; 45 import javax.management.Notification ; 46 import javax.management.NotificationListener ; 47 import javax.management.ObjectName ; 48 import javax.management.remote.JMXConnector ; 49 50 import org.objectweb.jonas.common.JProp; 51 import org.objectweb.jonas.common.Log; 52 import org.objectweb.jonas.discovery.DiscEvent; 53 import org.objectweb.jonas.discovery.DiscoveryListener; 54 import org.objectweb.jonas.jmx.J2eeObjectName; 55 import org.objectweb.jonas.jmx.JmxService; 56 import org.objectweb.util.monolog.api.BasicLevel; 57 import org.objectweb.util.monolog.api.Logger; 58 59 import java.io.FileInputStream ; 60 61 72 public class J2EEDomain extends J2EEManagedObject implements MBeanRegistration , NotificationListener { 73 74 private static final int BUFFER_SIZE = 1024; 75 76 79 private String name = null; 80 81 84 private List myServers = null; 85 86 89 private static Logger logger = null; 90 91 94 private MBeanServer mbeanServer = null; 95 96 99 private JmxService jmxService = null; 100 101 104 private Map managedServersToUrls = null; 105 106 114 public J2EEDomain(String objectName, boolean stateManageable, boolean statisticsProvider, boolean eventProvider) throws JMException { 115 super(objectName, stateManageable, statisticsProvider, eventProvider); 116 ObjectName myObjectName = ObjectName.getInstance(objectName); 117 name = myObjectName.getKeyProperty("name"); 118 myServers = Collections.synchronizedList(new ArrayList ()); 119 managedServersToUrls = new HashMap (); 120 logger = Log.getLogger("org.objectweb.jonas.management.j2eemanagement"); 121 if (logger.isLoggable(BasicLevel.DEBUG)) { 122 logger.log(BasicLevel.DEBUG, "J2EEDomain managed object created. OBJECT_NAME = " + objectName); 123 } 124 } 125 126 131 public String [] getServers() { 132 String [] sb = new String [myServers.size()]; 134 int i = 0; 135 for (Iterator it = myServers.iterator(); it.hasNext();) { 136 sb[i++] = (String ) it.next(); 137 } 138 return sb; 139 } 140 141 146 public String [] getServerNames() { 147 return (String []) managedServersToUrls.keySet().toArray(new String [managedServersToUrls.keySet().size()]); 148 } 149 150 155 public String [] getConnectorServerURLs(String serverName) { 156 return (String []) managedServersToUrls.get(serverName); 157 } 158 159 164 public void addServer(String serverOn, String [] connectorServerURLs) { 165 if (myServers.contains(serverOn)) { 166 if (logger.isLoggable(BasicLevel.DEBUG)) { 167 logger.log(BasicLevel.DEBUG, "The object name: " + serverOn + " is already in the servers list"); 168 } 169 } else { 170 myServers.add(serverOn); 171 172 try { 173 ObjectName on = ObjectName.getInstance(serverOn); 174 String name = on.getKeyProperty("name"); 175 managedServersToUrls.put(name, connectorServerURLs); 176 } catch (MalformedObjectNameException me) { 177 managedServersToUrls.put(name, null); 180 } 181 } 182 } 183 184 192 public String removeServer(String serverOn) { 193 int index = myServers.indexOf(serverOn); 194 if (index == -1) { 195 return null; 197 } else { 198 try { 199 ObjectName on = ObjectName.getInstance(serverOn); 200 String name = on.getKeyProperty("name"); 201 managedServersToUrls.remove(name); 202 } catch (MalformedObjectNameException me) { 203 return null; 206 207 } 208 return (String ) myServers.remove(index); 209 } 210 } 211 212 217 public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { 218 if (name == null) { 219 return J2eeObjectName.J2EEDomain(this.name); 220 } 221 String myName = getObjectName().toString(); 222 String argName = name.toString(); 223 if (myName.equals(argName)) { 224 mbeanServer = server; 226 return name; 227 } else { 228 if (logger.isLoggable(BasicLevel.WARN)) { 229 logger.log(BasicLevel.WARN, "Trying to register J2EEDomain MBean with the following name: " 230 + argName + " when the expected name is: " + myName); 231 } 232 return ObjectName.getInstance(this.name); 233 } 234 } 235 236 241 public void postRegister(Boolean registrationDone) { 242 if (registrationDone.booleanValue()) { 243 try { 244 ObjectName delegate = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate"); 246 mbeanServer.addNotificationListener(delegate, this, null, null); 247 } catch (JMException me) { 248 if (logger.isLoggable(BasicLevel.DEBUG)) { 249 logger.log(BasicLevel.DEBUG, "J2EEDomain MBean could not be added as notification listener for MBeanServerNotifications " 250 + "related to the REGISTRATION or UNREGISTRETION of JOnAS management MBeans"); 251 } 252 } 253 } 254 } 255 256 259 public void preDeregister() throws Exception { 260 262 } 263 264 267 public void postDeregister() { 268 270 } 271 272 276 public void handleNotification(Notification notification, Object handback) { 277 if (notification instanceof MBeanServerNotification ) { 278 handleMBeanServerNotification((MBeanServerNotification ) notification); 279 } else { 280 handleDiscoveryNotification(notification); 282 } 283 } 284 285 289 private void handleMBeanServerNotification(MBeanServerNotification notification) { 290 String type = notification.getType(); 291 ObjectName registeredOn = ((MBeanServerNotification ) notification).getMBeanName(); 293 String name = registeredOn.getKeyProperty("name"); 294 if ((name != null) && (name.equals("discoveryEnroller") || name.equals("discoveryClient"))) { 296 if (type.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) { 297 try { 298 mbeanServer.addNotificationListener(registeredOn, this, null, null); 300 if (logger.isLoggable(BasicLevel.DEBUG)) { 301 logger.log(BasicLevel.DEBUG, "J2EEDomain (this) registered as listener to notifs emitted by " + registeredOn); 302 } 303 } catch (InstanceNotFoundException e) { 304 e.printStackTrace(); 306 } 307 } 308 if (type.equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) { 309 try { 310 mbeanServer.removeNotificationListener(registeredOn, this); 312 if (logger.isLoggable(BasicLevel.DEBUG)) { 313 logger.log(BasicLevel.DEBUG, "J2EEDomain (this) removed listener for notifs emitted by " + registeredOn); 314 } 315 } catch (InstanceNotFoundException e) { 316 e.printStackTrace(); 318 } catch (ListenerNotFoundException e) { 319 e.printStackTrace(); 321 } 322 } 323 } 324 } 325 326 330 private void handleDiscoveryNotification(Notification notification) { 331 String type = notification.getType(); 333 if (type.equals(DiscoveryListener.DISCOVERY_TYPE)) { 334 DiscEvent userData = (DiscEvent) notification.getUserData(); 335 String domainName = userData.getDomainName(); 336 if (domainName.equals(name)) { 337 String serverName = userData.getServerName(); 338 ObjectName on = J2eeObjectName.J2EEServer(domainName, serverName); 339 String state = userData.getState(); 340 if (state.equals(DiscEvent.RUNNING)) { 341 addServer(on.toString(), userData.getConnectorURL()); 342 } else if (state.equals(DiscEvent.STOPPING)) { 343 removeServer(on.toString()); 344 } 345 } 346 } 347 } 348 349 360 static final private String RUNNING = "running"; 361 static final private String COMPLETED = "completed"; 362 static final private String FAILED = "failed"; 363 static final private String DEPLOYED = "deployed"; 364 static final private String UNDEPLOYED = "undeployed"; 365 366 static final private String DEPLOY = "deploy"; 367 static final private String UNDEPLOY = "undeploy"; 368 static final private String UPLOAD_DEPLOY = "uploadDeploy"; 369 static final private String UPLOAD = "upload"; 370 371 static final private boolean SUCCESS = true; 372 static final private boolean FAILURE = false; 373 374 private String progress = null; 375 private HashMap progressReport = null; 376 private String globalErrorReport = null; 377 378 381 public String getProgress() { 382 return progress; 383 } 384 385 388 private void setProgress(String progress) { 389 this.progress = progress; 390 } 391 392 395 public void resetProgress() { 396 progress = null; 397 } 398 399 406 public boolean deployJar(String [] target, String fileName) { 407 if (isAnotherOperationRunning()) { 408 return false; 409 } 410 411 String opName = "deployJar"; 412 String [] params = {fileName }; 413 String [] signature = {"java.lang.String" }; 414 return doDeploymentOperation(opName, params, signature, target, fileName, DEPLOY, false); 415 } 416 417 424 public boolean unDeployJar(String [] target, String fileName) { 425 if (isAnotherOperationRunning()) { 426 return false; 427 } 428 429 String opName = "unDeployJar"; 430 String [] params = {fileName }; 431 String [] signature = {"java.lang.String" }; 432 return doDeploymentOperation(opName, params, signature, target, fileName, UNDEPLOY, false); 433 } 434 435 442 public boolean deployWar(String [] target, String fileName) { 443 if (isAnotherOperationRunning()) { 444 return false; 445 } 446 447 String opName = "deployWar"; 448 String [] params = {fileName }; 449 String [] signature = {"java.lang.String" }; 450 return doDeploymentOperation(opName, params, signature, target, fileName, DEPLOY, false); 451 } 452 453 460 public boolean unDeployWar(String [] target, String fileName) { 461 if (isAnotherOperationRunning()) { 462 return false; 463 } 464 465 String opName = "unDeployWar"; 466 String [] params = {fileName }; 467 String [] signature = {"java.lang.String" }; 468 return doDeploymentOperation(opName, params, signature, target, fileName, UNDEPLOY, false); 469 } 470 471 478 public boolean deployRar(String [] target, String fileName) { 479 if (isAnotherOperationRunning()) { 480 return false; 481 } 482 483 String opName = "deployRar"; 484 String [] params = {fileName }; 485 String [] signature = {"java.lang.String" }; 486 return doDeploymentOperation(opName, params, signature, target, fileName, DEPLOY, false); 487 } 488 489 496 public boolean unDeployRar(String [] target, String fileName) { 497 if (isAnotherOperationRunning()) { 498 return false; 499 } 500 501 String opName = "unDeployRar"; 502 String [] params = {fileName }; 503 String [] signature = {"java.lang.String" }; 504 return doDeploymentOperation(opName, params, signature, target, fileName, UNDEPLOY, false); 505 } 506 507 514 public boolean deployEar(String [] target, String fileName) { 515 if (isAnotherOperationRunning()) { 516 return false; 517 } 518 519 String opName = "deployEar"; 520 String [] params = {fileName }; 521 String [] signature = {"java.lang.String" }; 522 return doDeploymentOperation(opName, params, signature, target, fileName, DEPLOY, false); 523 } 524 525 532 public boolean unDeployEar(String [] target, String fileName) { 533 if (isAnotherOperationRunning()) { 534 return false; 535 } 536 537 String opName = "unDeployEar"; 538 String [] params = {fileName }; 539 String [] signature = {"java.lang.String" }; 540 return doDeploymentOperation(opName, params, signature, target, fileName, UNDEPLOY, false); 541 } 542 543 551 public boolean uploadDeployEar(String [] target, String fileName, boolean replaceExisting) { 552 if (isAnotherOperationRunning()) { 553 return false; 554 } 555 556 String opName = "reDeployEar"; 557 String [] params = {fileName }; 558 String [] signature = {"java.lang.String" }; 559 return doDeploymentOperation(opName, params, signature, target, fileName, UPLOAD_DEPLOY, replaceExisting); 560 } 561 562 570 public boolean uploadDeployWar(String [] target, String fileName, boolean replaceExisting) { 571 if (isAnotherOperationRunning()) { 572 return false; 573 } 574 575 String opName = "reDeployWar"; 576 String [] params = {fileName }; 577 String [] signature = {"java.lang.String" }; 578 return doDeploymentOperation(opName, params, signature, target, fileName, UPLOAD_DEPLOY, replaceExisting); 579 } 580 581 589 public boolean uploadDeployJar(String [] target, String fileName, boolean replaceExisting) { 590 if (isAnotherOperationRunning()) { 591 return false; 592 } 593 594 String opName = "reDeployJar"; 595 String [] params = {fileName }; 596 String [] signature = {"java.lang.String" }; 597 return doDeploymentOperation(opName, params, signature, target, fileName, UPLOAD_DEPLOY, replaceExisting); 598 } 599 600 608 public boolean uploadDeployRar(String [] target, String fileName, boolean replaceExisting) { 609 if (isAnotherOperationRunning()) { 610 return false; 611 } 612 613 String opName = "reDeployRar"; 614 String [] params = {fileName }; 615 String [] signature = {"java.lang.String" }; 616 return doDeploymentOperation(opName, params, signature, target, fileName, UPLOAD_DEPLOY, replaceExisting); 617 } 618 619 627 public boolean uploadFile(String [] target, String fileName, boolean replaceExisting) { 628 if (isAnotherOperationRunning()) { 629 return false; 630 }; 631 632 String opName = "uploadFile"; 633 String [] params = {fileName}; 634 String [] signature = {"java.lang.String" }; 635 return doDeploymentOperation(opName, params, signature, target, fileName, UPLOAD, replaceExisting); 636 } 637 638 662 private boolean doDeploymentOperation(String opName, Object [] opParams, String [] opSignature, String [] target, String fileName, String opType, boolean replaceExisting) { 663 progressReport = new HashMap (); 664 if (isTargetCorrect(target)) { 666 MBeanServerConnection connection = null; 667 JMXConnector connector = null; 668 for (int i = 0; i < target.length; i++) { 669 ObjectName targetOn = null; 670 try { 671 targetOn = ObjectName.getInstance(target[i]); 672 } catch (MalformedObjectNameException me) { 673 setProgress(FAILED); 675 continue; 676 } 677 String serverName = targetOn.getKeyProperty("name"); 678 connection = jmxService.getServerConnection(serverName); 679 try { 680 testConnection(connection, serverName); 681 try { 682 if (opType.equals(DEPLOY)) { 683 doDeploy(connection, 684 targetOn, 685 opName, 686 opParams, 687 opSignature, 688 fileName); 689 } else if (opType.equals(UNDEPLOY)) { 690 doUnDeploy(connection, 691 targetOn, 692 opName, 693 opParams, 694 opSignature, 695 fileName); 696 } else if (opType.equals(UPLOAD_DEPLOY)) { 697 doUploadReDeploy(connection, 698 targetOn, 699 opName, 700 opParams, 701 opSignature, 702 fileName, 703 replaceExisting); 704 } else if (opType.equals(UPLOAD)) { 705 doUpload(connection, 706 targetOn, 707 fileName, 708 replaceExisting); 709 } 710 } catch (Exception e) { 711 setProgress(FAILED); 712 HashMap status = new HashMap (); 713 status.put(e.toString(), Boolean.valueOf(FAILURE)); 714 progressReport.put(serverName, status); 715 continue; 716 } 717 } catch (Exception e) { 718 setProgress(FAILED); 719 HashMap status = new HashMap (); 720 status.put("Could not connect to: " + 721 serverName + ". Error: " + e.toString(), Boolean.valueOf(FAILURE)); 722 progressReport.put(serverName, status); 723 continue; 724 } 725 } 726 if (!getProgress().equals(FAILED)) { 727 setProgress(COMPLETED); 728 } 729 } else { 730 setProgress(FAILED); 731 } 732 return true; 733 734 } 735 736 747 private void doDeploy(MBeanServerConnection connection, ObjectName targetOn, 748 String opName, Object [] opParams, String [] opSignature, 749 String fileName) 750 throws Exception { 751 752 String serverName = targetOn.getKeyProperty("name"); 753 754 if (!isDeployed(connection, targetOn, opName, opParams, opSignature)) { 755 756 connection.invoke(targetOn, opName, opParams, opSignature); 757 758 if (logger.isLoggable(BasicLevel.DEBUG)) { 759 logger.log(BasicLevel.DEBUG, "Opearation " 760 + opName + "'" + fileName 761 + ") on target " + serverName 762 + " done"); 763 } 764 HashMap status = new HashMap (); 766 status.put(DEPLOYED, new Boolean (SUCCESS)); 767 progressReport.put(serverName, status); 768 } else { 769 HashMap status = new HashMap (); 771 status.put("File is already deployed.", new Boolean (FAILURE)); 772 progressReport.put(serverName, status); 773 } 774 } 775 776 787 private void doUnDeploy(MBeanServerConnection connection, ObjectName targetOn, 788 String opName, Object [] opParams, String [] opSignature, String fileName) 789 throws Exception { 790 791 String serverName = targetOn.getKeyProperty("name"); 792 793 if (isDeployed(connection, targetOn, opName, opParams, opSignature)) { 794 795 connection.invoke(targetOn, opName, opParams, opSignature); 796 797 if (logger.isLoggable(BasicLevel.DEBUG)) { 798 logger.log(BasicLevel.DEBUG, "Opearation " 799 + opName + "'" + fileName 800 + ") on target " + serverName 801 + " done"); 802 } 803 HashMap status = new HashMap (); 804 status.put(UNDEPLOYED, Boolean.valueOf(SUCCESS)); 805 progressReport.put(serverName, status); 806 } else { 807 HashMap status = new HashMap (); 808 status.put("File is not deployed.", Boolean.valueOf(FAILURE)); 809 progressReport.put(serverName, status); 810 } 811 } 812 813 825 private void doUploadReDeploy(MBeanServerConnection connection, ObjectName targetOn, 826 String opName, Object [] opParams, String [] opSignature, String fileName, boolean replaceExisting) 827 throws Exception { 828 829 String serverName = targetOn.getKeyProperty("name"); 830 String uploadedName = fileName; 831 832 try { 833 uploadedName = doUpload(connection, targetOn, fileName, replaceExisting); 834 } catch (Exception e) { 835 HashMap status = new HashMap (); 836 status.put("File failed to upload: " + e.toString(), Boolean.valueOf(FAILURE)); 837 progressReport.put(serverName, status); 838 return; 839 } 840 841 opParams[0] = uploadedName; 845 846 boolean isDeployed = isDeployed(connection, targetOn, opName, opParams, opSignature); 847 if (isDeployed && replaceExisting) { 849 doUnDeploy(connection, targetOn, getUnDeployType(opName), opParams, opSignature, fileName); 850 doDeploy(connection, targetOn, getDeployType(opName), opParams, opSignature, fileName); 851 } 852 else if (!isDeployed) { 854 doDeploy(connection,targetOn, getDeployType(opName), opParams, opSignature, fileName); 855 } else { 856 } 858 } 859 860 870 private String doUpload(MBeanServerConnection connection, ObjectName targetOn, String fileName, boolean replaceExisting) throws Exception { 871 872 String opName = "sendFile"; 873 874 File file = null; 875 file = new File (fileName).getCanonicalFile(); 877 String deployName = file.getName(); 878 if (!file.exists()) { 879 String dir = getFolderDir(fileName); 881 file = new File (dir, fileName); 882 deployName = fileName; 883 } 884 885 FileInputStream inputStream = new FileInputStream (file); 886 ByteArrayOutputStream baos = new ByteArrayOutputStream (); 887 byte[] buf = new byte[BUFFER_SIZE]; 888 int len; 890 while ((len = inputStream.read(buf)) > 0) { 891 baos.write(buf, 0, len); 892 } 893 byte[] bytesOfFile = baos.toByteArray(); 894 895 Object [] opParams = new Object []{bytesOfFile, deployName, Boolean.valueOf(replaceExisting)}; 896 String [] opSignature = new String []{"[B", "java.lang.String", "boolean"}; 897 String filePath = (String ) connection.invoke(targetOn, opName, opParams, opSignature); 898 899 String serverName = targetOn.getKeyProperty("name"); 900 HashMap status = new HashMap (); 901 status.put("File uploaded to: '" + filePath + "'" , Boolean.valueOf(SUCCESS)); 902 progressReport.put(serverName, status); 903 904 return deployName; 905 } 906 907 914 private String getFolderDir(String fileName) throws Exception { 915 String jBase = JProp.getJonasBase(); 916 String dir = null; 918 if (fileName.toLowerCase().endsWith(".jar")) { 920 dir = jBase + File.separator + "ejbjars"; 921 } else if (fileName.toLowerCase().endsWith(".war")) { 922 dir = jBase + File.separator + "webapps"; 924 } else if (fileName.toLowerCase().endsWith(".ear")) { 925 dir = jBase + File.separator + "apps"; 927 } else if (fileName.toLowerCase().endsWith(".rar")) { 928 dir = jBase + File.separator + "rars"; 930 } else { 931 throw new Exception ("Invalid extension for the file '" + fileName 933 + "'. Valid are .jar, .war, .ear, .rar"); 934 } 935 return dir; 936 } 937 938 949 private boolean isDeployed(MBeanServerConnection connection, 950 ObjectName targetOn, String opName, Object [] opParams, 951 String [] opSignature) throws Exception { 952 953 Boolean deployed = (Boolean ) connection.invoke(targetOn, 954 getIsDeployedType(opName), opParams, opSignature); 955 956 return deployed.booleanValue(); 957 } 958 959 964 private String getIsDeployedType(String opName) { 965 String result = ""; 966 if (opName.indexOf("Ear") != -1) { 967 result = "isEarDeployed"; 968 } else if (opName.indexOf("War") != -1) { 969 result = "isWarDeployed"; 970 } else if (opName.indexOf("Rar") != -1) { 971 result = "isRarDeployed"; 972 } else if (opName.indexOf("Jar") != -1) { 973 result = "isJarDeployed"; 974 } 975 return result; 976 } 977 978 983 private String getUnDeployType(String opName) { 984 String result = ""; 985 if (opName.indexOf("Ear") != -1) { 986 result = "unDeployEar"; 987 } else if (opName.indexOf("War") != -1) { 988 result = "unDeployWar"; 989 } else if (opName.indexOf("Rar") != -1) { 990 result = "unDeployRar"; 991 } else if (opName.indexOf("Jar") != -1) { 992 result = "unDeployJar"; 993 } 994 return result; 995 } 996 997 1002 private String getDeployType(String opName) { 1003 String result = ""; 1004 if (opName.indexOf("Ear") != -1) { 1005 result = "deployEar"; 1006 } else if (opName.indexOf("War") != -1) { 1007 result = "deployWar"; 1008 } else if (opName.indexOf("Rar") != -1) { 1009 result = "deployRar"; 1010 } else if (opName.indexOf("Jar") != -1) { 1011 result = "deployJar"; 1012 } 1013 return result; 1014 } 1015 1016 1024 private boolean isAnotherOperationRunning() { 1025 if (progress == null) { 1026 setProgress(RUNNING); 1027 } else { 1028 if (!getProgress().equals(RUNNING)) { 1029 setProgress(RUNNING); 1030 } else { 1031 return true; 1032 } 1033 } 1034 return false; 1035 } 1036 1037 1042 private boolean isTargetCorrect(String [] target) { 1043 for (int i = 0; i < target.length; i++) { 1044 if (!myServers.contains(target[i])) { 1045 setGlobalErrorReport("Target element " + target[i] + " is not registered in J2EEDomain MBean " + getObjectName()); 1046 return false; 1047 } 1048 } 1049 return true; 1050 } 1051 1052 1058 private void testConnection(MBeanServerConnection connection, String serverName) throws IOException { 1059 Integer nb = connection.getMBeanCount(); 1060 } 1062 1063 1066 public JmxService getJmxService() { 1067 return jmxService; 1068 } 1069 1070 1073 public void setJmxService(JmxService jmxService) { 1074 this.jmxService = jmxService; 1075 } 1076 1077 1080 public HashMap getProgressReport() { 1081 return progressReport; 1082 } 1083 1084 1087 public void setProgressReport(HashMap report) { 1088 this.progressReport = report; 1089 } 1090 1091 1094 public String getGlobalErrorReport() { 1095 return globalErrorReport; 1096 } 1097 1098 1101 public void setGlobalErrorReport(String globalErrorReport) { 1102 this.globalErrorReport = globalErrorReport; 1103 } 1104} 1105 | Popular Tags |