
com.arakelian.jdbc.store.strategy.HasTimestampJdbcStrategy 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 com.arakelian.jdbc.store.strategy;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import org.apache.commons.lang3.StringUtils;
import com.arakelian.core.utils.DateUtils;
import com.arakelian.jdbc.conn.ConnectionFactory;
import com.arakelian.jdbc.utils.DatabaseUtils;
import com.arakelian.json.ImmutableJsonFilterOptions;
import com.arakelian.json.JsonFilter;
import com.arakelian.json.JsonFilterCallback;
import com.arakelian.json.JsonWriter;
import com.arakelian.store.StoreException;
import com.arakelian.store.feature.HasId;
import com.arakelian.store.feature.HasMutableTimestamp;
import com.arakelian.store.feature.HasTimestamp;
import com.arakelian.store.json.StoreObjectMapper;
import com.google.common.base.Preconditions;
public class HasTimestampJdbcStrategy extends AbstractJdbcStrategy {
/**
* We do not want to store the created and updated fields inside the JSON document. They're
* maintained outside in separate fields.
*/
private static final ImmutableJsonFilterOptions EXCLUDE_CREATED_UPDATED = //
ImmutableJsonFilterOptions.builder() //
.addExcludes("created", "updated") //
.build();
public static String addTimestampsToJson(final String json, final String created, final String updated)
throws IOException {
// we add the created and updated fields into the JSON itself, so that our bean, which may
// use the immutable builder pattern, can have access to them during construction.
final JsonFilterCallback addCreatedUpdatedFields = new JsonFilterCallback() {
@Override
public void beforeEndObject(final JsonFilter filter) throws IOException {
if (filter.getDepth() == 1) {
final JsonWriter writer = filter.getWriter();
writer.writeKeyValue("created", created);
writer.writeKeyValue("updated", updated);
}
}
};
// remove existing fields and add correct values
return JsonFilter.filter(
StringUtils.defaultIfEmpty(json, "{}"),
ImmutableJsonFilterOptions.builder() //
.addExcludes("created", "updated") //
.callback(addCreatedUpdatedFields) //
.build());
}
public static boolean supports(final Class> clazz) {
return HasTimestamp.class.isAssignableFrom(clazz);
}
public HasTimestampJdbcStrategy(final StoreObjectMapper serializer) {
super(serializer);
}
@Override
public T get(final ResultSet rs) throws SQLException {
final String json = StringUtils.defaultIfEmpty(rs.getString(1), "{}");
final String created = rs.getString(2);
final String updated = rs.getString(3);
try {
final String timestamped = addTimestampsToJson(json, created, updated);
final T bean = mapper.readValue(timestamped);
return bean;
} catch (final IOException e) {
throw new SQLException("Unable to process row", e);
}
}
@Override
public String getInsertSql(final String table) {
// we do not use REPLACE INTO syntax because that is technically a DELETE followed by INSERT
// if the row already exists; this syntax updates a single column
Preconditions.checkArgument(!StringUtils.isEmpty(table), "table must be non-empty");
return "insert into " + table
+ "(id,document,created,updated) values(?,?,?,?) on duplicate key update document=?,updated=?";
}
@Override
public String getSelectSql(final String table) {
Preconditions.checkArgument(!StringUtils.isEmpty(table), "table must be non-empty");
return "select document, created, updated from " + table;
}
@Override
public final void put(final ConnectionFactory connectionFactory, final String insertSql, final T value)
throws SQLException {
// convert value to JSON
final String json = toString(value);
// fetch timestamps
final HasTimestamp hasTimestamp = (HasTimestamp) value;
final ZonedDateTime c = hasTimestamp.getCreated();
final ZonedDateTime u = hasTimestamp.getUpdated();
// assign timestamps if not specified
final boolean mutable = value instanceof HasMutableTimestamp;
final ZonedDateTime created;
final ZonedDateTime updated;
if (mutable) {
created = c != null ? c : DateUtils.nowWithZoneUtc();
updated = u != null ? u : created;
} else {
created = Preconditions.checkNotNull(c, "Create date not specified: %s", value);
updated = Preconditions.checkNotNull(u, "Update date not specified: %s", value);
}
// store values
final String createdString = DateUtils.toStringIsoFormat(created);
final String updatedString = DateUtils.toStringIsoFormat(updated);
final int rowsAffected = DatabaseUtils.executeUpdate(
connectionFactory, //
insertSql,
value.getId(),
json,
createdString,
updatedString,
json,
updatedString);
if (mutable) {
// from MYSQL manual: "With ON DUPLICATE KEY UPDATE, the affected-rows value per row is
// 1 if the row is inserted as a new row and 2 if an existing row is updated."
final HasMutableTimestamp hasMutableTimestamp = (HasMutableTimestamp) value;
switch (rowsAffected) {
case 1:
// row inserted
if (c == null) {
hasMutableTimestamp.setCreated(created);
}
if (u == null) {
hasMutableTimestamp.setUpdated(updated);
}
break;
case 2:
// row updated
if (u == null) {
hasMutableTimestamp.setUpdated(updated);
}
break;
}
}
}
@Override
protected String toString(final T value) {
try {
// we don't store the created and updated fields inside the JSON itself; those
// fields are stored outside because our ON DUPLICATE KEY UPDATE logic changes the
// updated date field, not the json itself.
final String raw = super.toString(value);
return JsonFilter.filter(raw, EXCLUDE_CREATED_UPDATED);
} catch (final IOException e) {
throw new StoreException(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy