blob: c5d3db858dc235101dcfc49bf7b2a9a7559a97f3 [file] [log] [blame]
/*
* 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.camel.component.salesforce.api.dto.composite;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import org.apache.camel.component.salesforce.api.dto.AbstractDescribedSObjectBase;
import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
import org.apache.camel.component.salesforce.api.dto.RestError;
import org.apache.camel.component.salesforce.api.dto.SObjectDescription;
import org.apache.camel.util.ObjectHelper;
import static java.util.Objects.requireNonNull;
/**
* Represents one node in the SObject tree request. SObject trees
* ({@link SObjectTree}) are composed from instances of {@link SObjectNode}s.
* Each {@link SObjectNode} contains the SObject ({@link AbstractSObjectBase})
* and any child records linked to it. SObjects at root level are added to
* {@link SObjectTree} using {@link SObjectTree#addObject(AbstractSObjectBase)},
* then you can add child records on the {@link SObjectNode} returned by using
* {@link #addChild(AbstractDescribedSObjectBase)},
* {@link #addChildren(AbstractDescribedSObjectBase, AbstractDescribedSObjectBase...)}
* or {@link #addChild(String, AbstractSObjectBase)} and
* {@link #addChildren(String, AbstractSObjectBase, AbstractSObjectBase...)}.
* <p/>
* Upon submission to the Salesforce Composite API the {@link SObjectTree} and
* the {@link SObjectNode}s in it might contain errors that you need to fetch
* using {@link #getErrors()} method.
*
* @see SObjectTree
* @see RestError
*/
@XStreamAlias("records")
@XStreamConverter(SObjectNodeXStreamConverter.class)
public final class SObjectNode implements Serializable {
private static final String CHILD_PARAM = "child";
private static final String SOBJECT_TYPE_PARAM = "type";
private static final long serialVersionUID = 1L;
@JsonUnwrapped
final AbstractSObjectBase object;
final Map<String, List<SObjectNode>> records = new HashMap<>();
private List<RestError> errors;
@XStreamOmitField
private final ReferenceGenerator referenceGenerator;
SObjectNode(final SObjectTree tree, final AbstractSObjectBase object) {
this(tree.referenceGenerator, typeOf(object), object);
}
private SObjectNode(final ReferenceGenerator referenceGenerator, final String type, final AbstractSObjectBase object) {
this.referenceGenerator = requireNonNull(referenceGenerator, "ReferenceGenerator cannot be null");
this.object = requireNonNull(object, "Root SObject cannot be null");
object.getAttributes().setReferenceId(referenceGenerator.nextReferenceFor(object));
}
static String pluralOf(final AbstractDescribedSObjectBase object) {
final SObjectDescription description = object.description();
return description.getLabelPlural();
}
static String typeOf(final AbstractDescribedSObjectBase object) {
final SObjectDescription description = object.description();
return description.getName();
}
static String typeOf(final AbstractSObjectBase object) {
return object.getClass().getSimpleName();
}
/**
* Add a described child with the metadata needed already present within it
* to the this node.
*
* @param child to add
* @return the newly created node, used in builder fashion to add more child
* objects to it (on the next level)
*/
public SObjectNode addChild(final AbstractDescribedSObjectBase child) {
ObjectHelper.notNull(child, CHILD_PARAM);
return addChild(pluralOf(child), child);
}
/**
* Add a child that does not contain the required metadata to the this node.
* You need to specify the plural form of the child (e.g. `Account` its
* `Accounts`).
*
* @param labelPlural plural form
* @param child to add
* @return the newly created node, used in builder fashion to add more child
* objects to it (on the next level)
*/
public SObjectNode addChild(final String labelPlural, final AbstractSObjectBase child) {
ObjectHelper.notNull(labelPlural, "labelPlural");
ObjectHelper.notNull(child, CHILD_PARAM);
final SObjectNode node = new SObjectNode(referenceGenerator, typeOf(child), child);
return addChild(labelPlural, node);
}
/**
* Add multiple described children with the metadata needed already present
* within them to the this node..
*
* @param first first child to add
* @param others any other children to add
*/
public void addChildren(final AbstractDescribedSObjectBase first, final AbstractDescribedSObjectBase... others) {
ObjectHelper.notNull(first, "first");
ObjectHelper.notNull(others, "others");
addChild(pluralOf(first), first);
Arrays.stream(others).forEach(this::addChild);
}
/**
* Add a child that does not contain the required metadata to the this node.
* You need to specify the plural form of the child (e.g. `Account` its
* `Accounts`).
*
* @param labelPlural plural form
* @param first first child to add
* @param others any other children to add
*/
public void addChildren(final String labelPlural, final AbstractSObjectBase first, final AbstractSObjectBase... others) {
ObjectHelper.notNull(labelPlural, "labelPlural");
ObjectHelper.notNull(first, "first");
ObjectHelper.notNull(others, "others");
addChild(labelPlural, first);
Arrays.stream(others).forEach(c -> addChild(labelPlural, c));
}
/**
* Returns all children of this node (one level deep).
*
* @return children of this node
*/
@JsonIgnore
public Stream<SObjectNode> getChildNodes() {
return records.values().stream().flatMap(List::stream);
}
/**
* Returns all children of this node (one level deep) of certain type (in
* plural form).
*
* @param type type of child requested in plural form (e.g for `Account` is
* `Accounts`)
* @return children of this node of specified type
*/
public Stream<SObjectNode> getChildNodesOfType(final String type) {
ObjectHelper.notNull(type, SOBJECT_TYPE_PARAM);
return records.getOrDefault(type, Collections.emptyList()).stream();
}
/**
* Returns child SObjects of this node (one level deep).
*
* @return child SObjects of this node
*/
@JsonIgnore
public Stream<AbstractSObjectBase> getChildren() {
return records.values().stream().flatMap(List::stream).map(SObjectNode::getObject);
}
/**
* Returns child SObjects of this node (one level deep) of certain type (in
* plural form)
*
* @param type type of child requested in plural form (e.g for `Account` is
* `Accounts`)
* @return child SObjects of this node
*/
public Stream<AbstractSObjectBase> getChildrenOfType(final String type) {
ObjectHelper.notNull(type, SOBJECT_TYPE_PARAM);
return records.getOrDefault(type, Collections.emptyList()).stream().map(SObjectNode::getObject);
}
/**
* Errors reported against this this node received in response to the
* SObject tree being submitted.
*
* @return errors for this node
*/
@JsonIgnore
public List<RestError> getErrors() {
return Optional.ofNullable(errors).orElse(Collections.emptyList());
}
/**
* SObject at this node.
*
* @return SObject
*/
@JsonIgnore
public AbstractSObjectBase getObject() {
return object;
}
/**
* Are there any errors resulted from the submission on this node?
*
* @return true if there are errors
*/
public boolean hasErrors() {
return errors != null && !errors.isEmpty();
}
/**
* Size of the branch beginning with this node (number of SObjects in it).
*
* @return number of objects within this branch
*/
public int size() {
return 1 + records.values().stream().flatMapToInt(r -> r.stream().mapToInt(SObjectNode::size)).sum();
}
@Override
public String toString() {
return "Node<" + getObjectType() + ">";
}
SObjectNode addChild(final String labelPlural, final SObjectNode node) {
List<SObjectNode> children = records.get(labelPlural);
if (children == null) {
children = new ArrayList<>();
records.put(labelPlural, children);
}
children.add(node);
return node;
}
@JsonAnyGetter
Map<String, Map<String, List<SObjectNode>>> children() {
return records.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> Collections.singletonMap("records", e.getValue())));
}
@JsonIgnore
String getObjectType() {
return object.getAttributes().getType();
}
Stream<Class> objectTypes() {
return Stream.concat(Stream.of((Class)object.getClass()), getChildNodes().flatMap(SObjectNode::objectTypes));
}
void setErrors(final List<RestError> errors) {
this.errors = errors;
}
}