All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.activemq.artemis.utils.bean.MetaBean Maven / Gradle / Ivy

The newest version!
/*
 * 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.activemq.artemis.utils.bean;

import java.io.StringReader;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;

import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.json.JsonObject;
import org.apache.activemq.artemis.json.JsonObjectBuilder;
import org.apache.activemq.artemis.utils.JsonLoader;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Receives a metadata about a class with methods to read, write and certain gates.
 * And provides a generic logic to convert to and from JSON.
 * 

* As a historical context the first try to make a few objects more dynamic (e.g. AddressSettings) was * around BeanUtils however there was some implicit logic on when certain settings were Null or default values. * for that reason I decided for a meta-data approach where extra semantic could be applied for each individual attributes * rather than a generic BeanUtils parser. */ public class MetaBean { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private CopyOnWriteArrayList> metaData = new CopyOnWriteArrayList<>(); /** * Accepted types: * String.class * SimpleString.class * Integer.class * Long.class * Double.class * Float.class * Enumerations */ public MetaBean add(Class type, String name, BiConsumer setter, Function getter, Predicate gate) { if (type != String.class && type != SimpleString.class && type != Integer.class && type != Long.class && type != Double.class && type != Float.class && type != Boolean.class && !Enum.class.isAssignableFrom(type)) { throw new IllegalArgumentException("invalid type " + type); } metaData.add(new MetaData(type, name, setter, getter, gate)); return this; } public MetaBean add(Class type, String name, BiConsumer setter, Function getter) { return add(type, name, setter, getter, null); } public JsonObject toJSON(T object, boolean ignoreNullAttributes) { JsonObjectBuilder builder = JsonLoader.createObjectBuilder(); parseToJSON(object, builder, ignoreNullAttributes); return builder.build(); } public void parseToJSON(T object, JsonObjectBuilder builder, boolean ignoreNullAttributes) { logger.debug("Parsing object {}", object); this.forEach((type, name, setter, getter, gate) -> { logger.debug("Parsing {} {} {} {} {}", type, name, setter, getter, gate); Object value = getter.apply(object); if (ignoreNullAttributes && value == null) { logger.debug("Ignoring null attribute {}", name); return; } if (gate == null || gate.test(object)) { if (logger.isTraceEnabled()) { if (gate != null) { logger.trace("Gate passed for {}", name); } if (value == null) { logger.debug("Result for {} = IS NULL", name); } else { logger.debug("Result for {} = {}, type={}", name, value, value.getClass()); } } if (value == null) { logger.trace("Setting {} as null", name); builder.addNull(name); } else if (type == String.class || type == SimpleString.class) { logger.trace("Setting {} as String {}", name, value); builder.add(name, String.valueOf(value)); } else if (Number.class.isAssignableFrom(type) && value instanceof Number) { if (value instanceof Double || value instanceof Float) { logger.trace("Setting {} as double {}", name, value); builder.add(name, ((Number) value).doubleValue()); } else { logger.trace("Setting {} as long {}", name, value); builder.add(name, ((Number) value).longValue()); } } else if (type == Boolean.class) { builder.add(name, (Boolean) value); } else if (Enum.class.isAssignableFrom(type)) { // I know this is the same as the default else clause further down // but i wanted to have a separate branch in case we have to deal with it later builder.add(name, String.valueOf(value)); } else { builder.add(name, String.valueOf(value)); } } else { logger.debug("Gate ignored on {}", name); } }); } /** Generates a random Object using the setters for testing purposes. */ public void setRandom(T randomObject) { forEach((type, name, setter, getter, gate) -> { if (Enum.class.isAssignableFrom(type)) { Object[] enumValues = type.getEnumConstants(); int randomInt = RandomUtil.randomInterval(0, enumValues.length - 1); setter.accept(randomObject, enumValues[randomInt]); } else if (type == String.class) { setter.accept(randomObject, RandomUtil.randomString()); } else if (type == SimpleString.class) { setter.accept(randomObject, RandomUtil.randomSimpleString()); } else if (type == Integer.class) { setter.accept(randomObject, RandomUtil.randomPositiveInt()); } else if (type == Long.class) { setter.accept(randomObject, RandomUtil.randomPositiveLong()); } else if (type == Double.class) { setter.accept(randomObject, RandomUtil.randomDouble()); } else if (type == Float.class) { setter.accept(randomObject, RandomUtil.randomFloat()); } else if (type == Boolean.class) { setter.accept(randomObject, RandomUtil.randomBoolean()); } }); } public void copy(T source, T target) { forEach((type, name, setter, getter, gate) -> { Object sourceAttribute = getter.apply(source); setter.accept(target, sourceAttribute); }); } public void forEach(MetadataListener listener) { metaData.forEach(m -> { try { listener.metaItem(m.type, m.name, m.setter, m.getter, m.gate); } catch (Throwable e) { logger.warn("Error parsing {}", m, e); throw new RuntimeException("Error while parsing " + m, e); } }); } public void fromJSON(T resultObject, String jsonString) { logger.debug("Parsing JSON {}", jsonString); JsonObject json = JsonLoader.readObject(new StringReader(jsonString)); this.forEach((type, name, setter, getter, gate) -> { if (json.containsKey(name)) { if (json.isNull(name)) { setter.accept(resultObject, null); } else if (type == String.class) { setter.accept(resultObject, json.getString(name)); } else if (type == SimpleString.class) { setter.accept(resultObject, SimpleString.of(json.getString(name))); } else if (type == Integer.class) { setter.accept(resultObject, json.getInt(name)); } else if (type == Long.class) { setter.accept(resultObject, json.getJsonNumber(name).longValue()); } else if (type == Double.class) { setter.accept(resultObject, json.getJsonNumber(name).doubleValue()); } else if (type == Float.class) { setter.accept(resultObject, json.getJsonNumber(name).numberValue().floatValue()); } else if (type == Boolean.class) { setter.accept(resultObject, json.getBoolean(name)); } else if (Enum.class.isAssignableFrom(type)) { String value = json.getString(name); Object enumValue = Enum.valueOf(type, value); setter.accept(resultObject, enumValue); } } }); } static class MetaData { Class type; String name; BiConsumer setter; Function getter; Predicate gate; MetaData(Class type, String name, BiConsumer setter, Function getter, Predicate gate) { this.type = type; this.name = name; this.setter = setter; this.getter = getter; this.gate = gate; } @Override public String toString() { return "MetaData{" + "type=" + type + ", name='" + name + '\'' + '}'; } } public interface MetadataListener { void metaItem(Class type, String name, BiConsumer setter, Function getter, Predicate gate); } }