Commit 969fb9e8 authored by madvirus's avatar madvirus
Browse files

Initial Commit

parents
package com.myshop.common.jpa;
import com.myshop.common.model.Email;
import com.myshop.common.model.EmailSet;
import javax.persistence.AttributeConverter;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
public class EmailSetConverter implements AttributeConverter<EmailSet, String> {
@Override
public String convertToDatabaseColumn(EmailSet attribute) {
if (attribute == null) return null;
return attribute.getEmails().stream()
.map(email -> email.getAddress())
.collect(Collectors.joining(","));
}
@Override
public EmailSet convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
String[] emails = dbData.split(",");
Set<Email> emailSet = Arrays.stream(emails)
.map(value -> new Email(value))
.collect(toSet());
return new EmailSet(emailSet);
}
}
package com.myshop.common.jpa;
import com.myshop.common.model.Money;
import javax.persistence.AttributeConverter;
public class MoneyConverter implements AttributeConverter<Money, Integer> {
@Override
public Integer convertToDatabaseColumn(Money money) {
return money == null ? null : money.getValue();
}
@Override
public Money convertToEntityAttribute(Integer value) {
return value == null ? null : new Money(value);
}
}
package com.myshop.common.jpa;
import org.springframework.data.domain.Sort;
public class Rangeable {
private int start;
private int limit;
private Sort sort;
public Rangeable(int start, int limit, Sort sort) {
this.start = start;
this.limit = limit;
this.sort = sort;
}
public int getStart() {
return start;
}
public int getLimit() {
return limit;
}
public Sort getSort() {
return sort;
}
public static Rangeable of(int start, int limit) {
return new Rangeable(start, limit, Sort.unsorted());
}
}
\ No newline at end of file
package com.myshop.common.jpa;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
public interface RangeableExecutor<T> {
List<T> getRange(Specification<T> spec, Rangeable rangeable);
}
\ No newline at end of file
package com.myshop.common.jpa;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
import java.io.Serializable;
@NoRepositoryBean
public interface RangeableRepository<T, ID extends Serializable>
extends Repository<T, ID>, RangeableExecutor<T> {
}
\ No newline at end of file
package com.myshop.common.jpa;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.io.Serializable;
import java.util.List;
public class RangeableRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID>
implements RangeableRepository<T, ID> {
public RangeableRepositoryImpl(
JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
}
@Override
public List<T> getRange(Specification<T> spec, Rangeable rangeable) {
TypedQuery<T> query = getQuery(
spec, getDomainClass(), rangeable.getSort());
query.setFirstResult(rangeable.getStart());
query.setMaxResults(rangeable.getLimit());
return query.getResultList();
}
}
\ No newline at end of file
package com.myshop.common.jpa;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public class SpecBuilder {
public static <T> Builder<T> builder(Class<T> type) {
return new Builder<T>();
}
public static class Builder<T> {
private List<Specification<T>> specs = new ArrayList<>();
public Builder<T> and(Specification<T> spec) {
specs.add(spec);
return this;
}
public Builder<T> ifHasText(String str,
Function<String, Specification<T>> specSupplier) {
if (StringUtils.hasText(str)) {
specs.add(specSupplier.apply(str));
}
return this;
}
public Builder<T> ifTrue(Boolean cond,
Supplier<Specification<T>> specSupplier) {
if (cond != null && cond.booleanValue()) {
specs.add(specSupplier.get());
}
return this;
}
public Specification<T> toSpec() {
Specification<T> spec = Specification.where(null);
for (Specification<T> s : specs) {
spec = spec.and(s);
}
return spec;
}
}
}
package com.myshop.common.model;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class Address {
@Column(name = "zip_code")
private String zipCode;
@Column(name = "address1")
private String address1;
@Column(name = "address2")
private String address2;
public Address() {
}
public Address(String zipCode, String address1, String address2) {
this.zipCode = zipCode;
this.address1 = address1;
this.address2 = address2;
}
public String getZipCode() {
return zipCode;
}
public String getAddress1() {
return address1;
}
public String getAddress2() {
return address2;
}
}
package com.myshop.common.model;
import java.util.Objects;
public class Email {
private String address;
public Email(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Email email = (Email) o;
return Objects.equals(address, email.address);
}
@Override
public int hashCode() {
return Objects.hash(address);
}
public static Email of(String address) {
return new Email(address);
}
}
package com.myshop.common.model;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class EmailSet {
private Set<Email> emails = new HashSet<>();
public EmailSet(Set<Email> emails) {
this.emails.addAll(emails);
}
public Set<Email> getEmails() {
return Collections.unmodifiableSet(emails);
}
}
package com.myshop.common.model;
import java.util.Objects;
public class Money {
private int value;
public Money(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public Money multiply(int multiplier) {
return new Money(value * multiplier);
}
@Override
public String toString() {
return Integer.toString(value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return value == money.value;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
package com.myshop.common.ui;
public class Pagination {
private int current;
private int beginPage;
private int endPage;
private int numberOfPage;
private int totalPages;
public Pagination(int current, int totalPages, int numberOfPage) {
this.current = current;
this.totalPages = totalPages;
this.numberOfPage = numberOfPage;
int pageMod = current % 5;
beginPage = pageMod == 0 ? current - 5 + 1 : current - pageMod + 1;
endPage = beginPage + 5 - 1;
if (endPage > totalPages) {
endPage = totalPages;
}
}
public int getCurrent() {
return current;
}
public int getTotalPages() {
return totalPages;
}
public int getBeginPage() {
return beginPage;
}
public int getEndPage() {
return endPage;
}
public int getNumberOfPage() {
return numberOfPage;
}
public boolean isHasPrevious() {
return beginPage > 1;
}
public int getPreviousBeginPage() {
return beginPage - numberOfPage;
}
public int getNextBeginPage() {
return beginPage + numberOfPage;
}
public boolean isHasNext() {
return endPage < totalPages;
}
public int[] getPageNos() {
int[] result = new int[endPage - beginPage + 1];
for (int i = beginPage ; i <= endPage ; i++) {
result[i - beginPage] = i;
}
return result;
}
}
package com.myshop.eventstore.api;
public class EventEntry {
private Long id;
private String type;
private String contentType;
private String payload;
private long timestamp;
public EventEntry(String type, String contentType, String payload) {
this.type = type;
this.contentType = contentType;
this.payload = payload;
this.timestamp = System.currentTimeMillis();
}
public EventEntry(Long id, String type, String contentType, String payload,
long timestamp) {
this.id = id;
this.type = type;
this.contentType = contentType;
this.payload = payload;
this.timestamp = timestamp;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getContentType() {
return contentType;
}
public String getPayload() {
return payload;
}
public long getTimestamp() {
return timestamp;
}
}
package com.myshop.eventstore.api;
import java.util.List;
public interface EventStore {
void save(Object event);
List<EventEntry> get(long offset, long limit);
}
package com.myshop.eventstore.api;
public class PayloadConvertException extends RuntimeException {
public PayloadConvertException(Exception e) {
super(e);
}
}
package com.myshop.eventstore.infra;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.myshop.eventstore.api.EventEntry;
import com.myshop.eventstore.api.EventStore;
import com.myshop.eventstore.api.PayloadConvertException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
import java.util.List;
@Component
public class JdbcEventStore implements EventStore {
private ObjectMapper objectMapper;
private JdbcTemplate jdbcTemplate;
public JdbcEventStore(ObjectMapper objectMapper, JdbcTemplate jdbcTemplate) {
this.objectMapper = objectMapper;
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void save(Object event) {
EventEntry entry = new EventEntry(event.getClass().getName(),
"application/json", toJson(event));
jdbcTemplate.update(
"insert into evententry " +
"(type, content_type, payload, timestamp) " +
"values (?, ?, ?, ?)",
ps -> {
ps.setString(1, entry.getType());
ps.setString(2, entry.getContentType());
ps.setString(3, entry.getPayload());
ps.setTimestamp(4, new Timestamp(entry.getTimestamp()));
});
}
private String toJson(Object event) {
try {
return objectMapper.writeValueAsString(event);
} catch (JsonProcessingException e) {
throw new PayloadConvertException(e);
}
}
@Override
public List<EventEntry> get(long offset, long limit) {
return jdbcTemplate.query(
"select * from evententry order by id asc limit ?, ?",
ps -> {
ps.setLong(1, offset);
ps.setLong(2, limit);
},
(rs, rowNum) -> {
return new EventEntry(
rs.getLong("id"),
rs.getString("type"),
rs.getString("content_type"),
rs.getString("payload"),
rs.getTimestamp("timestamp").getTime());
});
}
}
\ No newline at end of file
package com.myshop.eventstore.ui;
import com.myshop.eventstore.api.EventEntry;
import com.myshop.eventstore.api.EventStore;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class EventApi {
private EventStore eventStore;
public EventApi(EventStore eventStore) {
this.eventStore = eventStore;
}
@RequestMapping(value = "/api/events", method = RequestMethod.GET)
public List<EventEntry> list(
@RequestParam("offset") Long offset,
@RequestParam("limit") Long limit) {
return eventStore.get(offset, limit);
}
}
package com.myshop.integration;
import com.myshop.eventstore.api.EventEntry;
import com.myshop.eventstore.api.EventStore;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class EventForwarder {
private static final int DEFAULT_LIMIT_SIZE = 100;
private EventStore eventStore;
private OffsetStore offsetStore;
private EventSender eventSender;
private int limitSize = DEFAULT_LIMIT_SIZE;
public EventForwarder(EventStore eventStore,
OffsetStore offsetStore,
EventSender eventSender) {
this.eventStore = eventStore;
this.offsetStore = offsetStore;
this.eventSender = eventSender;
}
@Scheduled(initialDelay = 1000L, fixedDelay = 1000L)
public void getAndSend() {
long nextOffset = getNextOffset();
List<EventEntry> events = eventStore.get(nextOffset, limitSize);
if (!events.isEmpty()) {
int processedCount = sendEvent(events);
if (processedCount > 0) {
saveNextOffset(nextOffset + processedCount);
}
}
}
private long getNextOffset() {
return offsetStore.get();
}
private int sendEvent(List<EventEntry> events) {
int processedCount = 0;
try {
for (EventEntry entry : events) {
eventSender.send(entry);
processedCount++;
}
} catch(Exception ex) {
// 로깅 처리
}
return processedCount;
}
private void saveNextOffset(long nextOffset) {
offsetStore.update(nextOffset);
}
}
package com.myshop.integration;
import com.myshop.eventstore.api.EventEntry;
public interface EventSender {
void send(EventEntry event);
}
package com.myshop.integration;
public interface OffsetStore {
long get();
void update(long nextOffset);
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment