| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2014 École Polytechnique de Montréal |
| 3 | * |
| 4 | * All rights reserved. This program and the accompanying materials are |
| 5 | * made available under the terms of the Eclipse Public License v1.0 which |
| 6 | * accompanies this distribution, and is available at |
| 7 | * http://www.eclipse.org/legal/epl-v10.html |
| 8 | * |
| 9 | * Contributors: |
| 10 | * Geneviève Bastien - Initial implementation |
| 11 | *******************************************************************************/ |
| 12 | |
| 13 | package org.eclipse.linuxtools.tmf.tests.stubs.trace.xml; |
| 14 | |
| 15 | import java.io.File; |
| 16 | import java.io.IOException; |
| 17 | import java.io.InputStream; |
| 18 | import java.net.URL; |
| 19 | |
| 20 | import javax.xml.XMLConstants; |
| 21 | import javax.xml.transform.Source; |
| 22 | import javax.xml.transform.stream.StreamSource; |
| 23 | import javax.xml.validation.Schema; |
| 24 | import javax.xml.validation.SchemaFactory; |
| 25 | import javax.xml.validation.Validator; |
| 26 | |
| 27 | import org.eclipse.core.resources.IProject; |
| 28 | import org.eclipse.core.resources.IResource; |
| 29 | import org.eclipse.core.runtime.IStatus; |
| 30 | import org.eclipse.core.runtime.Status; |
| 31 | import org.eclipse.jdt.annotation.NonNull; |
| 32 | import org.eclipse.linuxtools.internal.tmf.core.Activator; |
| 33 | import org.eclipse.linuxtools.tmf.core.event.ITmfEvent; |
| 34 | import org.eclipse.linuxtools.tmf.core.event.ITmfEventField; |
| 35 | import org.eclipse.linuxtools.tmf.core.event.ITmfEventType; |
| 36 | import org.eclipse.linuxtools.tmf.core.event.TmfEvent; |
| 37 | import org.eclipse.linuxtools.tmf.core.event.TmfEventField; |
| 38 | import org.eclipse.linuxtools.tmf.core.event.TmfEventType; |
| 39 | import org.eclipse.linuxtools.tmf.core.exceptions.TmfTraceException; |
| 40 | import org.eclipse.linuxtools.tmf.core.parsers.custom.CustomEventContent; |
| 41 | import org.eclipse.linuxtools.tmf.core.parsers.custom.CustomXmlEvent; |
| 42 | import org.eclipse.linuxtools.tmf.core.parsers.custom.CustomXmlTrace; |
| 43 | import org.eclipse.linuxtools.tmf.core.parsers.custom.CustomXmlTraceDefinition; |
| 44 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager; |
| 45 | import org.eclipse.linuxtools.tmf.core.timestamp.ITmfTimestamp; |
| 46 | import org.eclipse.linuxtools.tmf.core.trace.ITmfContext; |
| 47 | import org.eclipse.linuxtools.tmf.core.trace.TmfContext; |
| 48 | import org.eclipse.linuxtools.tmf.core.trace.TmfTrace; |
| 49 | import org.eclipse.linuxtools.tmf.core.trace.location.ITmfLocation; |
| 50 | import org.eclipse.osgi.util.NLS; |
| 51 | import org.xml.sax.SAXException; |
| 52 | |
| 53 | /** |
| 54 | * An XML development trace using a custom XML trace definition and schema. |
| 55 | * |
| 56 | * This class will typically be used to build custom traces to unit test more |
| 57 | * complex functionalities like analyzes or to develop and test data-driven |
| 58 | * analyzes. |
| 59 | * |
| 60 | * This class wraps a custom XML trace and rewrites the returned events in the |
| 61 | * getNext() method so that event's fields are the ones defined in <field ... /> |
| 62 | * elements instead of those defined in the custom XML parser. This way, each |
| 63 | * event can have a different set of fields. This class can, for example, mimic |
| 64 | * a CTF trace. |
| 65 | * |
| 66 | * @author Geneviève Bastien |
| 67 | */ |
| 68 | public class TmfXmlTraceStub extends TmfTrace { |
| 69 | |
| 70 | private static final String DEVELOPMENT_TRACE_PARSER_PATH = "TmfXmlDevelopmentTrace.xml"; //$NON-NLS-1$ |
| 71 | private static final String DEVELOPMENT_TRACE_XSD = "TmfXmlDevelopmentTrace.xsd"; //$NON-NLS-1$ |
| 72 | private static final String EMPTY = ""; //$NON-NLS-1$ |
| 73 | |
| 74 | /* XML elements and attributes names */ |
| 75 | private static final String EVENT_NAME_FIELD = "Message"; //$NON-NLS-1$ |
| 76 | private static final String FIELD_NAMES_FIELD = "fields"; //$NON-NLS-1$ |
| 77 | private static final String SOURCE_FIELD = "source"; //$NON-NLS-1$ |
| 78 | private static final String VALUES_FIELD = "values"; //$NON-NLS-1$ |
| 79 | private static final String TYPES_FIELD = "type"; //$NON-NLS-1$ |
| 80 | private static final String VALUES_SEPARATOR = " \\| "; //$NON-NLS-1$ |
| 81 | private static final String TYPE_INTEGER = "int"; //$NON-NLS-1$ |
| 82 | private static final String TYPE_LONG = "long"; //$NON-NLS-1$ |
| 83 | |
| 84 | private final CustomXmlTrace fTrace; |
| 85 | |
| 86 | /** |
| 87 | * Constructor. Constructs the custom XML trace with the appropriate |
| 88 | * definition. |
| 89 | */ |
| 90 | public TmfXmlTraceStub() { |
| 91 | |
| 92 | /* Load custom XML definition */ |
| 93 | try (InputStream in = TmfXmlTraceStub.class.getResourceAsStream(DEVELOPMENT_TRACE_PARSER_PATH);) { |
| 94 | CustomXmlTraceDefinition[] definitions = CustomXmlTraceDefinition.loadAll(in); |
| 95 | if (definitions.length == 0) { |
| 96 | throw new IllegalStateException("The custom trace definition does not exist"); //$NON-NLS-1$ |
| 97 | } |
| 98 | fTrace = new CustomXmlTrace(definitions[0]); |
| 99 | /* Deregister the custom XML trace */ |
| 100 | TmfSignalManager.deregister(fTrace); |
| 101 | this.setParser(fTrace); |
| 102 | } catch (IOException e) { |
| 103 | throw new IllegalStateException("Cannot open the trace parser for development traces"); //$NON-NLS-1$ |
| 104 | } |
| 105 | |
| 106 | } |
| 107 | |
| 108 | @Override |
| 109 | public void initTrace(IResource resource, String path, Class<? extends ITmfEvent> type) throws TmfTraceException { |
| 110 | super.initTrace(resource, path, type); |
| 111 | fTrace.initTrace(resource, path, type); |
| 112 | ITmfContext ctx; |
| 113 | /* Set the start and (current) end times for this trace */ |
| 114 | ctx = seekEvent(0L); |
| 115 | ITmfEvent event = getNext(ctx); |
| 116 | if (event != null) { |
| 117 | final ITmfTimestamp curTime = event.getTimestamp(); |
| 118 | this.setStartTime(curTime); |
| 119 | this.setEndTime(curTime); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | @Override |
| 124 | public ITmfLocation getCurrentLocation() { |
| 125 | return fTrace.getCurrentLocation(); |
| 126 | } |
| 127 | |
| 128 | @Override |
| 129 | public double getLocationRatio(ITmfLocation location) { |
| 130 | return fTrace.getLocationRatio(location); |
| 131 | } |
| 132 | |
| 133 | @Override |
| 134 | public ITmfContext seekEvent(ITmfLocation location) { |
| 135 | return fTrace.seekEvent(location); |
| 136 | } |
| 137 | |
| 138 | @Override |
| 139 | public ITmfContext seekEvent(double ratio) { |
| 140 | return fTrace.seekEvent(ratio); |
| 141 | } |
| 142 | |
| 143 | @Override |
| 144 | public IStatus validate(IProject project, String path) { |
| 145 | File xmlFile = new File(path); |
| 146 | if (!xmlFile.exists() || !xmlFile.isFile() || !xmlFile.canRead()) { |
| 147 | return new Status(IStatus.ERROR, Activator.PLUGIN_ID, NLS.bind(org.eclipse.linuxtools.tmf.tests.stubs.trace.xml.Messages.TmfDevelopmentTrace_FileNotFound, path)); |
| 148 | } |
| 149 | /* Does the XML file validate with the XSD */ |
| 150 | SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| 151 | Source xmlSource = new StreamSource(xmlFile); |
| 152 | |
| 153 | try { |
| 154 | URL url = TmfXmlTraceStub.class.getResource(DEVELOPMENT_TRACE_XSD); |
| 155 | Schema schema = schemaFactory.newSchema(url); |
| 156 | |
| 157 | Validator validator = schema.newValidator(); |
| 158 | validator.validate(xmlSource); |
| 159 | } catch (SAXException e) { |
| 160 | return new Status(IStatus.ERROR, Activator.PLUGIN_ID, NLS.bind(org.eclipse.linuxtools.tmf.tests.stubs.trace.xml.Messages.TmfDevelopmentTrace_ValidationError, path), e); |
| 161 | } catch (IOException e) { |
| 162 | return new Status(IStatus.ERROR, Activator.PLUGIN_ID, NLS.bind(org.eclipse.linuxtools.tmf.tests.stubs.trace.xml.Messages.TmfDevelopmentTrace_IoError, path), e); |
| 163 | } |
| 164 | return Status.OK_STATUS; |
| 165 | } |
| 166 | |
| 167 | private static String getStringValue(@NonNull ITmfEventField content, String fieldName) { |
| 168 | ITmfEventField field = content.getField(fieldName); |
| 169 | if (field == null) { |
| 170 | return EMPTY; |
| 171 | } |
| 172 | Object val = field.getValue(); |
| 173 | if (!(val instanceof String)) { |
| 174 | return EMPTY; |
| 175 | } |
| 176 | return (String) val; |
| 177 | } |
| 178 | |
| 179 | @Override |
| 180 | public synchronized ITmfEvent getNext(ITmfContext context) { |
| 181 | final ITmfContext savedContext = new TmfContext(context.getLocation(), context.getRank()); |
| 182 | CustomXmlEvent event = fTrace.getNext(context); |
| 183 | if (event == null) { |
| 184 | return null; |
| 185 | } |
| 186 | |
| 187 | /* Translate the content of the event */ |
| 188 | /* The "fields" field contains a | separated list of field names */ |
| 189 | /* The "values" field contains a | separated list of field values */ |
| 190 | /* the "type" field contains a | separated list of field types */ |
| 191 | ITmfEventField content = event.getContent(); |
| 192 | if (content == null) { |
| 193 | return null; |
| 194 | } |
| 195 | String fieldString = getStringValue(content, FIELD_NAMES_FIELD); |
| 196 | String valueString = getStringValue(content, VALUES_FIELD); |
| 197 | String typeString = getStringValue(content, TYPES_FIELD); |
| 198 | |
| 199 | String[] fields = fieldString.split(VALUES_SEPARATOR); |
| 200 | String[] values = valueString.split(VALUES_SEPARATOR); |
| 201 | String[] types = typeString.split(VALUES_SEPARATOR); |
| 202 | ITmfEventField[] fieldsArray = new TmfEventField[fields.length]; |
| 203 | |
| 204 | for (int i = 0; i < fields.length; i++) { |
| 205 | String value = EMPTY; |
| 206 | if (values.length > i) { |
| 207 | value = values[i]; |
| 208 | } |
| 209 | String type = null; |
| 210 | if (types.length > i) { |
| 211 | type = types[i]; |
| 212 | } |
| 213 | Object val = value; |
| 214 | if (type != null) { |
| 215 | switch (type) { |
| 216 | case TYPE_INTEGER: { |
| 217 | try { |
| 218 | val = Integer.valueOf(value); |
| 219 | } catch (NumberFormatException e) { |
| 220 | Activator.logError(String.format("Get next XML event: cannot cast value %s to integer", value), e); //$NON-NLS-1$ |
| 221 | val = 0; |
| 222 | } |
| 223 | break; |
| 224 | } |
| 225 | case TYPE_LONG: { |
| 226 | try { |
| 227 | val = Long.valueOf(value); |
| 228 | } catch (NumberFormatException e) { |
| 229 | Activator.logError(String.format("Get next XML event: cannot cast value %s to long", value), e); //$NON-NLS-1$ |
| 230 | val = 0L; |
| 231 | } |
| 232 | break; |
| 233 | } |
| 234 | default: |
| 235 | break; |
| 236 | } |
| 237 | } |
| 238 | fieldsArray[i] = new TmfEventField(fields[i], val, null); |
| 239 | } |
| 240 | |
| 241 | /* Create a new event with new fields and name */ |
| 242 | ITmfEventType customEventType = event.getType(); |
| 243 | TmfEventType eventType = new TmfEventType(customEventType.getContext(), getStringValue(content, EVENT_NAME_FIELD), customEventType.getRootField()); |
| 244 | ITmfEventField eventFields = new CustomEventContent(content.getName(), content.getValue(), fieldsArray); |
| 245 | TmfEvent newEvent = new TmfEvent(this, event.getTimestamp(), getStringValue(content, SOURCE_FIELD), eventType, eventFields, event.getReference()); |
| 246 | updateAttributes(savedContext, event.getTimestamp()); |
| 247 | context.increaseRank(); |
| 248 | |
| 249 | return newEvent; |
| 250 | } |
| 251 | |
| 252 | } |