/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jms.admin.cli;
import com.sun.appserv.connectors.internal.api.ConnectorsUtil;
import com.sun.enterprise.config.serverbeans.*;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.SystemPropertyConstants;
import org.glassfish.api.ActionReport;
import org.glassfish.api.I18n;
import org.glassfish.api.Param;
import org.glassfish.api.admin.*;
import org.glassfish.config.support.CommandTarget;
import org.glassfish.config.support.TargetType;
import org.glassfish.connectors.config.AdminObjectResource;
import org.glassfish.connectors.config.ConnectorConnectionPool;
import org.glassfish.connectors.config.ConnectorResource;
import org.glassfish.hk2.api.PerLookup;
import org.jvnet.hk2.annotations.Service;
import javax.inject.Inject;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
/**
* Create JMS Resource Command
*
*/
@Service(name="create-jms-resource")
@PerLookup
@I18n("create.jms.resource")
@ExecuteOn({RuntimeType.DAS})
@TargetType({CommandTarget.DAS,CommandTarget.STANDALONE_INSTANCE,CommandTarget.CLUSTER,CommandTarget.DOMAIN})
@RestEndpoints({
@RestEndpoint(configBean=Resources.class,
opType=RestEndpoint.OpType.POST,
path="create-jms-resource",
description="create-jms-resource")
})
public class CreateJMSResource implements AdminCommand {
@Param(name="resType")
String resourceType;
@Param(optional=true, defaultValue="true")
Boolean enabled;
@Param(name="property", optional=true, separator=':')
Properties props;
@Param(optional=true)
String target = SystemPropertyConstants.DEFAULT_SERVER_INSTANCE_NAME;
@Param(name="description", optional=true)
String description;
@Param(optional=true, defaultValue="false")
Boolean force;
@Param(name="jndi_name", primary=true)
String jndiName;
@Inject
CommandRunner commandRunner;
@Inject
Domain domain;
//ConnectorConnectionPool[] connPools;
private static final String QUEUE = "javax.jms.Queue";
private static final String TOPIC = "javax.jms.Topic";
private static final String QUEUE_CF = "javax.jms.QueueConnectionFactory";
private static final String TOPIC_CF = "javax.jms.TopicConnectionFactory";
private static final String UNIFIED_CF = "javax.jms.ConnectionFactory";
private static final String DEFAULT_JMS_ADAPTER = "jmsra";
private static final String DEFAULT_OPERAND="DEFAULT";
private static final String JNDINAME_APPENDER="-Connection-Pool";
/* As per new requirement all resources should have unique name so appending 'JNDINAME_APPENDER' to jndiName
for creating jndiNameForConnectionPool.
*/
private String jndiNameForConnectionPool;
//JMS destination resource properties
private static final String NAME = "Name";
private static final String IMQ_DESTINATION_NAME = "imqDestinationName";
final private static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(CreateJMSResource.class);
/**
* Executes the command with the command parameters passed as Properties
* where the keys are the paramter names and the values the parameter values
*
* @param context information
*/
public void execute(AdminCommandContext context) {
final ActionReport report = context.getActionReport();
//Collection connPools = domain.getResources().getResources(ConnectorConnectionPool.class);
if (resourceType == null) {
report.setMessage(localStrings.getLocalString("create.jms.resource.noResourceType",
"No Resoruce Type specified for JMS Resource."));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
if (jndiName == null) {
report.setMessage(localStrings.getLocalString("create.jms.resource.noJndiName",
"No JNDI name specified for JMS Resource."));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
if (!(resourceType.equals(TOPIC_CF) || resourceType.equals(QUEUE_CF) || resourceType.equals(UNIFIED_CF) || resourceType.equals(TOPIC) || resourceType.equals(QUEUE))) {
report.setMessage(localStrings.getLocalString("create.jms.resource.InvalidResourceType",
"Invalid Resource Type specified for JMS Resource."));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
jndiNameForConnectionPool = jndiName + JNDINAME_APPENDER;
if (force) {
Resource res = null;
if (resourceType.equals(TOPIC) || resourceType.equals(QUEUE))
res = ConnectorsUtil.getResourceByName(domain.getResources(), AdminObjectResource.class, jndiName);
else
res = ConnectorsUtil.getResourceByName(domain.getResources(), ConnectorResource.class, jndiName);
if (res != null) {
ActionReport deleteReport = report.addSubActionsReport();
ParameterMap parameters = new ParameterMap();
parameters.set(DEFAULT_OPERAND, jndiName);
parameters.set("target", target);
commandRunner.getCommandInvocation("delete-jms-resource", deleteReport, context.getSubject()).parameters(parameters).execute();
if (ActionReport.ExitCode.FAILURE.equals(deleteReport.getActionExitCode())) {
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
}
}
//Populate the JMS RA map
populateJmsRAMap();
/* Map MQ properties to Resource adapter properties */
if (props != null) {
Enumeration en = props.keys();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
String raKey = getMappedName(key);
if (raKey == null) raKey = key;
props.put(raKey, (String) props.get(key));
if(! raKey.equals(key))
props.remove(key);
}
}
ActionReport subReport = report.addSubActionsReport();
if (resourceType.equals(TOPIC_CF) || resourceType.equals(QUEUE_CF) || resourceType.equals(UNIFIED_CF)) {
ConnectorConnectionPool cpool = (ConnectorConnectionPool) ConnectorsUtil.getResourceByName(
domain.getResources(), ConnectorConnectionPool.class, jndiNameForConnectionPool);
boolean createdPool = false;
// If pool is already existing, do not try to create it again
if (cpool == null || ! filterForTarget (jndiNameForConnectionPool)) {
// Add connector-connection-pool.
ParameterMap parameters = populateConnectionPoolParameters();
commandRunner.getCommandInvocation("create-connector-connection-pool", subReport, context.getSubject()).parameters(parameters).execute();
createdPool= true;
if (ActionReport.ExitCode.FAILURE.equals(subReport.getActionExitCode())){
report.setMessage(localStrings.getLocalString("create.jms.resource.cannotCreateConnectionPool",
"Unable to create connection pool."));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
}
ParameterMap params = populateConnectionResourceParameters();
commandRunner.getCommandInvocation("create-connector-resource", subReport, context.getSubject()).parameters(params).execute();
if (ActionReport.ExitCode.FAILURE.equals(subReport.getActionExitCode())){
report.setMessage(localStrings.getLocalString("create.jms.resource.cannotCreateConnectorResource",
"Unable to create connection resource."));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
//rollback the connection pool ONLY if we created it...
if (createdPool)
commandRunner.getCommandInvocation("delete-connector-connection-pool", subReport, context.getSubject()).parameters(populateConnectionPoolParameters()).execute();
return;
}
} else if (resourceType.equals(TOPIC) ||
resourceType.equals(QUEUE))
{
ParameterMap aoAttrList = new ParameterMap();
try{
//validate the provided properties and modify it if required.
Properties properties = validateDestinationResourceProps(props, jndiName);
//aoAttrList.put("property", properties);
StringBuilder builder = new StringBuilder();
for (java.util.Map.Entry<Object, Object>prop : properties.entrySet()) {
builder.append(prop.getKey()).append("=").append(prop.getValue()).append(":");
}
String propString = builder.toString();
int lastColonIndex = propString.lastIndexOf(":");
if (lastColonIndex >= 0) {
propString = propString.substring(0, lastColonIndex);
}
aoAttrList.set("property", propString);
}catch (Exception e)
{
report.setMessage(localStrings.getLocalString("create.jms.resource.cannotCreateAdminObjectWithRootCause",
"Unable to create admin object. Reason: " + e.getMessage(), e.getMessage()));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
// create admin object
aoAttrList.set(DEFAULT_OPERAND, jndiName);
aoAttrList.set("restype", resourceType);
aoAttrList.set("raname", DEFAULT_JMS_ADAPTER);
aoAttrList.set("target", target);
if(enabled!=null)
aoAttrList.set("enabled", Boolean.toString(enabled));
commandRunner.getCommandInvocation("create-admin-object", subReport, context.getSubject()).parameters(aoAttrList).execute();
if (ActionReport.ExitCode.FAILURE.equals(subReport.getActionExitCode())){
report.setMessage(localStrings.getLocalString("create.jms.resource.cannotCreateAdminObject",
"Unable to create admin object."));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
return;
}
}
ActionReport.ExitCode ec = ActionReport.ExitCode.SUCCESS;
report.setActionExitCode(ec);
}
private boolean filterForTarget(String jndiName){
//List<String> resourceList = new ArrayList();
if (target != null){
List<ResourceRef> resourceRefs = null;
Cluster cluster = domain.getClusterNamed(target);
if (cluster != null)
resourceRefs= cluster.getResourceRef();
else {
Server server = domain.getServerNamed(target);
if (server != null)
resourceRefs = server.getResourceRef();
}
if (resourceRefs != null && resourceRefs.size() != 0){
for (ResourceRef resource : resourceRefs)
if(jndiName.equalsIgnoreCase(resource.getRef()))
return true;
}
}
return false;
}
Hashtable mapping = null;
private void populateJmsRAMap() {
mapping = new Hashtable();
mapping.put("imqDestinationName","Name");
mapping.put("imqDestinationDescription","Description");
mapping.put("imqConnectionURL","ConnectionURL");
mapping.put("imqDefaultUsername","UserName");
mapping.put("imqDefaultPassword","Password");
mapping.put("imqConfiguredClientID","ClientId");
mapping.put("imqAddressList","AddressList");
mapping.put("MessageServiceAddressList","AddressList");
}
public String getMappedName(String key){
return (String) mapping.get(key);
}
private ParameterMap populateConnectionPoolParameters(){
String steadyPoolSize = null;
String maxPoolSize = null;
String poolResizeQuantity = null;
String idleTimeoutInSecs = null;
String maxWaitTimeInMillis = null;
String failAllConnections = null;
String transactionSupport = null;
ParameterMap parameters = new ParameterMap();
if(props != null){
Enumeration keys = props.keys();
Properties tmpProps = new Properties();
while(keys.hasMoreElements())
{
String propKey = (String) keys.nextElement();
if ("steady-pool-size".equals(propKey))
steadyPoolSize = props.getProperty(propKey);
else if ("max-pool-size".equals(propKey))
maxPoolSize = props.getProperty(propKey);
else if ("pool-resize-quantity".equals(propKey))
poolResizeQuantity = props.getProperty(propKey);
else if ("idle-timeout-in-seconds".equals(propKey))
idleTimeoutInSecs = props.getProperty(propKey);
else if ("max-wait-time-in-millis".equals(propKey))
maxWaitTimeInMillis = props.getProperty(propKey);
else if ("transaction-support".equals(propKey))
transactionSupport = props.getProperty(propKey);
else if("fail-all-connections".equals(propKey))
failAllConnections = props.getProperty(propKey);
else{
if ("AddressList".equals(propKey)){
String addressListProp = props.getProperty(propKey);
props.setProperty(propKey, "\""+ addressListProp + "\"");
}
else if ("Password".equals(propKey)){
String password = props.getProperty(propKey);
if (isPasswordAlias(password))
//If the string is a password alias, it needs to be escapted with another pair of quotes...
props.setProperty(propKey, "\"" + password + "\"");
}
tmpProps.setProperty(propKey, props.getProperty(propKey));
}
}
if (tmpProps.size() >0)
{
StringBuilder builder = new StringBuilder();
for (java.util.Map.Entry<Object, Object>prop : tmpProps.entrySet()) {
builder.append(prop.getKey()).append("=").append(prop.getValue()).append(":");
}
String propString = builder.toString();
int lastColonIndex = propString.lastIndexOf(":");
if (lastColonIndex >= 0) {
propString = propString.substring(0, lastColonIndex);
}
parameters.set("property", propString);
}
}
//parameters.set("restype", resourceType);
parameters.set(DEFAULT_OPERAND, jndiNameForConnectionPool);
parameters.set("poolname", jndiName);
if(description != null)
parameters.set("description", description);
// Get the default res adapter name from Connector-runtime
String raName = DEFAULT_JMS_ADAPTER;
parameters.set("raname", raName);
parameters.set("connectiondefinition", resourceType);
parameters.set("maxpoolsize", (maxPoolSize == null) ? "250" : maxPoolSize);
parameters.set("steadypoolsize", (steadyPoolSize == null) ? "1" : steadyPoolSize);
if (poolResizeQuantity != null) {
parameters.set("poolresize", poolResizeQuantity);
}
if (idleTimeoutInSecs != null) {
parameters.set("idletimeout", idleTimeoutInSecs);
}
if (maxWaitTimeInMillis != null) {
parameters.set("maxwait", maxWaitTimeInMillis);
}
if (failAllConnections != null) {
parameters.set("failconnection",failAllConnections);
}
if (transactionSupport != null) {
parameters.set("transactionsupport", transactionSupport);
}
return parameters;
}
private boolean isPasswordAlias(String password){
if (password != null && password.startsWith("${ALIAS="))
return true;
return false;
}
private ParameterMap populateConnectionResourceParameters()
{
ParameterMap parameters = new ParameterMap();
parameters.set("jndi_name", jndiName);
parameters.set(DEFAULT_OPERAND, jndiName);
parameters.set("enabled", Boolean.toString(enabled));
parameters.set("poolname", jndiNameForConnectionPool);
parameters.set("target", target);
if(description != null)
parameters.set("description", description);
return parameters;
}
/**
* Validates the properties specified for a Destination Resource
* and returns a validated Properties list.
*
* NOTE: When "Name" property has not been specified by the user,
* the properties object is updated with a computed Name.
*/
private Properties validateDestinationResourceProps(Properties props,
String jndiName) throws Exception {
String providedDestinationName = null;
if(props != null)
providedDestinationName = getProvidedDestinationName(props);
else
props = new Properties();
//sLogger.fine("provided destination name = " + providedDestinationName);
if (providedDestinationName != null) {
//check validity of provided JMS destination name
if (!isSyntaxValid(providedDestinationName)) {
throw new Exception(localStrings.getLocalString(
"admin.mbeans.rmb.destination_name_invalid",
"Destination Resource " + jndiName +
" has an invalid destination name " + providedDestinationName,
jndiName, providedDestinationName));
}
} else {
//compute a valid destination name from the JNDI name.
String newDestName = computeDestinationName(jndiName);
//sLogger.log(Level.WARNING, "admin.mbeans.rmb.destination_name_missing",new Object[]{jndiName, newDestName});
props.put(NAME, newDestName);
//sLogger.fine("Computed destination name" + newDestName + " and updated props");
}
return props;
}
/**
* Get the physical destination name provided by the user. The "Name"
* and "imqDestinationName" properties are used to link a JMS destination
* resource to its physical destination in SJSMQ.
*/
private String getProvidedDestinationName(Properties props) {
for (Enumeration e = props.keys() ; e.hasMoreElements() ;) {
String key = (String)e.nextElement();
String value = (String)props.get(key);
if(NAME.equals(key) || IMQ_DESTINATION_NAME.equals(key)){
if (value != null && value.length() != 0) return value;
}
}
return null;
}
//Modified this method to support wildcards in MQ destinations...
private boolean isSyntaxValid(String name) {
if (name.startsWith("mq.")) {
return false;
}
try {
CharsetEncoder asciiEncoder = Charset.forName("US-ASCII").newEncoder();
if (!asciiEncoder.canEncode(name))
return false;
} catch (Exception e) {
// skip detecting non ASCII charactors if error occurs
}
char[] namechars = name.toCharArray();
if (Character.isJavaIdentifierStart(namechars[0]) || namechars[0] == '*' || namechars[0] == '>') {
for (int i = 1; i<namechars.length; i++) {
if (!Character.isJavaIdentifierPart(namechars[i]) && ! (namechars[i] == '.' || namechars[i] == '*' || namechars[i] == '>')) {
return false;
}
}
} else {
return false;
}
return true;
}
/**
* Derive a destination name, valid as per MQ destination naming rules,
* from the JNDI name provided for the JMS destination resource.
*
* Scheme: merely replace all invalid identifiers in the JNDI name with
* an 'underscore'.
*/
private String computeDestinationName(String providedJndiName) {
char[] jndiName = providedJndiName.toCharArray();
char[] finalName = new char[jndiName.length];
finalName[0] = Character.isJavaIdentifierStart(jndiName[0]) ? jndiName[0] : '_';
for (int i = 1; i < jndiName.length; i++) {
finalName[i] = Character.isJavaIdentifierPart(jndiName[i])? jndiName[i] : '_';
}
return new String(finalName);
}
}