forked from Qortal/qortal
Browse Source
(Also minor fix for orphan.java). Note use of afterUnmarshal() in TransactionData-subclass to replace trash code in Transaction-subclass constructor. See UpdateGroupTransactionData.afterUnmarshal() compared to RegisterNameTransaction constructor.split-DB
catbref
6 years ago
22 changed files with 1557 additions and 10 deletions
@ -0,0 +1,216 @@
|
||||
package org.qora.api.resource; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.Parameter; |
||||
import io.swagger.v3.oas.annotations.media.ArraySchema; |
||||
import io.swagger.v3.oas.annotations.media.Content; |
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody; |
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import java.util.List; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.ws.rs.GET; |
||||
import javax.ws.rs.POST; |
||||
import javax.ws.rs.Path; |
||||
import javax.ws.rs.PathParam; |
||||
import javax.ws.rs.Produces; |
||||
import javax.ws.rs.QueryParam; |
||||
import javax.ws.rs.core.Context; |
||||
import javax.ws.rs.core.MediaType; |
||||
|
||||
import org.qora.api.ApiError; |
||||
import org.qora.api.ApiErrors; |
||||
import org.qora.api.ApiExceptionFactory; |
||||
import org.qora.crypto.Crypto; |
||||
import org.qora.data.group.GroupData; |
||||
import org.qora.data.transaction.CreateGroupTransactionData; |
||||
import org.qora.data.transaction.UpdateGroupTransactionData; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
import org.qora.repository.RepositoryManager; |
||||
import org.qora.transaction.Transaction; |
||||
import org.qora.transaction.Transaction.ValidationResult; |
||||
import org.qora.transform.TransformationException; |
||||
import org.qora.transform.transaction.CreateGroupTransactionTransformer; |
||||
import org.qora.transform.transaction.UpdateGroupTransactionTransformer; |
||||
import org.qora.utils.Base58; |
||||
|
||||
@Path("/groups") |
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) |
||||
@Tag(name = "Groups") |
||||
public class GroupsResource { |
||||
|
||||
@Context |
||||
HttpServletRequest request; |
||||
|
||||
@GET |
||||
@Operation( |
||||
summary = "List all groups", |
||||
responses = { |
||||
@ApiResponse( |
||||
description = "group info", |
||||
content = @Content( |
||||
mediaType = MediaType.APPLICATION_JSON, |
||||
array = @ArraySchema(schema = @Schema(implementation = GroupData.class)) |
||||
) |
||||
) |
||||
} |
||||
) |
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE}) |
||||
public List<GroupData> getAllGroups(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
List<GroupData> groups = repository.getGroupRepository().getAllGroups(); |
||||
|
||||
// Pagination would take effect here (or as part of the repository access)
|
||||
int fromIndex = Integer.min(offset, groups.size()); |
||||
int toIndex = limit == 0 ? groups.size() : Integer.min(fromIndex + limit, groups.size()); |
||||
groups = groups.subList(fromIndex, toIndex); |
||||
|
||||
return groups; |
||||
} catch (DataException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); |
||||
} |
||||
} |
||||
|
||||
@GET |
||||
@Path("/address/{address}") |
||||
@Operation( |
||||
summary = "List all groups owned by address", |
||||
responses = { |
||||
@ApiResponse( |
||||
description = "group info", |
||||
content = @Content( |
||||
mediaType = MediaType.APPLICATION_JSON, |
||||
array = @ArraySchema(schema = @Schema(implementation = GroupData.class)) |
||||
) |
||||
) |
||||
} |
||||
) |
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) |
||||
public List<GroupData> getGroupsByAddress(@PathParam("address") String address) { |
||||
if (!Crypto.isValidAddress(address)) |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); |
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
List<GroupData> groups = repository.getGroupRepository().getGroupsByOwner(address); |
||||
|
||||
return groups; |
||||
} catch (DataException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); |
||||
} |
||||
} |
||||
|
||||
@GET |
||||
@Path("/{groupname}") |
||||
@Operation( |
||||
summary = "Info on group", |
||||
responses = { |
||||
@ApiResponse( |
||||
description = "group info", |
||||
content = @Content( |
||||
mediaType = MediaType.APPLICATION_JSON, |
||||
schema = @Schema(implementation = GroupData.class) |
||||
) |
||||
) |
||||
} |
||||
) |
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE}) |
||||
public GroupData getGroup(@PathParam("groupname") String groupName) { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
return repository.getGroupRepository().fromGroupName(groupName); |
||||
} catch (DataException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@POST |
||||
@Path("/create") |
||||
@Operation( |
||||
summary = "Build raw, unsigned, CREATE_GROUP transaction", |
||||
requestBody = @RequestBody( |
||||
required = true, |
||||
content = @Content( |
||||
mediaType = MediaType.APPLICATION_JSON, |
||||
schema = @Schema( |
||||
implementation = CreateGroupTransactionData.class |
||||
) |
||||
) |
||||
), |
||||
responses = { |
||||
@ApiResponse( |
||||
description = "raw, unsigned, CREATE_GROUP transaction encoded in Base58", |
||||
content = @Content( |
||||
mediaType = MediaType.TEXT_PLAIN, |
||||
schema = @Schema( |
||||
type = "string" |
||||
) |
||||
) |
||||
) |
||||
} |
||||
) |
||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) |
||||
public String createGroup(CreateGroupTransactionData transactionData) { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
Transaction transaction = Transaction.fromData(repository, transactionData); |
||||
|
||||
ValidationResult result = transaction.isValidUnconfirmed(); |
||||
if (result != ValidationResult.OK) |
||||
throw TransactionsResource.createTransactionInvalidException(request, result); |
||||
|
||||
byte[] bytes = CreateGroupTransactionTransformer.toBytes(transactionData); |
||||
return Base58.encode(bytes); |
||||
} catch (TransformationException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); |
||||
} catch (DataException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); |
||||
} |
||||
} |
||||
|
||||
@POST |
||||
@Path("/update") |
||||
@Operation( |
||||
summary = "Build raw, unsigned, UPDATE_GROUP transaction", |
||||
requestBody = @RequestBody( |
||||
required = true, |
||||
content = @Content( |
||||
mediaType = MediaType.APPLICATION_JSON, |
||||
schema = @Schema( |
||||
implementation = UpdateGroupTransactionData.class |
||||
) |
||||
) |
||||
), |
||||
responses = { |
||||
@ApiResponse( |
||||
description = "raw, unsigned, UPDATE_GROUP transaction encoded in Base58", |
||||
content = @Content( |
||||
mediaType = MediaType.TEXT_PLAIN, |
||||
schema = @Schema( |
||||
type = "string" |
||||
) |
||||
) |
||||
) |
||||
} |
||||
) |
||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) |
||||
public String updateGroup(UpdateGroupTransactionData transactionData) { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
Transaction transaction = Transaction.fromData(repository, transactionData); |
||||
|
||||
ValidationResult result = transaction.isValidUnconfirmed(); |
||||
if (result != ValidationResult.OK) |
||||
throw TransactionsResource.createTransactionInvalidException(request, result); |
||||
|
||||
byte[] bytes = UpdateGroupTransactionTransformer.toBytes(transactionData); |
||||
return Base58.encode(bytes); |
||||
} catch (TransformationException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); |
||||
} catch (DataException e) { |
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,89 @@
|
||||
package org.qora.data.group; |
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType; |
||||
import javax.xml.bind.annotation.XmlAccessorType; |
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD) |
||||
public class GroupData { |
||||
|
||||
// Properties
|
||||
private String owner; |
||||
private String groupName; |
||||
private String description; |
||||
private long created; |
||||
private Long updated; |
||||
private boolean isOpen; |
||||
private byte[] reference; |
||||
|
||||
// Constructors
|
||||
|
||||
// necessary for JAX-RS serialization
|
||||
protected GroupData() { |
||||
} |
||||
|
||||
public GroupData(String owner, String name, String description, long created, Long updated, boolean isOpen, byte[] reference) { |
||||
this.owner = owner; |
||||
this.groupName = name; |
||||
this.description = description; |
||||
this.created = created; |
||||
this.updated = updated; |
||||
this.isOpen = isOpen; |
||||
this.reference = reference; |
||||
} |
||||
|
||||
public GroupData(String owner, String name, String description, long created, boolean isOpen, byte[] reference) { |
||||
this(owner, name, description, created, null, isOpen, reference); |
||||
} |
||||
|
||||
// Getters / setters
|
||||
|
||||
public String getOwner() { |
||||
return this.owner; |
||||
} |
||||
|
||||
public void setOwner(String owner) { |
||||
this.owner = owner; |
||||
} |
||||
|
||||
public String getGroupName() { |
||||
return this.groupName; |
||||
} |
||||
|
||||
public String getDescription() { |
||||
return this.description; |
||||
} |
||||
|
||||
public void setDescription(String description) { |
||||
this.description = description; |
||||
} |
||||
|
||||
public long getCreated() { |
||||
return this.created; |
||||
} |
||||
|
||||
public Long getUpdated() { |
||||
return this.updated; |
||||
} |
||||
|
||||
public void setUpdated(Long updated) { |
||||
this.updated = updated; |
||||
} |
||||
|
||||
public byte[] getReference() { |
||||
return this.reference; |
||||
} |
||||
|
||||
public void setReference(byte[] reference) { |
||||
this.reference = reference; |
||||
} |
||||
|
||||
public boolean getIsOpen() { |
||||
return this.isOpen; |
||||
} |
||||
|
||||
public void setIsOpen(boolean isOpen) { |
||||
this.isOpen = isOpen; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,80 @@
|
||||
package org.qora.data.transaction; |
||||
|
||||
import java.math.BigDecimal; |
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType; |
||||
import javax.xml.bind.annotation.XmlAccessorType; |
||||
import javax.xml.bind.annotation.XmlElement; |
||||
|
||||
import org.qora.transaction.Transaction.TransactionType; |
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD) |
||||
@Schema(allOf = { TransactionData.class }) |
||||
public class CreateGroupTransactionData extends TransactionData { |
||||
|
||||
@Schema(description = "group owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v") |
||||
private String owner; |
||||
@Schema(description = "group name", example = "miner-group") |
||||
private String groupName; |
||||
@Schema(description = "short description of group", example = "this group is for block miners") |
||||
private String description; |
||||
@Schema(description = "whether anyone can join group (open) or group is invite-only (closed)", example = "true") |
||||
private boolean isOpen; |
||||
|
||||
// Constructors
|
||||
|
||||
// For JAX-RS
|
||||
protected CreateGroupTransactionData() { |
||||
super(TransactionType.CREATE_GROUP); |
||||
} |
||||
|
||||
public CreateGroupTransactionData(byte[] creatorPublicKey, String owner, String groupName, String description, boolean isOpen, BigDecimal fee, long timestamp, byte[] reference, |
||||
byte[] signature) { |
||||
super(TransactionType.CREATE_GROUP, fee, creatorPublicKey, timestamp, reference, signature); |
||||
|
||||
this.creatorPublicKey = creatorPublicKey; |
||||
this.owner = owner; |
||||
this.groupName = groupName; |
||||
this.description = description; |
||||
this.isOpen = isOpen; |
||||
} |
||||
|
||||
public CreateGroupTransactionData(byte[] creatorPublicKey, String owner, String groupName, String description, boolean isOpen, BigDecimal fee, long timestamp, byte[] reference) { |
||||
this(creatorPublicKey, owner, groupName, description, isOpen, fee, timestamp, reference, null); |
||||
} |
||||
|
||||
// Getters / setters
|
||||
|
||||
public String getOwner() { |
||||
return this.owner; |
||||
} |
||||
|
||||
public String getGroupName() { |
||||
return this.groupName; |
||||
} |
||||
|
||||
public String getDescription() { |
||||
return this.description; |
||||
} |
||||
|
||||
public boolean getIsOpen() { |
||||
return this.isOpen; |
||||
} |
||||
|
||||
// Re-expose creatorPublicKey for this transaction type for JAXB
|
||||
@XmlElement(name = "creatorPublicKey") |
||||
@Schema(name = "creatorPublicKey", description = "group creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") |
||||
public byte[] getGroupCreatorPublicKey() { |
||||
return this.creatorPublicKey; |
||||
} |
||||
|
||||
@XmlElement(name = "creatorPublicKey") |
||||
@Schema(name = "creatorPublicKey", description = "group creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") |
||||
public void setGroupCreatorPublicKey(byte[] creatorPublicKey) { |
||||
this.creatorPublicKey = creatorPublicKey; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,98 @@
|
||||
package org.qora.data.transaction; |
||||
|
||||
import java.math.BigDecimal; |
||||
|
||||
import javax.xml.bind.Unmarshaller; |
||||
import javax.xml.bind.annotation.XmlAccessType; |
||||
import javax.xml.bind.annotation.XmlAccessorType; |
||||
import javax.xml.bind.annotation.XmlTransient; |
||||
|
||||
import org.qora.transaction.Transaction.TransactionType; |
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD) |
||||
@Schema(allOf = { TransactionData.class }) |
||||
public class UpdateGroupTransactionData extends TransactionData { |
||||
|
||||
// Properties
|
||||
@Schema(description = "owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") |
||||
private byte[] ownerPublicKey; |
||||
@Schema(description = "new owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v") |
||||
private String newOwner; |
||||
@Schema(description = "which group to update", example = "my-group") |
||||
private String groupName; |
||||
@Schema(description = "replacement group description", example = "my group for accounts I like") |
||||
private String newDescription; |
||||
@Schema(description = "new group join policy", example = "true") |
||||
private boolean newIsOpen; |
||||
// For internal use when orphaning
|
||||
@XmlTransient |
||||
@Schema(hidden = true) |
||||
private byte[] groupReference; |
||||
|
||||
// Constructors
|
||||
|
||||
// For JAX-RS
|
||||
protected UpdateGroupTransactionData() { |
||||
super(TransactionType.UPDATE_GROUP); |
||||
} |
||||
|
||||
public void afterUnmarshal(Unmarshaller u, Object parent) { |
||||
this.creatorPublicKey = this.ownerPublicKey; |
||||
} |
||||
|
||||
public UpdateGroupTransactionData(byte[] ownerPublicKey, String groupName, String newOwner, String newDescription, boolean newIsOpen, byte[] groupReference, BigDecimal fee, long timestamp, |
||||
byte[] reference, byte[] signature) { |
||||
super(TransactionType.UPDATE_GROUP, fee, ownerPublicKey, timestamp, reference, signature); |
||||
|
||||
this.ownerPublicKey = ownerPublicKey; |
||||
this.newOwner = newOwner; |
||||
this.groupName = groupName; |
||||
this.newDescription = newDescription; |
||||
this.newIsOpen = newIsOpen; |
||||
this.groupReference = groupReference; |
||||
} |
||||
|
||||
public UpdateGroupTransactionData(byte[] ownerPublicKey, String groupName, String newOwner, String newDescription, boolean newIsOpen, BigDecimal fee, long timestamp, byte[] reference, |
||||
byte[] signature) { |
||||
this(ownerPublicKey, groupName, newOwner, newDescription, newIsOpen, null, fee, timestamp, reference, signature); |
||||
} |
||||
|
||||
public UpdateGroupTransactionData(byte[] ownerPublicKey, String groupName, String newOwner, String newDescription, boolean newIsOpen, byte[] groupReference, BigDecimal fee, long timestamp, |
||||
byte[] reference) { |
||||
this(ownerPublicKey, groupName, newOwner, newDescription, newIsOpen, groupReference, fee, timestamp, reference, null); |
||||
} |
||||
|
||||
// Getters / setters
|
||||
|
||||
public byte[] getOwnerPublicKey() { |
||||
return this.ownerPublicKey; |
||||
} |
||||
|
||||
public String getNewOwner() { |
||||
return this.newOwner; |
||||
} |
||||
|
||||
public String getGroupName() { |
||||
return this.groupName; |
||||
} |
||||
|
||||
public String getNewDescription() { |
||||
return this.newDescription; |
||||
} |
||||
|
||||
public boolean getNewIsOpen() { |
||||
return this.newIsOpen; |
||||
} |
||||
|
||||
public byte[] getGroupReference() { |
||||
return this.groupReference; |
||||
} |
||||
|
||||
public void setGroupReference(byte[] groupReference) { |
||||
this.groupReference = groupReference; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,112 @@
|
||||
package org.qora.group; |
||||
|
||||
import org.qora.data.group.GroupData; |
||||
import org.qora.data.transaction.CreateGroupTransactionData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.data.transaction.UpdateGroupTransactionData; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
|
||||
public class Group { |
||||
|
||||
// Properties
|
||||
private Repository repository; |
||||
private GroupData groupData; |
||||
|
||||
// Useful constants
|
||||
public static final int MAX_NAME_SIZE = 400; |
||||
public static final int MAX_DESCRIPTION_SIZE = 4000; |
||||
|
||||
// Constructors
|
||||
|
||||
/** |
||||
* Construct Group business object using info from create group transaction. |
||||
* |
||||
* @param repository |
||||
* @param createGroupTransactionData |
||||
*/ |
||||
public Group(Repository repository, CreateGroupTransactionData createGroupTransactionData) { |
||||
this.repository = repository; |
||||
this.groupData = new GroupData(createGroupTransactionData.getOwner(), |
||||
createGroupTransactionData.getGroupName(), createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(), |
||||
createGroupTransactionData.getIsOpen(), createGroupTransactionData.getSignature()); |
||||
} |
||||
|
||||
/** |
||||
* Construct Group business object using existing group in repository. |
||||
* |
||||
* @param repository |
||||
* @param groupName |
||||
* @throws DataException |
||||
*/ |
||||
public Group(Repository repository, String groupName) throws DataException { |
||||
this.repository = repository; |
||||
this.groupData = this.repository.getGroupRepository().fromGroupName(groupName); |
||||
} |
||||
|
||||
// Processing
|
||||
|
||||
public void create() throws DataException { |
||||
this.repository.getGroupRepository().save(this.groupData); |
||||
} |
||||
|
||||
public void uncreate() throws DataException { |
||||
this.repository.getGroupRepository().delete(this.groupData.getGroupName()); |
||||
} |
||||
|
||||
private void revert() throws DataException { |
||||
TransactionData previousTransactionData = this.repository.getTransactionRepository().fromSignature(this.groupData.getReference()); |
||||
if (previousTransactionData == null) |
||||
throw new DataException("Unable to revert group transaction as referenced transaction not found in repository"); |
||||
|
||||
switch (previousTransactionData.getType()) { |
||||
case CREATE_GROUP: |
||||
CreateGroupTransactionData previousCreateGroupTransactionData = (CreateGroupTransactionData) previousTransactionData; |
||||
this.groupData.setOwner(previousCreateGroupTransactionData.getOwner()); |
||||
this.groupData.setDescription(previousCreateGroupTransactionData.getDescription()); |
||||
this.groupData.setIsOpen(previousCreateGroupTransactionData.getIsOpen()); |
||||
this.groupData.setUpdated(null); |
||||
break; |
||||
|
||||
case UPDATE_GROUP: |
||||
UpdateGroupTransactionData previousUpdateGroupTransactionData = (UpdateGroupTransactionData) previousTransactionData; |
||||
this.groupData.setOwner(previousUpdateGroupTransactionData.getNewOwner()); |
||||
this.groupData.setDescription(previousUpdateGroupTransactionData.getNewDescription()); |
||||
this.groupData.setIsOpen(previousUpdateGroupTransactionData.getNewIsOpen()); |
||||
this.groupData.setUpdated(previousUpdateGroupTransactionData.getTimestamp()); |
||||
break; |
||||
|
||||
default: |
||||
throw new IllegalStateException("Unable to revert group transaction due to unsupported referenced transaction"); |
||||
} |
||||
} |
||||
|
||||
public void update(UpdateGroupTransactionData updateGroupTransactionData) throws DataException { |
||||
// Update reference in transaction data
|
||||
updateGroupTransactionData.setGroupReference(this.groupData.getReference()); |
||||
|
||||
// New group reference is this transaction's signature
|
||||
this.groupData.setReference(updateGroupTransactionData.getSignature()); |
||||
|
||||
// Update Group's owner and description
|
||||
this.groupData.setOwner(updateGroupTransactionData.getNewOwner()); |
||||
this.groupData.setDescription(updateGroupTransactionData.getNewDescription()); |
||||
this.groupData.setIsOpen(updateGroupTransactionData.getNewIsOpen()); |
||||
this.groupData.setUpdated(updateGroupTransactionData.getTimestamp()); |
||||
|
||||
// Save updated group data
|
||||
this.repository.getGroupRepository().save(this.groupData); |
||||
} |
||||
|
||||
public void revert(UpdateGroupTransactionData updateGroupTransactionData) throws DataException { |
||||
// Previous group reference is taken from this transaction's cached copy
|
||||
this.groupData.setReference(updateGroupTransactionData.getGroupReference()); |
||||
|
||||
// Previous Group's owner and/or description taken from referenced transaction
|
||||
this.revert(); |
||||
|
||||
// Save reverted group data
|
||||
this.repository.getGroupRepository().save(this.groupData); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.qora.repository; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.qora.data.group.GroupData; |
||||
|
||||
public interface GroupRepository { |
||||
|
||||
public GroupData fromGroupName(String groupName) throws DataException; |
||||
|
||||
public boolean groupExists(String groupName) throws DataException; |
||||
|
||||
public List<GroupData> getAllGroups() throws DataException; |
||||
|
||||
public List<GroupData> getGroupsByOwner(String address) throws DataException; |
||||
|
||||
public void save(GroupData groupData) throws DataException; |
||||
|
||||
public void delete(String groupName) throws DataException; |
||||
|
||||
} |
@ -0,0 +1,144 @@
|
||||
package org.qora.repository.hsqldb; |
||||
|
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
import java.sql.Timestamp; |
||||
import java.util.ArrayList; |
||||
import java.util.Calendar; |
||||
import java.util.List; |
||||
|
||||
import org.qora.data.group.GroupData; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.GroupRepository; |
||||
|
||||
public class HSQLDBGroupRepository implements GroupRepository { |
||||
|
||||
protected HSQLDBRepository repository; |
||||
|
||||
public HSQLDBGroupRepository(HSQLDBRepository repository) { |
||||
this.repository = repository; |
||||
} |
||||
|
||||
@Override |
||||
public GroupData fromGroupName(String groupName) throws DataException { |
||||
try (ResultSet resultSet = this.repository |
||||
.checkedExecute("SELECT owner, description, created, updated, reference, is_open FROM AccountGroups WHERE group_name = ?", groupName)) { |
||||
if (resultSet == null) |
||||
return null; |
||||
|
||||
String owner = resultSet.getString(1); |
||||
String description = resultSet.getString(2); |
||||
long created = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); |
||||
|
||||
// Special handling for possibly-NULL "updated" column
|
||||
Timestamp updatedTimestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)); |
||||
Long updated = resultSet.wasNull() ? null : updatedTimestamp.getTime(); |
||||
|
||||
byte[] reference = resultSet.getBytes(5); |
||||
boolean isOpen = resultSet.getBoolean(6); |
||||
|
||||
return new GroupData(owner, groupName, description, created, updated, isOpen, reference); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to fetch group info from repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean groupExists(String groupName) throws DataException { |
||||
try { |
||||
return this.repository.exists("AccountGroups", "group_name = ?", groupName); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to check for group in repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public List<GroupData> getAllGroups() throws DataException { |
||||
List<GroupData> groups = new ArrayList<>(); |
||||
|
||||
try (ResultSet resultSet = this.repository |
||||
.checkedExecute("SELECT group_name, description, owner, created, updated, reference, is_open FROM AccountGroups")) { |
||||
if (resultSet == null) |
||||
return groups; |
||||
|
||||
do { |
||||
String groupName = resultSet.getString(1); |
||||
String description = resultSet.getString(2); |
||||
String owner = resultSet.getString(3); |
||||
long created = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); |
||||
|
||||
// Special handling for possibly-NULL "updated" column
|
||||
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)); |
||||
Long updated = resultSet.wasNull() ? null : updatedTimestamp.getTime(); |
||||
|
||||
byte[] reference = resultSet.getBytes(6); |
||||
boolean isOpen = resultSet.getBoolean(7); |
||||
|
||||
groups.add(new GroupData(owner, groupName, description, created, updated, isOpen, reference)); |
||||
} while (resultSet.next()); |
||||
|
||||
return groups; |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to fetch groups from repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public List<GroupData> getGroupsByOwner(String owner) throws DataException { |
||||
List<GroupData> groups = new ArrayList<>(); |
||||
|
||||
try (ResultSet resultSet = this.repository |
||||
.checkedExecute("SELECT group_name, description, created, updated, reference, is_open FROM AccountGroups WHERE owner = ?", owner)) { |
||||
if (resultSet == null) |
||||
return groups; |
||||
|
||||
do { |
||||
String groupName = resultSet.getString(1); |
||||
String description = resultSet.getString(2); |
||||
long created = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); |
||||
|
||||
// Special handling for possibly-NULL "updated" column
|
||||
Timestamp updatedTimestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)); |
||||
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime(); |
||||
|
||||
byte[] reference = resultSet.getBytes(5); |
||||
boolean isOpen = resultSet.getBoolean(6); |
||||
|
||||
groups.add(new GroupData(owner, groupName, description, created, updated, isOpen, reference)); |
||||
} while (resultSet.next()); |
||||
|
||||
return groups; |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to fetch account's groups from repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void save(GroupData groupData) throws DataException { |
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountGroups"); |
||||
|
||||
// Special handling for "updated" timestamp
|
||||
Long updated = groupData.getUpdated(); |
||||
Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated); |
||||
|
||||
saveHelper.bind("owner", groupData.getOwner()).bind("group_name", groupData.getGroupName()) |
||||
.bind("description", groupData.getDescription()).bind("created", new Timestamp(groupData.getCreated())).bind("updated", updatedTimestamp) |
||||
.bind("reference", groupData.getReference()).bind("is_open", groupData.getIsOpen()); |
||||
|
||||
try { |
||||
saveHelper.execute(this.repository); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to save group info into repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void delete(String groupName) throws DataException { |
||||
try { |
||||
this.repository.delete("AccountGroups", "group_name = ?", groupName); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to delete group info from repository", e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,53 @@
|
||||
package org.qora.repository.hsqldb.transaction; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
|
||||
import org.qora.data.transaction.CreateGroupTransactionData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.hsqldb.HSQLDBRepository; |
||||
import org.qora.repository.hsqldb.HSQLDBSaver; |
||||
|
||||
public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRepository { |
||||
|
||||
public HSQLDBCreateGroupTransactionRepository(HSQLDBRepository repository) { |
||||
this.repository = repository; |
||||
} |
||||
|
||||
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { |
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner, group_name, description, is_open FROM CreateGroupTransactions WHERE signature = ?", |
||||
signature)) { |
||||
if (resultSet == null) |
||||
return null; |
||||
|
||||
String owner = resultSet.getString(1); |
||||
String groupName = resultSet.getString(2); |
||||
String description = resultSet.getString(3); |
||||
boolean isOpen = resultSet.getBoolean(4); |
||||
|
||||
return new CreateGroupTransactionData(creatorPublicKey, owner, groupName, description, isOpen, fee, timestamp, reference, signature); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to fetch create group transaction from repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void save(TransactionData transactionData) throws DataException { |
||||
CreateGroupTransactionData createGroupTransactionData = (CreateGroupTransactionData) transactionData; |
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("CreateGroupTransactions"); |
||||
|
||||
saveHelper.bind("signature", createGroupTransactionData.getSignature()).bind("creator", createGroupTransactionData.getCreatorPublicKey()) |
||||
.bind("owner", createGroupTransactionData.getOwner()).bind("group_name", createGroupTransactionData.getGroupName()) |
||||
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.getIsOpen()); |
||||
|
||||
try { |
||||
saveHelper.execute(this.repository); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to save create group transaction into repository", e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,55 @@
|
||||
package org.qora.repository.hsqldb.transaction; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
|
||||
import org.qora.data.transaction.UpdateGroupTransactionData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.hsqldb.HSQLDBRepository; |
||||
import org.qora.repository.hsqldb.HSQLDBSaver; |
||||
|
||||
public class HSQLDBUpdateGroupTransactionRepository extends HSQLDBTransactionRepository { |
||||
|
||||
public HSQLDBUpdateGroupTransactionRepository(HSQLDBRepository repository) { |
||||
this.repository = repository; |
||||
} |
||||
|
||||
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { |
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT group_name, new_owner, new_description, new_is_open, group_reference FROM UpdateGroupTransactions WHERE signature = ?", |
||||
signature)) { |
||||
if (resultSet == null) |
||||
return null; |
||||
|
||||
String groupName = resultSet.getString(1); |
||||
String newOwner = resultSet.getString(2); |
||||
String newDescription = resultSet.getString(3); |
||||
boolean newIsOpen = resultSet.getBoolean(4); |
||||
byte[] groupReference = resultSet.getBytes(5); |
||||
|
||||
return new UpdateGroupTransactionData(creatorPublicKey, groupName, newOwner, newDescription, newIsOpen, groupReference, fee, timestamp, reference, signature); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to fetch update group transaction from repository", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void save(TransactionData transactionData) throws DataException { |
||||
UpdateGroupTransactionData updateGroupTransactionData = (UpdateGroupTransactionData) transactionData; |
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("UpdateGroupTransactions"); |
||||
|
||||
saveHelper.bind("signature", updateGroupTransactionData.getSignature()).bind("owner", updateGroupTransactionData.getOwnerPublicKey()) |
||||
.bind("group_name", updateGroupTransactionData.getGroupName()).bind("new_owner", updateGroupTransactionData.getNewOwner()) |
||||
.bind("new_description", updateGroupTransactionData.getNewDescription()).bind("new_is_open", updateGroupTransactionData.getNewIsOpen()) |
||||
.bind("group_reference", updateGroupTransactionData.getGroupReference()); |
||||
|
||||
try { |
||||
saveHelper.execute(this.repository); |
||||
} catch (SQLException e) { |
||||
throw new DataException("Unable to save update group transaction into repository", e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,146 @@
|
||||
package org.qora.transaction; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.qora.account.Account; |
||||
import org.qora.asset.Asset; |
||||
import org.qora.crypto.Crypto; |
||||
import org.qora.data.transaction.CreateGroupTransactionData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.group.Group; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
|
||||
import com.google.common.base.Utf8; |
||||
|
||||
public class CreateGroupTransaction extends Transaction { |
||||
|
||||
// Properties
|
||||
private CreateGroupTransactionData createGroupTransactionData; |
||||
|
||||
// Constructors
|
||||
|
||||
public CreateGroupTransaction(Repository repository, TransactionData transactionData) { |
||||
super(repository, transactionData); |
||||
|
||||
this.createGroupTransactionData = (CreateGroupTransactionData) this.transactionData; |
||||
} |
||||
|
||||
// More information
|
||||
|
||||
@Override |
||||
public List<Account> getRecipientAccounts() throws DataException { |
||||
return Collections.singletonList(getOwner()); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isInvolved(Account account) throws DataException { |
||||
String address = account.getAddress(); |
||||
|
||||
if (address.equals(this.getCreator().getAddress())) |
||||
return true; |
||||
|
||||
if (address.equals(this.getOwner().getAddress())) |
||||
return true; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public BigDecimal getAmount(Account account) throws DataException { |
||||
String address = account.getAddress(); |
||||
BigDecimal amount = BigDecimal.ZERO.setScale(8); |
||||
|
||||
if (address.equals(this.getCreator().getAddress())) |
||||
amount = amount.subtract(this.transactionData.getFee()); |
||||
|
||||
return amount; |
||||
} |
||||
|
||||
// Navigation
|
||||
|
||||
public Account getOwner() throws DataException { |
||||
return new Account(this.repository, this.createGroupTransactionData.getOwner()); |
||||
} |
||||
|
||||
// Processing
|
||||
|
||||
@Override |
||||
public ValidationResult isValid() throws DataException { |
||||
// Check owner address is valid
|
||||
if (!Crypto.isValidAddress(createGroupTransactionData.getOwner())) |
||||
return ValidationResult.INVALID_ADDRESS; |
||||
|
||||
// Check group name size bounds
|
||||
int groupNameLength = Utf8.encodedLength(createGroupTransactionData.getGroupName()); |
||||
if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE) |
||||
return ValidationResult.INVALID_NAME_LENGTH; |
||||
|
||||
// Check description size bounds
|
||||
int descriptionLength = Utf8.encodedLength(createGroupTransactionData.getDescription()); |
||||
if (descriptionLength < 1 || descriptionLength > Group.MAX_DESCRIPTION_SIZE) |
||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH; |
||||
|
||||
// Check group name is lowercase
|
||||
if (!createGroupTransactionData.getGroupName().equals(createGroupTransactionData.getGroupName().toLowerCase())) |
||||
return ValidationResult.NAME_NOT_LOWER_CASE; |
||||
|
||||
// Check the group name isn't already taken
|
||||
if (this.repository.getGroupRepository().groupExists(createGroupTransactionData.getGroupName())) |
||||
return ValidationResult.GROUP_ALREADY_EXISTS; |
||||
|
||||
// Check fee is positive
|
||||
if (createGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) |
||||
return ValidationResult.NEGATIVE_FEE; |
||||
|
||||
// Check reference is correct
|
||||
Account creator = getCreator(); |
||||
|
||||
if (!Arrays.equals(creator.getLastReference(), createGroupTransactionData.getReference())) |
||||
return ValidationResult.INVALID_REFERENCE; |
||||
|
||||
// Check creator has enough funds
|
||||
if (creator.getConfirmedBalance(Asset.QORA).compareTo(createGroupTransactionData.getFee()) < 0) |
||||
return ValidationResult.NO_BALANCE; |
||||
|
||||
return ValidationResult.OK; |
||||
} |
||||
|
||||
@Override |
||||
public void process() throws DataException { |
||||
// Create Group
|
||||
Group group = new Group(this.repository, createGroupTransactionData); |
||||
group.create(); |
||||
|
||||
// Save this transaction
|
||||
this.repository.getTransactionRepository().save(createGroupTransactionData); |
||||
|
||||
// Update creator's balance
|
||||
Account creator = getCreator(); |
||||
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(createGroupTransactionData.getFee())); |
||||
|
||||
// Update creator's reference
|
||||
creator.setLastReference(createGroupTransactionData.getSignature()); |
||||
} |
||||
|
||||
@Override |
||||
public void orphan() throws DataException { |
||||
// Uncreate group
|
||||
Group group = new Group(this.repository, createGroupTransactionData.getGroupName()); |
||||
group.uncreate(); |
||||
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(createGroupTransactionData); |
||||
|
||||
// Update creator's balance
|
||||
Account creator = getCreator(); |
||||
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(createGroupTransactionData.getFee())); |
||||
|
||||
// Update creator's reference
|
||||
creator.setLastReference(createGroupTransactionData.getReference()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,159 @@
|
||||
package org.qora.transaction; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.qora.account.Account; |
||||
import org.qora.account.PublicKeyAccount; |
||||
import org.qora.asset.Asset; |
||||
import org.qora.crypto.Crypto; |
||||
import org.qora.data.transaction.UpdateGroupTransactionData; |
||||
import org.qora.data.group.GroupData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.group.Group; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
|
||||
import com.google.common.base.Utf8; |
||||
|
||||
public class UpdateGroupTransaction extends Transaction { |
||||
|
||||
// Properties
|
||||
private UpdateGroupTransactionData updateGroupTransactionData; |
||||
|
||||
// Constructors
|
||||
|
||||
public UpdateGroupTransaction(Repository repository, TransactionData transactionData) { |
||||
super(repository, transactionData); |
||||
|
||||
this.updateGroupTransactionData = (UpdateGroupTransactionData) this.transactionData; |
||||
} |
||||
|
||||
// More information
|
||||
|
||||
@Override |
||||
public List<Account> getRecipientAccounts() throws DataException { |
||||
return Collections.singletonList(getNewOwner()); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isInvolved(Account account) throws DataException { |
||||
String address = account.getAddress(); |
||||
|
||||
if (address.equals(this.getOwner().getAddress())) |
||||
return true; |
||||
|
||||
if (address.equals(this.getNewOwner().getAddress())) |
||||
return true; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public BigDecimal getAmount(Account account) throws DataException { |
||||
String address = account.getAddress(); |
||||
BigDecimal amount = BigDecimal.ZERO.setScale(8); |
||||
|
||||
if (address.equals(this.getOwner().getAddress())) |
||||
amount = amount.subtract(this.transactionData.getFee()); |
||||
|
||||
return amount; |
||||
} |
||||
|
||||
// Navigation
|
||||
|
||||
public Account getOwner() throws DataException { |
||||
return new PublicKeyAccount(this.repository, this.updateGroupTransactionData.getOwnerPublicKey()); |
||||
} |
||||
|
||||
public Account getNewOwner() throws DataException { |
||||
return new Account(this.repository, this.updateGroupTransactionData.getNewOwner()); |
||||
} |
||||
|
||||
// Processing
|
||||
|
||||
@Override |
||||
public ValidationResult isValid() throws DataException { |
||||
// Check new owner address is valid
|
||||
if (!Crypto.isValidAddress(updateGroupTransactionData.getNewOwner())) |
||||
return ValidationResult.INVALID_ADDRESS; |
||||
|
||||
// Check group name size bounds
|
||||
int groupNameLength = Utf8.encodedLength(updateGroupTransactionData.getGroupName()); |
||||
if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE) |
||||
return ValidationResult.INVALID_NAME_LENGTH; |
||||
|
||||
// Check new description size bounds
|
||||
int newDescriptionLength = Utf8.encodedLength(updateGroupTransactionData.getNewDescription()); |
||||
if (newDescriptionLength < 1 || newDescriptionLength > Group.MAX_DESCRIPTION_SIZE) |
||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH; |
||||
|
||||
// Check group name is lowercase
|
||||
if (!updateGroupTransactionData.getGroupName().equals(updateGroupTransactionData.getGroupName().toLowerCase())) |
||||
return ValidationResult.NAME_NOT_LOWER_CASE; |
||||
|
||||
GroupData groupData = this.repository.getGroupRepository().fromGroupName(updateGroupTransactionData.getGroupName()); |
||||
|
||||
// Check group exists
|
||||
if (groupData == null) |
||||
return ValidationResult.GROUP_DOES_NOT_EXIST; |
||||
|
||||
// Check transaction's public key matches group's current owner
|
||||
Account owner = getOwner(); |
||||
if (!owner.getAddress().equals(groupData.getOwner())) |
||||
return ValidationResult.INVALID_GROUP_OWNER; |
||||
|
||||
// Check fee is positive
|
||||
if (updateGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) |
||||
return ValidationResult.NEGATIVE_FEE; |
||||
|
||||
// Check reference is correct
|
||||
Account creator = getCreator(); |
||||
|
||||
if (!Arrays.equals(creator.getLastReference(), updateGroupTransactionData.getReference())) |
||||
return ValidationResult.INVALID_REFERENCE; |
||||
|
||||
// Check creator has enough funds
|
||||
if (creator.getConfirmedBalance(Asset.QORA).compareTo(updateGroupTransactionData.getFee()) < 0) |
||||
return ValidationResult.NO_BALANCE; |
||||
|
||||
return ValidationResult.OK; |
||||
} |
||||
|
||||
@Override |
||||
public void process() throws DataException { |
||||
// Update Group
|
||||
Group group = new Group(this.repository, updateGroupTransactionData.getGroupName()); |
||||
group.update(updateGroupTransactionData); |
||||
|
||||
// Save this transaction, now with updated "group reference" to previous transaction that updated group
|
||||
this.repository.getTransactionRepository().save(updateGroupTransactionData); |
||||
|
||||
// Update owner's balance
|
||||
Account owner = getOwner(); |
||||
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(updateGroupTransactionData.getFee())); |
||||
|
||||
// Update owner's reference
|
||||
owner.setLastReference(updateGroupTransactionData.getSignature()); |
||||
} |
||||
|
||||
@Override |
||||
public void orphan() throws DataException { |
||||
// Revert name
|
||||
Group group = new Group(this.repository, updateGroupTransactionData.getGroupName()); |
||||
group.revert(updateGroupTransactionData); |
||||
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(updateGroupTransactionData); |
||||
|
||||
// Update owner's balance
|
||||
Account owner = getOwner(); |
||||
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(updateGroupTransactionData.getFee())); |
||||
|
||||
// Update owner's reference
|
||||
owner.setLastReference(updateGroupTransactionData.getReference()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,116 @@
|
||||
package org.qora.transform.transaction; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.math.BigDecimal; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import org.json.simple.JSONObject; |
||||
import org.qora.account.PublicKeyAccount; |
||||
import org.qora.data.transaction.CreateGroupTransactionData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.group.Group; |
||||
import org.qora.transform.TransformationException; |
||||
import org.qora.utils.Serialization; |
||||
|
||||
import com.google.common.base.Utf8; |
||||
import com.google.common.hash.HashCode; |
||||
import com.google.common.primitives.Ints; |
||||
import com.google.common.primitives.Longs; |
||||
|
||||
public class CreateGroupTransactionTransformer extends TransactionTransformer { |
||||
|
||||
// Property lengths
|
||||
private static final int CREATOR_LENGTH = PUBLIC_KEY_LENGTH; |
||||
private static final int OWNER_LENGTH = ADDRESS_LENGTH; |
||||
private static final int NAME_SIZE_LENGTH = INT_LENGTH; |
||||
private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH; |
||||
private static final int IS_OPEN_LENGTH = BOOLEAN_LENGTH; |
||||
|
||||
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + IS_OPEN_LENGTH; |
||||
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { |
||||
long timestamp = byteBuffer.getLong(); |
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH]; |
||||
byteBuffer.get(reference); |
||||
|
||||
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer); |
||||
|
||||
String owner = Serialization.deserializeAddress(byteBuffer); |
||||
|
||||
String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_SIZE); |
||||
|
||||
String description = Serialization.deserializeSizedString(byteBuffer, Group.MAX_DESCRIPTION_SIZE); |
||||
|
||||
boolean isOpen = byteBuffer.get() != 0; |
||||
|
||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); |
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH]; |
||||
byteBuffer.get(signature); |
||||
|
||||
return new CreateGroupTransactionData(creatorPublicKey, owner, groupName, description, isOpen, fee, timestamp, reference, signature); |
||||
} |
||||
|
||||
public static int getDataLength(TransactionData transactionData) throws TransformationException { |
||||
CreateGroupTransactionData createGroupTransactionData = (CreateGroupTransactionData) transactionData; |
||||
|
||||
int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + Utf8.encodedLength(createGroupTransactionData.getGroupName()) |
||||
+ Utf8.encodedLength(createGroupTransactionData.getDescription()); |
||||
|
||||
return dataLength; |
||||
} |
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException { |
||||
try { |
||||
CreateGroupTransactionData createGroupTransactionData = (CreateGroupTransactionData) transactionData; |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
bytes.write(Ints.toByteArray(createGroupTransactionData.getType().value)); |
||||
bytes.write(Longs.toByteArray(createGroupTransactionData.getTimestamp())); |
||||
bytes.write(createGroupTransactionData.getReference()); |
||||
|
||||
bytes.write(createGroupTransactionData.getCreatorPublicKey()); |
||||
Serialization.serializeAddress(bytes, createGroupTransactionData.getOwner()); |
||||
Serialization.serializeSizedString(bytes, createGroupTransactionData.getGroupName()); |
||||
Serialization.serializeSizedString(bytes, createGroupTransactionData.getDescription()); |
||||
|
||||
bytes.write((byte) (createGroupTransactionData.getIsOpen() ? 1 : 0)); |
||||
|
||||
Serialization.serializeBigDecimal(bytes, createGroupTransactionData.getFee()); |
||||
|
||||
if (createGroupTransactionData.getSignature() != null) |
||||
bytes.write(createGroupTransactionData.getSignature()); |
||||
|
||||
return bytes.toByteArray(); |
||||
} catch (IOException | ClassCastException e) { |
||||
throw new TransformationException(e); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { |
||||
JSONObject json = TransactionTransformer.getBaseJSON(transactionData); |
||||
|
||||
try { |
||||
CreateGroupTransactionData createGroupTransactionData = (CreateGroupTransactionData) transactionData; |
||||
|
||||
byte[] creatorPublicKey = createGroupTransactionData.getCreatorPublicKey(); |
||||
|
||||
json.put("creator", PublicKeyAccount.getAddress(creatorPublicKey)); |
||||
json.put("creatorPublicKey", HashCode.fromBytes(creatorPublicKey).toString()); |
||||
|
||||
json.put("owner", createGroupTransactionData.getOwner()); |
||||
json.put("groupName", createGroupTransactionData.getGroupName()); |
||||
json.put("description", createGroupTransactionData.getDescription()); |
||||
json.put("isOpen", createGroupTransactionData.getIsOpen()); |
||||
} catch (ClassCastException e) { |
||||
throw new TransformationException(e); |
||||
} |
||||
|
||||
return json; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,117 @@
|
||||
package org.qora.transform.transaction; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.math.BigDecimal; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import org.json.simple.JSONObject; |
||||
import org.qora.account.PublicKeyAccount; |
||||
import org.qora.data.transaction.UpdateGroupTransactionData; |
||||
import org.qora.data.transaction.TransactionData; |
||||
import org.qora.group.Group; |
||||
import org.qora.transform.TransformationException; |
||||
import org.qora.utils.Serialization; |
||||
|
||||
import com.google.common.base.Utf8; |
||||
import com.google.common.hash.HashCode; |
||||
import com.google.common.primitives.Ints; |
||||
import com.google.common.primitives.Longs; |
||||
|
||||
public class UpdateGroupTransactionTransformer extends TransactionTransformer { |
||||
|
||||
// Property lengths
|
||||
private static final int OWNER_LENGTH = PUBLIC_KEY_LENGTH; |
||||
private static final int NEW_OWNER_LENGTH = ADDRESS_LENGTH; |
||||
private static final int NAME_SIZE_LENGTH = INT_LENGTH; |
||||
private static final int NEW_DESCRIPTION_SIZE_LENGTH = INT_LENGTH; |
||||
private static final int NEW_IS_OPEN_LENGTH = BOOLEAN_LENGTH; |
||||
|
||||
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NEW_OWNER_LENGTH + NAME_SIZE_LENGTH + NEW_DESCRIPTION_SIZE_LENGTH + NEW_IS_OPEN_LENGTH; |
||||
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { |
||||
long timestamp = byteBuffer.getLong(); |
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH]; |
||||
byteBuffer.get(reference); |
||||
|
||||
byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer); |
||||
|
||||
String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_SIZE); |
||||
|
||||
String newOwner = Serialization.deserializeAddress(byteBuffer); |
||||
|
||||
String newDescription = Serialization.deserializeSizedString(byteBuffer, Group.MAX_DESCRIPTION_SIZE); |
||||
|
||||
boolean newIsOpen = byteBuffer.get() != 0; |
||||
|
||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); |
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH]; |
||||
byteBuffer.get(signature); |
||||
|
||||
return new UpdateGroupTransactionData(ownerPublicKey, groupName, newOwner, newDescription, newIsOpen, fee, timestamp, reference, signature); |
||||
} |
||||
|
||||
public static int getDataLength(TransactionData transactionData) throws TransformationException { |
||||
UpdateGroupTransactionData updateGroupTransactionData = (UpdateGroupTransactionData) transactionData; |
||||
|
||||
int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + Utf8.encodedLength(updateGroupTransactionData.getGroupName()) |
||||
+ Utf8.encodedLength(updateGroupTransactionData.getNewDescription()); |
||||
|
||||
return dataLength; |
||||
} |
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException { |
||||
try { |
||||
UpdateGroupTransactionData updateGroupTransactionData = (UpdateGroupTransactionData) transactionData; |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
bytes.write(Ints.toByteArray(updateGroupTransactionData.getType().value)); |
||||
bytes.write(Longs.toByteArray(updateGroupTransactionData.getTimestamp())); |
||||
bytes.write(updateGroupTransactionData.getReference()); |
||||
|
||||
bytes.write(updateGroupTransactionData.getCreatorPublicKey()); |
||||
Serialization.serializeSizedString(bytes, updateGroupTransactionData.getGroupName()); |
||||
|
||||
Serialization.serializeAddress(bytes, updateGroupTransactionData.getNewOwner()); |
||||
Serialization.serializeSizedString(bytes, updateGroupTransactionData.getNewDescription()); |
||||
|
||||
bytes.write((byte) (updateGroupTransactionData.getNewIsOpen() ? 1 : 0)); |
||||
|
||||
Serialization.serializeBigDecimal(bytes, updateGroupTransactionData.getFee()); |
||||
|
||||
if (updateGroupTransactionData.getSignature() != null) |
||||
bytes.write(updateGroupTransactionData.getSignature()); |
||||
|
||||
return bytes.toByteArray(); |
||||
} catch (IOException | ClassCastException e) { |
||||
throw new TransformationException(e); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { |
||||
JSONObject json = TransactionTransformer.getBaseJSON(transactionData); |
||||
|
||||
try { |
||||
UpdateGroupTransactionData updateGroupTransactionData = (UpdateGroupTransactionData) transactionData; |
||||
|
||||
byte[] ownerPublicKey = updateGroupTransactionData.getOwnerPublicKey(); |
||||
|
||||
json.put("owner", PublicKeyAccount.getAddress(ownerPublicKey)); |
||||
json.put("ownerPublicKey", HashCode.fromBytes(ownerPublicKey).toString()); |
||||
|
||||
json.put("groupName", updateGroupTransactionData.getGroupName()); |
||||
json.put("newOwner", updateGroupTransactionData.getNewOwner()); |
||||
json.put("newDescription", updateGroupTransactionData.getNewDescription()); |
||||
json.put("newIsOpen", updateGroupTransactionData.getNewIsOpen()); |
||||
} catch (ClassCastException e) { |
||||
throw new TransformationException(e); |
||||
} |
||||
|
||||
return json; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue