/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * */ /* * Created on Jul 27, 2004 */ package org.apache.jmeter.save.converters; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import org.apache.jmeter.save.SaveService; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.util.NameUpdater; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; /** * Utility conversion routines for use with XStream * */ public class ConversionHelp { private static final Logger log = LoggerFactory.getLogger(ConversionHelp.class); private static final String CHAR_SET = StandardCharsets.UTF_8.name(); // Attributes for TestElement and TestElementProperty // Must all be unique public static final String ATT_CLASS = "class"; //$NON-NLS-1$ // Also used by PropertyConverter classes public static final String ATT_NAME = "name"; // $NON-NLS-1$ public static final String ATT_ELEMENT_TYPE = "elementType"; // $NON-NLS-1$ private static final String ATT_TE_ENABLED = "enabled"; //$NON-NLS-1$ private static final String ATT_TE_TESTCLASS = "testclass"; //$NON-NLS-1$ static final String ATT_TE_GUICLASS = "guiclass"; //$NON-NLS-1$ private static final String ATT_TE_NAME = "testname"; //$NON-NLS-1$ /* * These must be set before reading/writing the XML. Rather a hack, but * saves changing all the method calls to include an extra variable. * * AFAIK the variables should only be accessed from one thread, so no need to synchronize. */ private static String inVersion; private static String outVersion = "1.1"; // Default for writing//$NON-NLS-1$ public static void setInVersion(String v) { inVersion = v; } public static void setOutVersion(String v) { outVersion = v; } /** * Encode a string (if necessary) for output to a JTL file. * Strings are only encoded if the output version is 1.0, * but nulls are always converted to the empty string. * * @param p string to encode * @return encoded string (will never be null) */ public static String encode(String p) { if (p == null) {// Nulls cannot be written using PrettyPrintWriter - they cause an NPE return ""; // $NON-NLS-1$ } // Only encode strings if outVersion = 1.0 if (!"1.0".equals(outVersion)) {//$NON-NLS-1$ return p; } try { String p1 = URLEncoder.encode(p, CHAR_SET); return p1; } catch (UnsupportedEncodingException e) { log.warn("System doesn't support {}", CHAR_SET, e); return p; } } /** * Decode a string if {@link #inVersion} equals <code>1.0</code> * * @param p * the string to be decoded * @return the newly decoded string */ public static String decode(String p) { if (!"1.0".equals(inVersion)) {//$NON-NLS-1$ return p; } // Only decode strings if inVersion = 1.0 if (p == null) { return null; } try { return URLDecoder.decode(p, CHAR_SET); } catch (UnsupportedEncodingException e) { log.warn("System doesn't support {}", CHAR_SET, e); return p; } } /** * Embed an array of bytes as a string with <code>encoding</code> in a * xml-cdata section * * @param chars * bytes to be encoded and embedded * @param encoding * the encoding to be used * @return the encoded string embedded in a xml-cdata section * @throws UnsupportedEncodingException * when the bytes can not be encoded using <code>encoding</code> */ public static String cdata(byte[] chars, String encoding) throws UnsupportedEncodingException { StringBuilder buf = new StringBuilder("<![CDATA["); buf.append(new String(chars, encoding)); buf.append("]]>"); return buf.toString(); } /** * Names of properties that are handled specially */ private static final Map<String, String> propertyToAttribute = new HashMap<>(); private static void mapentry(String prop, String att){ propertyToAttribute.put(prop,att); } static{ mapentry(TestElement.NAME,ATT_TE_NAME); mapentry(TestElement.GUI_CLASS,ATT_TE_GUICLASS);//$NON-NLS-1$ mapentry(TestElement.TEST_CLASS,ATT_TE_TESTCLASS);//$NON-NLS-1$ mapentry(TestElement.ENABLED,ATT_TE_ENABLED); } private static void saveClass(TestElement el, HierarchicalStreamWriter writer, String prop){ String clazz=el.getPropertyAsString(prop); if (clazz.length()>0) { writer.addAttribute(propertyToAttribute.get(prop),SaveService.classToAlias(clazz)); } } private static void restoreClass(TestElement el, HierarchicalStreamReader reader, String prop) { String att=propertyToAttribute.get(prop); String alias=reader.getAttribute(att); if (alias!=null){ alias=SaveService.aliasToClass(alias); if (TestElement.GUI_CLASS.equals(prop)) { // mainly for TestElementConverter alias = NameUpdater.getCurrentName(alias); } el.setProperty(prop,alias); } } private static void saveItem(TestElement el, HierarchicalStreamWriter writer, String prop, boolean encode){ String item=el.getPropertyAsString(prop); if (item.length() > 0) { if (encode) { item=ConversionHelp.encode(item); } writer.addAttribute(propertyToAttribute.get(prop),item); } } private static void restoreItem(TestElement el, HierarchicalStreamReader reader, String prop, boolean decode) { String att=propertyToAttribute.get(prop); String value=reader.getAttribute(att); if (value!=null){ if (decode) { value=ConversionHelp.decode(value); } el.setProperty(prop,value); } } /** * Check whether <code>name</code> specifies a <em>special</em> property * * @param name * the name of the property to be checked * @return <code>true</code> if <code>name</code> is the name of a special * property */ public static boolean isSpecialProperty(String name) { return propertyToAttribute.containsKey(name); } /** * Get the property name, updating it if necessary using {@link NameUpdater}. * @param reader where to read the name attribute * @param context the unmarshalling context * * @return the property name, may be null if the property has been deleted. * @see #getUpgradePropertyName(String, UnmarshallingContext) */ public static String getPropertyName(HierarchicalStreamReader reader, UnmarshallingContext context) { String name = ConversionHelp.decode(reader.getAttribute(ATT_NAME)); return getUpgradePropertyName(name, context); } /** * Get the property value, updating it if necessary using {@link NameUpdater}. * * Do not use for GUI_CLASS or TEST_CLASS. * * @param reader where to read the value * @param context the unmarshalling context * @param name the name of the property * * @return the property value, updated if necessary. * @see #getUpgradePropertyValue(String, String, UnmarshallingContext) */ public static String getPropertyValue(HierarchicalStreamReader reader, UnmarshallingContext context, String name) { String value = ConversionHelp.decode(reader.getValue()); return getUpgradePropertyValue(name, value, context); } /** * Update a property name using {@link NameUpdater}. * @param name the original property name * @param context the unmarshalling context * * @return the property name, may be null if the property has been deleted. */ public static String getUpgradePropertyName(String name, UnmarshallingContext context) { String testClass = (String) context.get(SaveService.TEST_CLASS_NAME); final String newName = NameUpdater.getCurrentName(name, testClass); // Delete any properties whose name converts to the empty string if (name.length() != 0 && newName.length()==0) { return null; } return newName; } /** * Update a property value using {@link NameUpdater#getCurrentName(String, String, String)}. * * Do not use for GUI_CLASS or TEST_CLASS. * * @param name the original property name * @param value the original property value * @param context the unmarshalling context * * @return the property value, updated if necessary */ public static String getUpgradePropertyValue(String name, String value, UnmarshallingContext context) { String testClass = (String) context.get(SaveService.TEST_CLASS_NAME); return NameUpdater.getCurrentName(value, name, testClass); } /** * Save the special properties: * <ul> * <li>TestElement.GUI_CLASS</li> * <li>TestElement.TEST_CLASS</li> * <li>TestElement.NAME</li> * <li>TestElement.ENABLED</li> * </ul> * * @param testElement * element for which the special properties should be saved * @param writer * {@link HierarchicalStreamWriter} in which the special * properties should be saved */ public static void saveSpecialProperties(TestElement testElement, HierarchicalStreamWriter writer) { saveClass(testElement,writer,TestElement.GUI_CLASS); saveClass(testElement,writer,TestElement.TEST_CLASS); saveItem(testElement,writer,TestElement.NAME,true); saveItem(testElement,writer,TestElement.ENABLED,false); } /** * Restore the special properties: * <ul> * <li>TestElement.GUI_CLASS</li> * <li>TestElement.TEST_CLASS</li> * <li>TestElement.NAME</li> * <li>TestElement.ENABLED</li> * </ul> * * @param testElement * in which the special properties should be restored * @param reader * {@link HierarchicalStreamReader} from which the special * properties should be restored */ public static void restoreSpecialProperties(TestElement testElement, HierarchicalStreamReader reader) { restoreClass(testElement,reader,TestElement.GUI_CLASS); restoreClass(testElement,reader,TestElement.TEST_CLASS); restoreItem(testElement,reader,TestElement.NAME,true); restoreItem(testElement,reader,TestElement.ENABLED,false); } }