/*******************************************************************************
* Copyright (c) 2015 IBM Corp.
*
* Licensed 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.ibm.ws.lars.rest.model;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.ibm.ws.lars.rest.AssetConverter;
import com.ibm.ws.lars.rest.exceptions.InvalidJsonAssetException;
import com.ibm.ws.lars.rest.exceptions.RepositoryException;
/**
* Enforces the constrains on a JSON document to turn it into a Asset as required by the asset
* service.
*
*/
@JsonSerialize(converter = AssetConverter.class)
public class Asset extends RepositoryObject {
public static final String ATTACHMENTS = "attachments";
/** */
public static final String LAST_UPDATED_ON = "lastUpdatedOn";
/** */
public static final String CREATED_ON = "createdOn";
public static final String STATE = "state";
public static final String CREATED_BY = "createdBy";
public static final String NAME = "name";
public Asset() {
super();
}
/**
* Wrap an existing map of properties in an asset object.
* <p>
* Changes to the asset will be reflected in the original map.
*
* @param state the map of properties
*/
public Asset(Map<String, Object> state) {
super(state);
}
/**
* Copy another attachment, creating a new copy of the internal set of properties.
*
* @param toClone the attachment to clone
*/
public Asset(Asset toClone) {
super(toClone);
}
public String getCreatedOn() {
return get(CREATED_ON);
}
public void setCreatedOn(String date) {
put(CREATED_ON, date);
}
public String getLastUpdatedOn() {
Object lastUpdatedOn = properties.get(LAST_UPDATED_ON);
if (lastUpdatedOn != null) {
return (String) lastUpdatedOn;
}
return null;
}
public void setLastUpdatedOn(String date) {
put(LAST_UPDATED_ON, date);
}
public String getCreatedBy() {
String createdByName = null;
Map<?, ?> createdBy = (Map<?, ?>) properties.get(CREATED_BY);
if (createdBy != null) {
createdByName = (String) createdBy.get(NAME);
}
return createdByName;
}
public void setCreatedBy(String name) {
Map<String, Object> createdBy = new HashMap<>();
createdBy.put(NAME, name);
properties.put(CREATED_BY, createdBy);
}
/**
* Reads an asset from the supplied JSON. No fields will be modified. This method does not
* sanitise the asset or check that it is in a fit state to be written straight to the database.
*
* TODO we really wanna get rid of this method
*/
public static Asset deserializeAssetFromJson(String json) throws InvalidJsonAssetException {
// TODO get rid
return new Asset(readJsonState(json));
}
/**
* Reads an asset from the supplied JSON. No fields will be modified. This method does not
* sanitise the asset or check that it is in a fit state to be written straight to the database.
*
* @param json
* @return
* @throws InvalidJsonAssetException
*/
public static Asset deserializeAssetFromJson(InputStream json) throws InvalidJsonAssetException {
return new Asset(readJsonState(json));
}
public static Asset createAssetFromMap(Map<String, Object> state) {
return new Asset(state);
}
public void setAttachments(AttachmentList attachments) {
put(ATTACHMENTS, attachments.getState());
}
public AttachmentList getAttachments() {
List<Map<String, Object>> attachmentsState = get(ATTACHMENTS);
return AttachmentList.createAttachmentListFromMaps(attachmentsState);
}
/**
* State should normally be updated using the <code>updateState</code> method which enforces
* life-cycle state transition rules.
*
* @param newState
*/
private void setState(State newState) {
properties.put(STATE, newState.getValue());
}
public State getState() {
Object state = properties.get(STATE);
if (state != null) {
State foundState = State.forValue((String) state);
if (foundState != null) {
return foundState;
}
}
// Have failed to find a correct state value in the asset. This is an internal
// error as the system should not allow an asset to be created in this manner.
throw new RepositoryException("Asset doesn't contain valid state value."
+ "Actual value is: " + properties.get(STATE));
}
/**
* This interface is implemented by the State enum. The methods change the state of the asset.
* The actions equate to the following state transitions:
*
* PUBLISH: Move the state from DRAFT to AWAITING_APPROVAL<br>
* APPROVE: Move the state from AWAITING_APPROVAL to PUBLISHED<br>
* CANCEL: Move the state from AWAITING_APPROVAL to DRAFT<br>
* NEED_MORE_INFO: Move the state from AWAITING_APPROVAL to NEED_MORE_INFO<br>
* UNPUBLISH: Move the state from PUBLISHED to DRAFT
*
*/
private interface StateMachine {
public void publish(Asset ass) throws RepositoryResourceLifecycleException;
public void approve(Asset ass) throws RepositoryResourceLifecycleException;
public void cancel(Asset ass) throws RepositoryResourceLifecycleException;
public void need_more_info(Asset ass) throws RepositoryResourceLifecycleException;
public void unpublish(Asset ass) throws RepositoryResourceLifecycleException;
}
public enum State implements StateMachine {
/**
* The asset is in draft state, valid transitions are to publish.
*/
DRAFT("draft") {
@Override
public void publish(Asset ass) throws RepositoryResourceLifecycleException {
ass.setState(State.AWAITING_APPROVAL);
}
},
/**
* The asset is in awaiting_approval valid transitions are to approve, cancel, or
* need_more_info
*/
AWAITING_APPROVAL("awaiting_approval") {
@Override
public void approve(Asset ass) throws RepositoryResourceLifecycleException {
ass.setState(State.PUBLISHED);
}
@Override
public void cancel(Asset ass) throws RepositoryResourceLifecycleException {
ass.setState(State.DRAFT);
}
@Override
public void need_more_info(Asset ass) throws RepositoryResourceLifecycleException {
ass.setState(State.NEED_MORE_INFO);
}
},
/**
* The asset is in need_more_info state, valid transitions are to publish
*/
NEED_MORE_INFO("need_more_info") {
@Override
public void publish(Asset ass) throws RepositoryResourceLifecycleException {
ass.setState(State.AWAITING_APPROVAL);
}
},
/**
* The asset is in published state, there are no valid transitions from this state.
*/
PUBLISHED("published") {
@Override
public void unpublish(Asset ass) throws RepositoryResourceLifecycleException {
ass.setState(State.DRAFT);
}
};
// Default implementation of the StateMachine methods, which all throw exceptions
@Override
public void publish(Asset ass) throws RepositoryResourceLifecycleException {
throw new RepositoryResourceLifecycleException(this, StateAction.PUBLISH);
}
@Override
public void approve(Asset ass) throws RepositoryResourceLifecycleException {
throw new RepositoryResourceLifecycleException(this, StateAction.APPROVE);
}
@Override
public void cancel(Asset ass) throws RepositoryResourceLifecycleException {
throw new RepositoryResourceLifecycleException(this, StateAction.CANCEL);
}
@Override
public void need_more_info(Asset ass) throws RepositoryResourceLifecycleException {
throw new RepositoryResourceLifecycleException(this, StateAction.NEED_MORE_INFO);
}
@Override
public void unpublish(Asset ass) throws RepositoryResourceLifecycleException {
throw new RepositoryResourceLifecycleException(this, StateAction.UNPUBLISH);
}
private final String state;
private State(String state) {
this.state = state;
}
public String getValue() {
return state;
}
public static State forValue(String value) {
for (State state : State.values()) {
if (state.getValue().equals(value)) {
return state;
}
}
return null;
}
}
private interface PerformAction {
public void performAction(Asset asset) throws RepositoryResourceLifecycleException;
}
/**
* The possible actions to change the state of an asset.
*/
public enum StateAction implements PerformAction {
PUBLISH("publish") {
@Override
public void performAction(Asset asset) throws RepositoryResourceLifecycleException {
asset.getState().publish(asset);
}
},
APPROVE("approve") {
@Override
public void performAction(Asset asset) throws RepositoryResourceLifecycleException {
asset.getState().approve(asset);
}
},
CANCEL("cancel") {
@Override
public void performAction(Asset asset) throws RepositoryResourceLifecycleException {
asset.getState().cancel(asset);
}
},
NEED_MORE_INFO("need_more_info") {
@Override
public void performAction(Asset asset) throws RepositoryResourceLifecycleException {
asset.getState().need_more_info(asset);
}
},
UNPUBLISH("unpublish") {
@Override
public void performAction(Asset asset) throws RepositoryResourceLifecycleException {
asset.getState().unpublish(asset);
}
};
String action;
private StateAction(String action) {
this.action = action;
}
public String getValue() {
return action;
}
public static StateAction forValue(String value) {
for (StateAction stateAction : StateAction.values()) {
if (stateAction.getValue().equals(value)) {
return stateAction;
}
}
return null;
}
}
}