/*
* 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 org.apache.ambari.server.state.quicklinksprofile;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.deser.std.StdDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;
/**
* Loads and parses JSON quicklink profiles.
*/
public class QuickLinksProfileParser {
private final ObjectMapper mapper = new ObjectMapper();
public QuickLinksProfileParser() {
SimpleModule module =
new SimpleModule("Quick Links Parser", new Version(1, 0, 0, null));
module.addDeserializer(Filter.class, new QuickLinksFilterDeserializer());
mapper.registerModule(module);
}
public QuickLinksProfile parse(byte[] input) throws IOException {
return mapper.readValue(input, QuickLinksProfile.class);
}
public QuickLinksProfile parse(URL url) throws IOException {
return parse(Resources.toByteArray(url));
}
public String encode(QuickLinksProfile profile) throws IOException {
return mapper.writeValueAsString(profile);
}
}
/**
* Custom deserializer is needed to handle filter polymorphism.
*/
class QuickLinksFilterDeserializer extends StdDeserializer<Filter> {
static final String PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER =
"A filter is not allowed to declare both link_name and link_attribute at the same time.";
static final String PARSE_ERROR_MESSAGE_INVALID_JSON_TAG =
"Invalid attribute(s) in filter declaration: ";
QuickLinksFilterDeserializer() {
super(Filter.class);
}
/**
* Filter polymorphism is handled here. If a filter object in the JSON document has:
* <ul>
* <li>a {@code link_attribute} field, it will parsed as {@link LinkAttributeFilter}</li>
* <li>a {@code link_name} field, it will be parsed as {@link LinkNameFilter}</li>
* <li>both {@code link_attribute} and {@code link_name}, it will throw a {@link JsonParseException}</li>
* <li>neither of the above fields, it will be parsed as {@link AcceptAllFilter}</li>
* </ul>
*
* @throws JsonParseException if ambiguous filter definitions are found, or any JSON syntax error.
*/
@Override
public Filter deserialize (JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(parser);
Class<? extends Filter> filterClass = null;
List<String> invalidAttributes = new ArrayList<>();
for (String fieldName: ImmutableList.copyOf(root.getFieldNames())) {
switch(fieldName) {
case LinkAttributeFilter.LINK_ATTRIBUTE:
if (null != filterClass) {
throw new JsonParseException(PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER, parser.getCurrentLocation());
}
filterClass = LinkAttributeFilter.class;
break;
case LinkNameFilter.LINK_NAME:
if (null != filterClass) {
throw new JsonParseException(PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER, parser.getCurrentLocation());
}
filterClass = LinkNameFilter.class;
break;
case Filter.VISIBLE:
// silently ignore here, will be parsed later in mapper.readValue
break;
default:
invalidAttributes.add(fieldName);
}
}
if (!invalidAttributes.isEmpty()) {
throw new JsonParseException(PARSE_ERROR_MESSAGE_INVALID_JSON_TAG + invalidAttributes,
parser.getCurrentLocation());
}
if (null == filterClass) {
filterClass = AcceptAllFilter.class;
}
return mapper.readValue(root, filterClass);
}
}