/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.
*/
package org.wso2.siddhi.extension.input.mapper.text;
import org.apache.log4j.Logger;
import org.wso2.siddhi.annotation.Example;
import org.wso2.siddhi.annotation.Extension;
import org.wso2.siddhi.core.event.ComplexEventChunk;
import org.wso2.siddhi.core.event.Event;
import org.wso2.siddhi.core.exception.ExecutionPlanRuntimeException;
import org.wso2.siddhi.core.query.output.callback.OutputCallback;
import org.wso2.siddhi.core.stream.AttributeMapping;
import org.wso2.siddhi.core.stream.input.InputEventHandler;
import org.wso2.siddhi.core.stream.input.source.Source;
import org.wso2.siddhi.core.stream.input.source.SourceMapper;
import org.wso2.siddhi.core.util.AttributeConverter;
import org.wso2.siddhi.core.util.config.ConfigReader;
import org.wso2.siddhi.core.util.transport.OptionHolder;
import org.wso2.siddhi.query.api.definition.Attribute;
import org.wso2.siddhi.query.api.definition.StreamDefinition;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This mapper converts TEXT string input to {@link ComplexEventChunk}. This extension accepts optional regex mapping to
* select specific attributes from the stream.
*/
@Extension(
name = "text",
namespace = "sourceMapper",
description = "TBD",
examples = @Example(description = "TBD", syntax = "TBD")
)
public class TextSourceMapper extends SourceMapper {
/**
* Logger to log the events.
*/
private static final Logger log = Logger.getLogger(TextSourceMapper.class);
/**
* Default regex assumes that the attributes in the text are separated by comma in order.
*/
public static final String DEFAULT_MAPPING_REGEX = "([^,;]+)";
/**
* Output StreamDefinition of the input mapper.
*/
private StreamDefinition streamDefinition;
/**
* Attributes of the output stream.
*/
private List<Attribute> streamAttributes;
/**
* Array of information about event mapping.
*/
private MappingPositionData[] mappingPositions;
private AttributeConverter attributeConverter;
/**
* Initialize the mapper and the mapping configurations.
* @param streamDefinition the StreamDefinition
* @param optionHolder mapping options
* @param attributeMappingList list of attributes mapping
* @param configReader
*/
@Override
public void init(StreamDefinition streamDefinition, OptionHolder optionHolder, List<AttributeMapping>
attributeMappingList, ConfigReader configReader) {
attributeConverter = new AttributeConverter();
this.streamDefinition = streamDefinition;
this.streamAttributes = this.streamDefinition.getAttributeList();
int attributesSize = this.streamDefinition.getAttributeList().size();
this.mappingPositions = new MappingPositionData[attributesSize];
// Create the position regex arrays
if (attributeMappingList != null && attributeMappingList.size() > 0) {
// Custom regex parameters are given
for (int i = 0; i < attributeMappingList.size(); i++) {
// i represents the position of attributes as given by the user in mapping
AttributeMapping attributeMapping = attributeMappingList.get(i);
// The optional name given by the user in attribute mapping
String attributeName = attributeMapping.getRename();
// The position of the attribute in the output stream definition
int position;
if (attributeName != null) {
// Use the name to determine the position
position = this.streamDefinition.getAttributePosition(attributeName);
} else {
// Use the same order as provided by the user
position = i;
}
String[] mappingComponents = attributeMapping.getMapping().split("\\[");
String regex = optionHolder.validateAndGetStaticValue(mappingComponents[0]);
int index = Integer.parseInt(mappingComponents[1].substring(0, mappingComponents[1].length() - 1));
this.mappingPositions[i] = new MappingPositionData(position, regex, index);
}
} else {
StringBuilder regexBuilder = new StringBuilder();
for (int i = 0; i < attributesSize; i++) {
regexBuilder.append(DEFAULT_MAPPING_REGEX).append(",?");
}
//String regex = regexBuilder.toString();
for (int i = 0; i < attributesSize; i++) {
this.mappingPositions[i] = new MappingPositionData(i, regexBuilder.toString(), i + 1);
}
}
}
/**
* Receive TEXT string from {@link Source}, convert to {@link ComplexEventChunk} and send to the
* {@link OutputCallback}.
*
* @param eventObject the TEXT string
*/
@Override
protected void mapAndProcess(Object eventObject, InputEventHandler inputEventHandler) throws InterruptedException {
if (eventObject != null) {
synchronized (this) {
Event event = convertToEvent(eventObject);
inputEventHandler.sendEvent(event);
}
}
}
/**
* Convert the given TEXT string to {@link Event}
*
* @param eventObject TEXT string
* @return the constructed Event object
*/
private Event convertToEvent(Object eventObject) {
// Validate the event
if (eventObject == null) {
throw new ExecutionPlanRuntimeException("Null object received from the Source to TextsourceMapper");
}
if (!(eventObject instanceof String)) {
throw new ExecutionPlanRuntimeException("Invalid TEXT object received. Expected String, but found " +
eventObject.getClass()
.getCanonicalName());
}
Event event = new Event(this.streamDefinition.getAttributeList().size());
String eventObj = (String) eventObject;
if (eventObj.contains("siddhiEventId:")) {
// siddhiEventId should be the first line of the message
event.setId(Long.parseLong(eventObj.split("\n")[0].split("siddhiEventId:")[1]));
eventObj = eventObj.replaceFirst(eventObj.split("\n")[0] + "\n", "");
}
Object[] data = event.getData();
for (MappingPositionData mappingPositionData : this.mappingPositions) {
int position = mappingPositionData.getPosition();
Attribute attribute = streamAttributes.get(position);
data[position] = attributeConverter.getPropertyValue(mappingPositionData.match(eventObj),
attribute.getType());
}
return event;
}
/**
* A POJO class which holds the attribute position in output stream, regex mapping and gruping index.
*/
private class MappingPositionData {
/**
* Attribute position in the output stream.
*/
private int position;
/**
* User defined regex value.
* It can be shared across multiple {@link MappingPositionData}.
*/
private String regex;
/**
* The group index to be used for mapping in the given regex.
*/
private int groupIndex;
public MappingPositionData(int position, String regex, int groupIndex) {
this.position = position;
this.regex = regex;
this.groupIndex = groupIndex;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
}
public int getGroupIndex() {
return groupIndex;
}
public void setGroupIndex(int groupIndex) {
this.groupIndex = groupIndex;
}
/**
* Match the given text against the regex and return the group defined by the group index.
*
* @param text the input text
* @return matched output
*/
public String match(String text) {
String matchedText;
Pattern pattern = Pattern.compile(this.regex);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
matchedText = matcher.group(this.groupIndex);
} else {
matchedText = null;
}
return matchedText;
}
}
}