package com.belladati.sdk.view.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
import com.belladati.sdk.exception.impl.UnknownViewTypeException;
import com.belladati.sdk.exception.interval.InvalidIntervalException;
import com.belladati.sdk.filter.Filter;
import com.belladati.sdk.filter.Filter.MultiValueFilter;
import com.belladati.sdk.impl.BellaDatiServiceImpl;
import com.belladati.sdk.filter.FilterOperation;
import com.belladati.sdk.filter.FilterValue;
import com.belladati.sdk.intervals.AbsoluteInterval;
import com.belladati.sdk.intervals.CustomInterval;
import com.belladati.sdk.intervals.DateUnit;
import com.belladati.sdk.intervals.Interval;
import com.belladati.sdk.intervals.RelativeInterval;
import com.belladati.sdk.intervals.TimeUnit;
import com.belladati.sdk.util.impl.LocalizationImpl;
import com.belladati.sdk.view.View;
import com.belladati.sdk.view.ViewLoader;
import com.belladati.sdk.view.ViewType;
import com.belladati.sdk.view.export.ViewExporter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
public abstract class ViewImpl implements View {
/**
*
* Builds an instance based on the given node. Will select an appropriate
* class to instantiate based on the view's type.
*
* @param service BelladatiService
* @param node json node to create view from
* @return assembled ViewImpl
* @throws UnknownViewTypeException
*/
public static ViewImpl buildView(BellaDatiServiceImpl service, JsonNode node) throws UnknownViewTypeException {
switch (parseType(node)) {
case TABLE:
return new TableViewImpl(service, node);
case IMAGE:
return new ImageViewImpl(service, node);
default:
return new JsonViewImpl(service, node);
}
}
/**
* Parses the view type from the given JSON node.
*
* @param node the node to examine
* @return the view type from the node
* @throws UnknownViewTypeException if no view type was found or it couldn't
* be parsed
*/
private static ViewType parseType(JsonNode node) throws UnknownViewTypeException {
if (node.hasNonNull("type")) {
try {
return ViewType.valueOf(node.get("type").asText().toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
throw new UnknownViewTypeException(node.get("type").asText());
}
} else {
throw new UnknownViewTypeException("missing type");
}
}
/**
* Parses the date interval from the given date/time definition node.
*
* @param node the node to examine
* @return the node's date interval, or <tt>null</tt> if none is defined or
* it's invalid
*/
private static Interval<DateUnit> parseDateInterval(JsonNode node) {
try {
if (node.hasNonNull("dateInterval") && node.get("dateInterval").hasNonNull("aggregationType")) {
JsonNode dateInterval = node.get("dateInterval");
DateUnit unit = DateUnit.valueOf(dateInterval.get("aggregationType").asText().toUpperCase(Locale.ENGLISH));
JsonNode interval = dateInterval.get("interval");
String type = interval.get("type").asText().toLowerCase(Locale.ENGLISH);
if ("relative".equals(type)) {
// the server may send numbers inside strings
// or numbers as decimals, e.g. 3.0
// regardless, we treat them all as int
String from = interval.get("from").asText();
String to = interval.get("to").asText();
return new RelativeInterval<DateUnit>(unit, (int) Float.parseFloat(from), (int) Float.parseFloat(to));
} else if ("absolute".equals(type)) {
// an absolute interval
return new AbsoluteInterval<DateUnit>(unit, unit.parseAbsolute(interval.get("from")),
unit.parseAbsolute(interval.get("to")));
} else if ("custom".equals(type)) {
// a custom interval
return new CustomInterval<DateUnit>(unit, interval.get("from").asText(), interval.get("to").asText());
}
}
} catch (InvalidIntervalException e) {
// ignore the interval
} catch (NumberFormatException e) {
// ignore the interval
} catch (IllegalArgumentException e) {
// ignore the interval
}
return null;
}
/**
* Parses the time interval from the given date/time definition node.
*
* @param node the node to examine
* @return the node's time interval, or <tt>null</tt> if none is defined or
* it's invalid
*/
private static Interval<TimeUnit> parseTimeInterval(JsonNode node) {
try {
if (node.hasNonNull("timeInterval") && node.get("timeInterval").hasNonNull("aggregationType")) {
JsonNode timeInterval = node.get("timeInterval");
TimeUnit unit = TimeUnit.valueOf(timeInterval.get("aggregationType").asText().toUpperCase(Locale.ENGLISH));
JsonNode interval = timeInterval.get("interval");
String type = interval.get("type").asText().toLowerCase(Locale.ENGLISH);
if ("relative".equals(type)) {
// the server may send numbers inside strings
// or numbers as decimals, e.g. 3.0
// regardless, we treat them all as int
String from = interval.get("from").asText();
String to = interval.get("to").asText();
return new RelativeInterval<TimeUnit>(unit, (int) Float.parseFloat(from), (int) Float.parseFloat(to));
} else if ("absolute".equals(type)) {
// an absolute interval
return new AbsoluteInterval<TimeUnit>(unit, unit.parseAbsolute(interval.get("from")),
unit.parseAbsolute(interval.get("to")));
} else if ("custom".equals(type)) {
// a custom interval
return new CustomInterval<TimeUnit>(unit, interval.get("from").asText(), interval.get("to").asText());
}
}
} catch (InvalidIntervalException e) {
// ignore the interval
} catch (NumberFormatException e) {
// ignore the interval
} catch (IllegalArgumentException e) {
// ignore the interval
}
return null;
}
private static Set<Filter<?>> parseFilter(JsonNode node) {
if (!node.hasNonNull("filter") || !node.get("filter").isObject() || !node.get("filter").hasNonNull("drilldown")
|| !node.get("filter").get("drilldown").isObject()) {
return Collections.emptySet();
}
Set<Filter<?>> filters = new HashSet<Filter<?>>();
for (Iterator<Entry<String, JsonNode>> entries = node.get("filter").get("drilldown").fields(); entries.hasNext();) {
Entry<String, JsonNode> entry = entries.next();
String code = entry.getKey();
String op = entry.getValue().get("op").asText();
FilterOperation<?> operation = findOperation(op);
if (operation != null) {
Filter<?> filter = operation.createFilter(null, null, code);
if (filter instanceof MultiValueFilter && entry.getValue().hasNonNull("values")) {
for (JsonNode value : (ArrayNode) entry.getValue().get("values")) {
((MultiValueFilter) filter).addAll(new FilterValue(value.asText()));
}
}
filters.add(filter);
}
}
return filters;
}
private static FilterOperation<?> findOperation(String op) {
for (FilterOperation<?> operation : FilterOperation.values()) {
if (operation.toString().equalsIgnoreCase(op)) {
return operation;
}
}
return null;
}
protected final BellaDatiServiceImpl service;
private final String id;
private final String name;
private final ViewType type;
private final boolean dateIntervalSupported;
private final boolean timeIntervalSupported;
private final Interval<DateUnit> dateInterval;
private final Interval<TimeUnit> timeInterval;
private final Set<Filter<?>> filters;
private final LocalizationImpl localization;
protected ViewImpl(BellaDatiServiceImpl service, JsonNode node) throws UnknownViewTypeException {
this.service = service;
this.id = node.get("id").asText();
this.name = node.get("name").asText();
this.type = parseType(node);
this.filters = parseFilter(node);
if (node.hasNonNull("dateTimeDefinition") && this.type != ViewType.TABLE) {
// we have a date/time definition and are not dealing with a table
JsonNode definition = node.get("dateTimeDefinition");
if (definition.hasNonNull("dateSupported")) {
dateIntervalSupported = definition.get("dateSupported").asBoolean();
} else {
dateIntervalSupported = false;
}
if (definition.hasNonNull("timeSupported")) {
timeIntervalSupported = definition.get("timeSupported").asBoolean();
} else {
timeIntervalSupported = false;
}
dateInterval = parseDateInterval(definition);
timeInterval = parseTimeInterval(definition);
} else {
dateIntervalSupported = false;
timeIntervalSupported = false;
dateInterval = null;
timeInterval = null;
}
this.localization = new LocalizationImpl(node);
}
@Override
public String getId() {
return id;
}
@Override
public String getName() {
return name;
}
@Override
public String getName(Locale locale) {
return localization.getName(locale);
}
@Override
public boolean hasLocalization(Locale locale) {
return localization.hasLocalization(locale);
}
@Override
public ViewType getType() {
return type;
}
@Override
public Object loadContent(Filter<?>... filters) {
return service.loadViewContent(id, type, filters);
}
@Override
public Object loadContent(Collection<Filter<?>> filters) {
return service.loadViewContent(id, type, filters);
}
@Override
public String toString() {
return name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ViewImpl) {
return id.equals(((ViewImpl) obj).id);
}
return false;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean hasPredefinedFilters() {
return !filters.isEmpty();
}
@Override
public Set<Filter<?>> getPredefinedFilters() {
return filters;
}
@Override
public boolean isDateIntervalSupported() {
return dateIntervalSupported;
}
@Override
public boolean isTimeIntervalSupported() {
return timeIntervalSupported;
}
@Override
public boolean hasPredefinedDateInterval() {
return dateInterval != null;
}
@Override
public boolean hasPredefinedTimeInterval() {
return timeInterval != null;
}
@Override
public Interval<DateUnit> getPredefinedDateInterval() {
return dateInterval;
}
@Override
public Interval<TimeUnit> getPredefinedTimeInterval() {
return timeInterval;
}
@Override
public ViewLoader createLoader() {
return service.setupViewLoader(id, type);
}
@Override
public ViewExporter createExporter() {
return service.setupViewExporter(id);
}
}