/* * Copyright (c) 2015 Spotify AB. * * 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. */ package com.spotify.heroic.filter; import static com.spotify.heroic.filter.FilterEncoding.filter; import static com.spotify.heroic.filter.FilterEncoding.string; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.common.collect.ImmutableMap; import com.spotify.heroic.grammar.QueryParser; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class FilterRegistry { private final Map<String, FilterEncoding<? extends Filter>> deserializers = new HashMap<>(); private final Map<Class<? extends Filter>, JsonSerializer<Filter>> serializers = new HashMap<>(); private final HashMap<Class<?>, String> typeMapping = new HashMap<>(); public <T extends Filter> void registerList( String id, Class<T> type, FilterEncoding<T> s ) { registerJson(id, type, s); register(id, type); } public <T extends Filter> void registerTwo( String id, Class<T> type, FilterEncoding<T> s ) { registerJson(id, type, s); register(id, type); } public <T extends Filter> void registerOne( String id, Class<T> type, FilterEncoding<T> s ) { registerJson(id, type, s); register(id, type); } public <T extends Filter> void registerEmpty( String id, Class<T> type, FilterEncoding<T> s ) { registerJson(id, type, s); register(id, type); } public Module module(final QueryParser parser) { final SimpleModule m = new SimpleModule("filter"); for (final Map.Entry<Class<? extends Filter>, JsonSerializer<Filter>> e : this .serializers.entrySet()) { m.addSerializer(e.getKey(), e.getValue()); } final FilterJsonDeserializer deserializer = new FilterJsonDeserializer(ImmutableMap.copyOf(deserializers), parser); m.addDeserializer(Filter.class, deserializer); return m; } @SuppressWarnings("unchecked") private <T extends Filter> void registerJson( String id, Class<T> type, FilterEncoding<T> serialization ) { serializers.put(type, new FilterJsonSerializer((FilterEncoding<Filter>) serialization)); deserializers.put(id, serialization); } private <T extends Filter> void register(String id, Class<T> type) { if (typeMapping.put(type, id) != null) { throw new IllegalStateException("Multiple mappings for single type: " + type); } } @RequiredArgsConstructor private static final class FilterJsonSerializer extends JsonSerializer<Filter> { private final FilterEncoding<Filter> serializer; @Override public void serialize(Filter value, JsonGenerator g, SerializerProvider provider) throws IOException { g.writeStartArray(); g.writeString(value.operator()); final EncoderImpl s = new EncoderImpl(g); serializer.serialize(s, value); g.writeEndArray(); } @RequiredArgsConstructor private static final class EncoderImpl implements FilterEncoding.Encoder { private final JsonGenerator generator; @Override public void string(String string) throws IOException { generator.writeString(string); } @Override public void filter(Filter filter) throws IOException { generator.writeObject(filter); } } } @RequiredArgsConstructor static class FilterJsonDeserializer extends JsonDeserializer<Filter> { final Map<String, FilterEncoding<? extends Filter>> deserializers; final QueryParser parser; @Override public Filter deserialize(JsonParser p, DeserializationContext c) throws IOException, JsonProcessingException { if (p.getCurrentToken() != JsonToken.START_ARRAY) { throw c.mappingException("Expected start of array"); } if (p.nextToken() != JsonToken.VALUE_STRING) { throw c.mappingException("Expected operator (string)"); } final String operator = p.readValueAs(String.class); final FilterEncoding<? extends Filter> deserializer = deserializers.get(operator); if (deserializer == null) { throw c.mappingException("No such operator: " + operator); } p.nextToken(); final FilterEncoding.Decoder d = new Decoder(p, c); final Filter filter; try { filter = deserializer.deserialize(d); if (p.getCurrentToken() != JsonToken.END_ARRAY) { throw c.mappingException("Expected end of array from '" + deserializer + "'"); } if (filter instanceof RawFilter) { return parseRawFilter((RawFilter) filter); } return filter.optimize(); } catch (final Exception e) { // use special {operator} syntax to indicate filter. throw JsonMappingException.wrapWithPath(e, this, "{" + operator + "}"); } } private Filter parseRawFilter(RawFilter filter) { return parser.parseFilter(filter.getFilter()); } @RequiredArgsConstructor private static final class Decoder implements FilterEncoding.Decoder { private final JsonParser parser; private final DeserializationContext c; private int index = 0; @Override public Optional<String> string() throws IOException { final int index = this.index++; if (parser.getCurrentToken() == JsonToken.END_ARRAY) { return Optional.empty(); } if (parser.getCurrentToken() != JsonToken.VALUE_STRING) { throw c.mappingException("Expected string"); } final String string; try { string = parser.getValueAsString(); } catch (final JsonMappingException e) { throw JsonMappingException.wrapWithPath(e, this, index); } parser.nextToken(); return Optional.of(string); } @Override public Optional<Filter> filter() throws IOException { final int index = this.index++; if (parser.getCurrentToken() == JsonToken.END_ARRAY) { return Optional.empty(); } if (parser.getCurrentToken() != JsonToken.START_ARRAY) { throw c.mappingException("Expected start of new filter expression"); } final Filter filter; try { filter = parser.readValueAs(Filter.class); } catch (final JsonMappingException e) { throw JsonMappingException.wrapWithPath(e, this, index); } parser.nextToken(); return Optional.of(filter); } } } public static FilterRegistry registry() { final FilterRegistry registry = new FilterRegistry(); registry.registerList(AndFilter.OPERATOR, AndFilter.class, new MultiArgumentsFilterBase<>(AndFilter::new, AndFilter::getStatements, filter())); registry.registerList(OrFilter.OPERATOR, OrFilter.class, new MultiArgumentsFilterBase<>(OrFilter::new, OrFilter::getStatements, filter())); registry.registerOne(NotFilter.OPERATOR, NotFilter.class, new OneArgumentFilterEncoding<>(NotFilter::new, NotFilter::getFilter, filter())); registry.registerTwo(MatchKeyFilter.OPERATOR, MatchKeyFilter.class, new OneArgumentFilterEncoding<>(MatchKeyFilter::new, MatchKeyFilter::getValue, string())); registry.registerTwo(MatchTagFilter.OPERATOR, MatchTagFilter.class, new TwoArgumentFilterEncoding<>(MatchTagFilter::new, MatchTagFilter::getTag, MatchTagFilter::getValue, string(), string())); registry.registerOne(HasTagFilter.OPERATOR, HasTagFilter.class, new OneArgumentFilterEncoding<>(HasTagFilter::new, HasTagFilter::getTag, string())); registry.registerTwo(StartsWithFilter.OPERATOR, StartsWithFilter.class, new TwoArgumentFilterEncoding<>(StartsWithFilter::new, StartsWithFilter::getTag, StartsWithFilter::getValue, string(), string())); registry.registerTwo(RegexFilter.OPERATOR, RegexFilter.class, new TwoArgumentFilterEncoding<>(RegexFilter::new, RegexFilter::getTag, RegexFilter::getValue, string(), string())); registry.registerEmpty(TrueFilter.OPERATOR, TrueFilter.class, new NoArgumentFilterBase<>(TrueFilter::get)); registry.registerEmpty(FalseFilter.OPERATOR, FalseFilter.class, new NoArgumentFilterBase<>(FalseFilter::get)); registry.registerOne(RawFilter.OPERATOR, RawFilter.class, new OneArgumentFilterEncoding<>(RawFilter::new, RawFilter::getFilter, string())); return registry; } }