com.sun.enterprise.v3.admin.SetCommand Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* 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.
*/
// Portions Copyright [2017] [Payara Foundation and/or its affiliates]
package com.sun.enterprise.v3.admin;
import com.sun.enterprise.admin.util.ClusterOperationUtil;
import com.sun.enterprise.config.modularity.ConfigModularityUtils;
import com.sun.enterprise.config.modularity.GetSetModularityHelper;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.util.LocalStringManagerImpl;
import org.glassfish.api.ActionReport;
import org.glassfish.api.I18n;
import org.glassfish.api.Param;
import org.glassfish.api.admin.AdminCommand;
import org.glassfish.api.admin.AdminCommandContext;
import org.glassfish.api.admin.ExecuteOn;
import org.glassfish.api.admin.FailurePolicy;
import org.glassfish.api.admin.ParameterMap;
import org.glassfish.api.admin.RuntimeType;
import org.glassfish.api.admin.config.LegacyConfigurationUpgrade;
import org.glassfish.internal.api.Target;
import org.jvnet.hk2.annotations.Optional;
import org.jvnet.hk2.annotations.Service;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.hk2.api.ServiceLocator;
import org.jvnet.hk2.config.ConfigBean;
import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.ConfigModel;
import org.jvnet.hk2.config.ConfigSupport;
import org.jvnet.hk2.config.Dom;
import org.jvnet.hk2.config.SingleConfigCode;
import org.jvnet.hk2.config.TransactionFailure;
import org.jvnet.hk2.config.WriteableView;
import org.jvnet.hk2.config.types.Property;
import org.jvnet.tiger_types.Types;
import javax.inject.Inject;
import java.beans.PropertyVetoException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.glassfish.api.admin.AccessRequired.AccessCheck;
import org.glassfish.api.admin.AdminCommandSecurity;
/**
* User: Jerome Dochez Date: Jul 11, 2008 Time: 4:39:05 AM
*/
@Service(name = "set")
@ExecuteOn(RuntimeType.INSTANCE)
@PerLookup
@I18n("set")
public class SetCommand extends V2DottedNameSupport implements AdminCommand, PostConstruct,
AdminCommandSecurity.AccessCheckProvider, AdminCommandSecurity.Preauthorization {
@Inject
ServiceLocator habitat;
@Inject
Domain domain;
@Inject
ConfigSupport config;
@Inject
Target targetService;
@Inject
@Optional
GetSetModularityHelper modularityHelper;
@Inject
ConfigModularityUtils utils;
@Param(primary = true, multiple = true)
String[] values;
final private static LocalStringManagerImpl localStrings
= new LocalStringManagerImpl(SetCommand.class);
private HashMap targetLevel = null;
private final Collection setOperations = new ArrayList();
@Override
public void postConstruct() {
targetLevel = new HashMap();
targetLevel.put("applications", 0);
targetLevel.put("system-applications", 0);
targetLevel.put("resources", 0);
targetLevel.put("configs", 3);
targetLevel.put("clusters", 3);
targetLevel.put("servers", 3);
targetLevel.put("nodes", 3);
}
@Override
public boolean preAuthorization(AdminCommandContext context) {
for (String value : values) {
if (value.contains(".log-service")) {
fail(context, localStrings.getLocalString("admin.set.invalid.logservice.command", "For setting log levels/attributes use set-log-levels/set-log-attributes command."));
return false;
}
if (!prepare(context, value)) {
return false;
}
}
return true;
}
@Override
public Collection extends AccessCheck> getAccessChecks() {
final Collection accessChecks = new ArrayList();
for (SetOperation op : setOperations) {
accessChecks.add(new AccessCheck(op.getResourceName(), "update"));
}
return accessChecks;
}
@Override
public void execute(AdminCommandContext context) {
for (SetOperation op : setOperations) {
if (!set(context, op)) {
return;
}
}
}
/**
* Captures information about each set operation conveyed on a single
* command invocation.
*/
private static class SetOperation {
private final String target;
private final String value;
private final String pattern;
private final boolean isProperty;
private final String attrName;
private SetOperation(final String target, final String value, final String pattern,
final String attrName, final boolean isProperty) {
this.target = target;
this.value = value;
this.pattern = pattern;
this.attrName = attrName;
this.isProperty = isProperty;
}
/**
* Returns the name of the resource being affected by this set
* operation.
*
* @return
*/
private String getResourceName() {
StringBuilder dottedNameForResourceName = new StringBuilder();
if (isProperty) {
final int propertyLiteralIndex = pattern.indexOf("property.");
dottedNameForResourceName.append(pattern.substring(0, propertyLiteralIndex));
} else {
dottedNameForResourceName.append(pattern);
}
if (!dottedNameForResourceName.toString().startsWith("domain.")) {
dottedNameForResourceName.insert(0, "domain.");
}
return dottedNameForResourceName.toString().replace('.', '/');
}
}
/**
* Processes a single name/value pair just enough to figure out what kind of
* entity the target is (an element, an attribute, a property) and saves
* that information as a SetOperation instance.
*
* @param context admin command context
* @param nameval a single name/value pair from the command
* @return
*/
private boolean prepare(AdminCommandContext context, String nameval) {
int i = nameval.indexOf('=');
if (i < 0) {
//ail(context, "Invalid attribute " + nameval);
fail(context, localStrings.getLocalString("admin.set.invalid.namevalue", "Invalid name value pair {0}. Missing expected equal sign.", nameval));
return false;
}
String target = nameval.substring(0, i);
String value = nameval.substring(i + 1);
// so far I assume we always want to change one attribute so I am removing the
// last element from the target pattern which is supposed to be the
// attribute name
int lastDotIndex = trueLastIndexOf(target, '.');
if (lastDotIndex == -1) {
// error.
//fail(context, "Invalid attribute name " + target);
fail(context, localStrings.getLocalString("admin.set.invalid.attributename", "Invalid attribute name {0}", target));
return false;
}
String attrName = target.substring(lastDotIndex + 1).replace("\\.", ".");
String pattern = target.substring(0, lastDotIndex);
if (attrName.replace('_', '-').equals("jndi-name")) {
//fail(context, "Cannot change a primary key\nChange of " + target + " is rejected.");
fail(context, localStrings.getLocalString("admin.set.reject.keychange", "Cannot change a primary key\nChange of {0}", target));
return false;
}
boolean isProperty = false;
if ("property".equals(pattern.substring(trueLastIndexOf(pattern, '.') + 1))) {
// we are looking for a property, let's look it it exists already...
pattern = target.replaceAll("\\\\\\.", "\\.");
isProperty = true;
}
setOperations.add(new SetOperation(target, value, pattern, attrName, isProperty));
return true;
}
private boolean set(AdminCommandContext context, SetOperation op) {
String pattern = op.pattern;
String value = op.value;
String target = op.target;
String attrName = op.attrName;
boolean isProperty = op.isProperty;
// now
// first let's get the parent for this pattern.
TreeNode[] parentNodes = getAliasedParent(domain, pattern);
// reset the pattern.
String prefix;
boolean lookAtSubNodes = true;
if (parentNodes[0].relativeName.length() == 0
|| parentNodes[0].relativeName.equals("domain")) {
// handle the case where the pattern references an attribute of the top-level node
prefix = "";
// pattern is already set properly
lookAtSubNodes = false;
} else if (!pattern.startsWith(parentNodes[0].relativeName)) {
prefix = pattern.substring(0, pattern.indexOf(parentNodes[0].relativeName));
pattern = parentNodes[0].relativeName;
} else {
prefix = "";
pattern = parentNodes[0].relativeName;
}
String targetName = prefix + pattern;
if (modularityHelper != null) {
synchronized (utils) {
boolean oldv = utils.isCommandInvocation();
utils.setCommandInvocation(true);
modularityHelper.getLocationForDottedName(targetName);
utils.setCommandInvocation(oldv);
}
}
Map matchingNodes;
boolean applyOverrideRules = false;
Map dottedNames = new HashMap();
if (lookAtSubNodes) {
for (TreeNode parentNode : parentNodes) {
dottedNames.putAll(getAllDottedNodes(parentNode.node));
}
matchingNodes = getMatchingNodes(dottedNames, pattern);
applyOverrideRules = true;
} else {
matchingNodes = new HashMap();
for (TreeNode parentNode : parentNodes) {
matchingNodes.put(parentNode.node, pattern);
}
}
if (matchingNodes.isEmpty()) {
// it's possible they are trying to create a property object.. lets check this.
// strip out the property name
pattern = target.substring(0, trueLastIndexOf(target, '.'));
if (pattern.endsWith("property")) {
// need to find the right parent.
Dom parentNode = null;
if ("property".equals(pattern)) {
// we are setting domain properties as there is no previous pattern
// create and set the property
try {
final String fname = attrName;
final String fvalue = value;
ConfigSupport.apply(new SingleConfigCode() {
@Override
public Object run(Domain domain) throws PropertyVetoException, TransactionFailure {
Property p = domain.createChild(Property.class);
p.setName(fname);
p.setValue(fvalue);
domain.getProperty().add(p);
return p;
}
},domain);
success(context, targetName, value);
runLegacyChecks(context);
if (targetService.isThisDAS() && !replicateSetCommand(context, target, value)) {
return false;
}
return true;
} catch (TransactionFailure transactionFailure) {
//fail(context, "Could not change the attributes: " +
// transactionFailure.getMessage(), transactionFailure);
fail(context, localStrings.getLocalString("admin.set.attribute.change.failure", "Could not change the attributes: {0}",
transactionFailure.getMessage()), transactionFailure);
return false;
}
} else {
pattern = pattern.substring(0, trueLastIndexOf(pattern, '.'));
parentNodes = getAliasedParent(domain, pattern);
}
pattern = parentNodes[0].relativeName;
matchingNodes = getMatchingNodes(dottedNames, pattern);
if (matchingNodes.isEmpty()) {
//fail(context, "No configuration found for " + targetName);
fail(context, localStrings.getLocalString("admin.set.configuration.notfound", "No configuration found for {0}", targetName));
return false;
}
for (Map.Entry node : matchingNodes.entrySet()) {
if (node.getValue().equals(pattern)) {
parentNode = node.getKey();
}
}
if (parentNode == null) {
//fail(context, "No configuration found for " + targetName);
fail(context, localStrings.getLocalString("admin.set.configuration.notfound", "No configuration found for {0}", targetName));
return false;
}
if (value == null || value.length() == 0) {
// setting to the empty string means to remove the property, so don't create it
success(context, targetName, value);
return true;
}
// create and set the property
Map attributes = new HashMap();
attributes.put("value", value);
attributes.put("name", attrName);
try {
if (!(parentNode instanceof ConfigBean)) {
final ClassCastException cce = new ClassCastException(parentNode.getClass().getName());
fail(context, localStrings.getLocalString("admin.set.attribute.change.failure",
"Could not change the attributes: {0}",
cce.getMessage(), cce));
return false;
}
ConfigSupport.createAndSet((ConfigBean) parentNode, Property.class, attributes);
success(context, targetName, value);
runLegacyChecks(context);
if (targetService.isThisDAS() && !replicateSetCommand(context, target, value)) {
return false;
}
return true;
} catch (TransactionFailure transactionFailure) {
//fail(context, "Could not change the attributes: " +
// transactionFailure.getMessage(), transactionFailure);
fail(context, localStrings.getLocalString("admin.set.attribute.change.failure", "Could not change the attributes: {0}",
transactionFailure.getMessage()), transactionFailure);
return false;
}
}
}
Map> changes = new HashMap>();
boolean setElementSuccess = false;
boolean delPropertySuccess = false;
boolean delProperty = false;
Map attrChanges = new HashMap();
if (isProperty) {
attrName = "value";
if ((value == null) || (value.length() == 0)) {
delProperty = true;
}
attrChanges.put(attrName, value);
}
List mNodes = new ArrayList(matchingNodes.entrySet());
if (applyOverrideRules) {
mNodes = applyOverrideRules(mNodes);
}
for (Map.Entry node : mNodes) {
final Dom targetNode = node.getKey();
for (String name : targetNode.model.getAttributeNames()) {
String finalDottedName = node.getValue() + "." + name;
if (matches(finalDottedName, pattern)) {
if (attrName.equals(name)
|| attrName.replace('_', '-').equals(name.replace('_', '-'))) {
if (isDeprecatedAttr(targetNode, name)) {
warning(context, localStrings.getLocalString("admin.set.deprecated",
"Warning: The attribute {0} is deprecated.", finalDottedName));
}
if (!isProperty) {
targetName = prefix + finalDottedName;
if (value != null && value.length() > 0) {
attrChanges.put(name, value);
} else {
attrChanges.put(name, null);
}
} else {
targetName = prefix + node.getValue();
}
if (delProperty) {
// delete property element
String str = node.getValue();
if (trueLastIndexOf(str, '.') != -1) {
str = str.substring(trueLastIndexOf(str, '.') + 1);
}
try {
if (str != null) {
ConfigSupport.deleteChild((ConfigBean) targetNode.parent(), (ConfigBean) targetNode);
delPropertySuccess = true;
}
} catch (IllegalArgumentException ie) {
fail(context, localStrings.getLocalString("admin.set.delete.property.failure", "Could not delete the property: {0}",
ie.getMessage()), ie);
return false;
} catch (TransactionFailure transactionFailure) {
fail(context, localStrings.getLocalString("admin.set.attribute.change.failure", "Could not change the attributes: {0}",
transactionFailure.getMessage()), transactionFailure);
return false;
}
} else {
changes.put((ConfigBean) node.getKey(), attrChanges);
}
}
}
}
for (String name : targetNode.model.getLeafElementNames()) {
String finalDottedName = node.getValue() + "." + name;
if (matches(finalDottedName, pattern)) {
if (attrName.equals(name)
|| attrName.replace('_', '-').equals(name.replace('_', '-'))) {
if (isDeprecatedAttr(targetNode, name)) {
warning(context, localStrings.getLocalString("admin.set.elementdeprecated",
"Warning: The element {0} is deprecated.", finalDottedName));
}
try {
setLeafElement((ConfigBean) targetNode, name, value);
} catch (TransactionFailure ex) {
fail(context, localStrings.getLocalString("admin.set.badelement", "Cannot change the element: {0}",
ex.getMessage()), ex);
return false;
}
setElementSuccess = true;
break;
}
}
}
}
if (!changes.isEmpty()) {
try {
config.apply(changes);
success(context, targetName, value);
runLegacyChecks(context);
} catch (TransactionFailure transactionFailure) {
//fail(context, "Could not change the attributes: " +
// transactionFailure.getMessage(), transactionFailure);
fail(context, localStrings.getLocalString("admin.set.attribute.change.failure", "Could not change the attributes: {0}",
transactionFailure.getMessage()), transactionFailure);
return false;
}
} else if (delPropertySuccess || setElementSuccess) {
success(context, targetName, value);
} else {
fail(context, localStrings.getLocalString("admin.set.configuration.notfound", "No configuration found for {0}", targetName));
return false;
}
if (targetService.isThisDAS()
&& !replicateSetCommand(context, target, value)) {
return false;
}
return true;
}
public static void setLeafElement(
final ConfigBean node,
final String elementName,
final String values)
throws TransactionFailure {
ConfigBeanProxy readableView = node.getProxy(node.getProxyType());
ConfigSupport.apply(new SingleConfigCode() {
/**
* Runs the following command passing the configuration object. The
* code will be run within a transaction, returning true will commit
* the transaction, false will abort it.
*
* @param param is the configuration object protected by the
* transaction
* @return any object that should be returned from within the
* transaction code
* @throws java.beans.PropertyVetoException if the changes cannot be
* applied to the configuration
*/
@Override
public Object run(ConfigBeanProxy param) throws PropertyVetoException, TransactionFailure {
WriteableView writeableParent = (WriteableView) Proxy.getInvocationHandler(param);
StringTokenizer st = new StringTokenizer(values, ",");
List valList = new ArrayList<>();
while (st.hasMoreTokens()) {
valList.add(st.nextToken());
}
ConfigBean bean = writeableParent.getMasterView();
for (Method m : writeableParent.getProxyType().getMethods()) {
// Check to see if the method is a setter for the element
// An element setter has to have the right name, take a single
// collection parameter that parameterized with the right type
Class argClasses[] = m.getParameterTypes();
Type argTypes[] = m.getGenericParameterTypes();
ConfigModel.Property prop = bean.model.toProperty(m);
if (prop == null
|| !prop.xmlName().equals(elementName)
|| argClasses.length != 1
|| !Collection.class.isAssignableFrom(argClasses[0])
|| argTypes.length != 1
|| !(argTypes[0] instanceof ParameterizedType)
|| !Types.erasure(Types.getTypeArgument(argTypes[0], 0)).isAssignableFrom(values.getClass())) {
continue;
}
// we have the right method. Now call it
try {
m.invoke(writeableParent.getProxy(writeableParent.getProxyType()), valList);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new TransactionFailure("Exception while setting element", e);
}
return node;
}
throw new TransactionFailure("No method found for setting element");
}
}, readableView);
}
/*
* Determine whether this attribute is deprecated. This method
* stops looking after it finds the first method or field whose name matches
* the attribute name. So to make an attribute deprecated, all of the
* methods (set, get, etc.) must be marked as deprecated.
*/
private boolean isDeprecatedAttr(Dom dom, String name) {
if (dom == null || dom.model == null || name == null) {
return false;
}
Class t = dom.getProxyType();
if (t == null) {
return false;
}
for (Method m : t.getDeclaredMethods()) {
ConfigModel.Property p = dom.model.toProperty(m);
if (p != null && name.equals(p.xmlName())) {
return m.isAnnotationPresent(Deprecated.class
);
}
}
for (Field f : t.getDeclaredFields()) {
if (name.equals(dom.model.camelCaseToXML(f.getName()))) {
return f.isAnnotationPresent(Deprecated.class
);
}
}
return false;
}
private String getElementFromString(String name, int index) {
StringTokenizer token = new StringTokenizer(name, ".");
String target = null;
for (int j = 0; j < index; j++) {
if (token.hasMoreTokens()) {
target = token.nextToken();
}
}
return target;
}
private boolean replicateSetCommand(AdminCommandContext context, String targetName, String value) {
// "domain." on the front of the attribute name is optional. So if it is
// there, strip it off.
List replicationInstances = null;
String tName;
if (targetName.startsWith("domain.")) {
tName = targetName.substring("domain.".length());
if (tName.indexOf('.') == -1) {
// This is a domain-level attribute, replicate to all instances
replicationInstances = targetService.getAllInstances();
}
} else {
tName = targetName;
}
if (replicationInstances == null) {
int dotIdx = tName.indexOf('.');
String firstElementOfName = dotIdx != -1 ? tName.substring(0, dotIdx) : tName;
Integer targetElementLocation = targetLevel.get(firstElementOfName);
if (targetElementLocation == null) {
targetElementLocation = 1;
}
if (targetElementLocation == 0) {
if ("resources".equals(firstElementOfName)) {
replicationInstances = targetService.getAllInstances();
}
if ("applications".equals(firstElementOfName)) {
String appName = getElementFromString(tName, 3);
if (appName == null) {
fail(context, localStrings.getLocalString("admin.set.invalid.appname",
"Unable to extract application name from {0}", targetName));
return false;
}
replicationInstances = targetService.getInstances(domain.getAllReferencedTargetsForApplication(appName));
}
} else {
String target = getElementFromString(tName, targetElementLocation);
if (target == null) {
fail(context, localStrings.getLocalString("admin.set.invalid.target",
"Unable to extract replication target from {0}", targetName));
return false;
}
replicationInstances = targetService.getInstances(target);
}
}
if (replicationInstances != null && !replicationInstances.isEmpty()) {
ParameterMap params = new ParameterMap();
params.set("DEFAULT", targetName + "=" + value);
ActionReport.ExitCode ret = ClusterOperationUtil.replicateCommand("set", FailurePolicy.Error,
FailurePolicy.Warn, FailurePolicy.Ignore, replicationInstances, context, params, habitat);
if (ret.equals(ActionReport.ExitCode.FAILURE)) {
return false;
}
}
return true;
}
private void runLegacyChecks(AdminCommandContext context) {
final Collection list = habitat.getAllServices(LegacyConfigurationUpgrade.class
);
for (LegacyConfigurationUpgrade upgrade : list) {
upgrade.execute(context);
}
}
/**
* Find the rightmost unescaped occurrence of specified character in target
* string.
*
* XXX Doesn't correctly interpret escaped backslash characters, e.g.
* foo\\.bar
*
* @param target string to search
* @param ch a character
* @return index index of last unescaped occurrence of specified character
* or -1 if there are no unescaped occurrences of this character.
*/
private static int trueLastIndexOf(String target, char ch) {
int i = target.lastIndexOf(ch);
while (i > 0) {
if (target.charAt(i - 1) == '\\') {
i = target.lastIndexOf(ch, i - 1);
} else {
break;
}
}
return i;
}
/**
* Indicate in the action report that the command failed.
*/
private static void fail(AdminCommandContext context, String msg) {
fail(context, msg, null);
}
/**
* Indicate in the action report that the command failed.
*/
private static void fail(AdminCommandContext context, String msg,
Exception ex) {
context.getActionReport().setActionExitCode(
ActionReport.ExitCode.FAILURE);
if (ex != null) {
context.getActionReport().setFailureCause(ex);
}
context.getActionReport().setMessage(msg);
}
/**
* Indicate in the action report a warning message.
*/
private void warning(AdminCommandContext context, String msg) {
ActionReport ar = context.getActionReport().addSubActionsReport();
ar.setActionExitCode(ActionReport.ExitCode.WARNING);
ar.setMessage(msg);
}
/**
* Indicate in the action report that the command succeeded and include the
* target property and it's value in the report
*/
private void success(AdminCommandContext context, String target, String value) {
context.getActionReport().setActionExitCode(ActionReport.ExitCode.SUCCESS);
ActionReport.MessagePart part = context.getActionReport().getTopMessagePart().addChild();
part.setChildrenType("DottedName");
part.setMessage(target + "=" + value);
}
}