I have a builder class which I am using in one of my project.
- Let's say I have
metricA
as builder based on below class. - I need to make a new builder
metricB
based onmetricA
by cloningmetricA
so thatmetricB
contains all the values which were already there inmetricA
.
In the constructor of MetricHolder
I am initializing some fields (which are not set directly) basis on fields that have been set already.
clientTypeOrPayId
- I am initializing this field. IfpayId
is present, then I will set this value or I will setclientType
.clientKey
- I am initializing this field as well in the same constructor.- And most importantly, I am putting few mandatory fields in the
clientPayload
map. I am not sure what is the right way to do that. But I need to addis_clientid
andis_deviceid
into the map. (In general I am adding few more fields). - And then in the last of the constructor, I am calculating latency difference and sending it to some other system.
Below is my class:
public final class MetricHolder {
private final String clientId;
private final String deviceId;
private final String payId;
private final String clientType;
private final String clientTypeOrPayId;
private final Schema schema;
private final String schemaId;
private final String clientKey;
private final Map<String, String> clientPayload;
private final Record record;
private final long clientCreateTimestamp;
private final long clientSentTimestamp;
private MetricHolder(Builder builder) {
this.payId = builder.payId;
this.siteId = builder.siteId;
this.clientType = builder.clientType;
this.clientId = builder.clientId;
this.deviceId = builder.deviceId;
this.schema = builder.schema;
this.schemaId = builder.schemaId;
// populating all the required fields in the map and make it immutable
// not sure whether this is right?
builder.clientPayload.put("is_clientid", (clientId == null) ? "false" : "true");
builder.clientPayload.put("is_deviceid", (clientId == null) ? "true" : "false");
this.clientPayload = Collections.unmodifiableMap(builder.clientPayload);
this.clientTypeOrPayId = Strings.isNullOrEmpty(payId) ? clientType : payId;
this.record = builder.record;
this.clientKey = "process:" + System.currentTimeMillis() + ":"
+ ((clientId == null) ? deviceId : clientId);
this.clientCreateTimestamp = builder.clientCreateTimestamp;
this.clientSentTimestamp = builder.clientSentTimestamp;
// this will be called twice while cloning
// what is the right way to do this then?
SendData.getInstance().insert(clientTypeOrPayId,
System.currentTimeMillis() - clientCreateTimestamp);
SendData.getInstance().insert(clientTypeOrPayId,
System.currentTimeMillis() - clientSentTimestamp);
}
public static class Builder {
private final Record record;
private Schema schema;
private String schemaId;
private String clientId;
private String deviceId;
private String payId;
private String clientType;
private Map<String, String> clientPayload;
private long clientCreateTimestamp;
private long clientSentTimestamp;
// this is for cloning
public Builder(MetricHolder packet) {
this.record = packet.record;
this.schema = packet.schema;
this.schemaId = packet.schemaId;
this.clientId = packet.clientId;
this.deviceId = packet.deviceId;
this.payId = packet.payId;
this.clientType = packet.clientType;
// make a new map and check whether mandatory fields are present already or not
// and if they are present don't add it again.
this.clientPayload = new HashMap<>();
for (Map.Entry<String, String> entry : packet.clientPayload.entrySet()) {
if (!("is_clientid".equals(entry.getKey()) || "is_deviceid".equals(entry.getKey())) {
this.clientPayload.put(entry.getKey(), entry.getValue());
}
}
this.clientCreateTimestamp = packet.clientCreateTimestamp;
this.clientSentTimestamp = packet.clientSentTimestamp;
}
public Builder(Record record) {
this.record = record;
}
public Builder setSchema(Schema schema) {
this.schema = schema;
return this;
}
public Builder setSchemaId(String schemaId) {
this.schemaId = schemaId;
return this;
}
public Builder setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public Builder setDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
public Builder setPayId(String payId) {
this.payId = payId;
return this;
}
public Builder setClientType(String clientType) {
this.clientType = clientType;
return this;
}
public Builder setClientPayload(Map<String, String> payload) {
this.clientPayload = payload;
return this;
}
public Builder setClientCreateTimestamp(long clientCreateTimestamp) {
this.clientCreateTimestamp = clientCreateTimestamp;
return this;
}
public Builder setClientSentTimestamp(long clientSentTimestamp) {
this.clientSentTimestamp = clientSentTimestamp;
return this;
}
public MetricHolder build() {
return new MetricHolder(this);
}
}
// getters
}
Question:-
Below is how I make metricA
builder object:
MetricHolder metricA = new MetricHolder.Builder(record).setClientId("123456").setDeviceId("abcdefhg")
. setPayId("98765").setClientPayload(payloadMapHolder).setClientCreateTimestamp(createTimestamp)
.setClientSentTimestamp(sentTimestamp).build();
Now this is how I clone the metricA
object later on in the code when I get all other fields as shown below:
MetricHolder metricB = new MetricHolder.Builder(metricA).setSchema(schema).setSchemaId("345").build();
I see two problem now:
- First of all, my
SendData.getInstance()
line in theMetricHolder
constructor will be called twice. First is when I makemetricA
and second when I makemetricB
by cloningmetricA
. But I just want to call it only once when I try to createmetricA
builder object? How can I make this possible? - Second is, the way I am populating
clientPayload
map with two mandatory fields in theMetricHolder
constructor doesn't look right to me. Is there any other better way to do the same thing?
I guess the whole problem is happening because the way I am cloning metricA
to make a metricB
builder object? What is the best way to do this? I want to achieve above two things but in a right way.