001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.util.server.control;
018
019import com.echothree.util.common.command.BaseEditResult;
020import com.echothree.util.common.command.BaseResult;
021import com.echothree.util.common.command.EditMode;
022import com.echothree.util.common.form.BaseEdit;
023import com.echothree.util.common.form.BaseSpec;
024import com.echothree.util.common.message.ExecutionErrors;
025import com.echothree.util.common.validation.FieldDefinition;
026import com.echothree.util.server.persistence.BaseEntity;
027import com.echothree.util.server.persistence.EntityPermission;
028import java.util.List;
029
030public abstract class BaseAbstractEditCommand<S extends BaseSpec, E extends BaseEdit, R extends BaseEditResult<E>, BE extends BaseEntity, LE extends BaseEntity>
031        extends BaseEditCommand<S, E> {
032    
033    protected BaseAbstractEditCommand(CommandSecurityDefinition commandSecurityDefinition, List<FieldDefinition> specFieldDefinitions,
034            List<FieldDefinition> editFieldDefinitions) {
035        super(commandSecurityDefinition, specFieldDefinitions, editFieldDefinitions);
036    }
037    
038    protected abstract R getResult();
039    
040    protected abstract E getEdit();
041    
042    protected EntityPermission editModeToEntityPermission(EditMode editMode) {
043        EntityPermission entityPermission;
044        
045        if(editMode == EditMode.UPDATE) {
046            entityPermission = EntityPermission.READ_WRITE;
047        } else {
048            // EditMode.LOCK & EditMode.ABANDON
049            entityPermission = EntityPermission.READ_ONLY;
050        }
051        
052        return entityPermission;
053    }
054    
055    protected abstract BE getEntity(R result);
056    
057    protected abstract LE getLockEntity(BE baseEntity);
058    
059    protected abstract void fillInResult(R result, BE baseEntity);
060    
061    private void fillInResult(R result, BE baseEntity, LE lockEntity) {
062        fillInResult(result, baseEntity);
063        
064        if(editMode.equals(EditMode.LOCK) || editMode.equals(EditMode.UPDATE)) {
065            result.setEntityLock(getEntityLockTransfer(lockEntity));
066        }
067    }
068    
069    protected abstract void doLock(E edit, BE baseEntity);
070    
071    // This check is called for both EditMode.LOCK and EditMode.UPDATE.
072    protected void canEdit(BE baseEntity) {
073        // Nothing.
074    }
075    
076    // This check is called for EditMode.UPDATE, after canEdit(...) is called.
077    protected void canUpdate(BE baseEntity) {
078        // Nothing.
079    }
080    
081    protected abstract void doUpdate(BE baseEntity);
082    
083    @Override
084    protected BaseResult execute() {
085        var result = getResult();
086        var baseEntity = getEntity(result);
087        
088        // getEntity(...) may set both SecurityMessages and ExecutionErrors.
089        if(!hasSecurityMessages() && !hasExecutionErrors()) {
090            var lockEntity = getLockEntity(baseEntity);
091
092            switch(editMode) {
093                case LOCK -> {
094                    canEdit(baseEntity);
095
096                    // canEdit(...) may set both SecurityMessages and ExecutionErrors.
097                    if(!hasSecurityMessages()) {
098                        if(!hasExecutionErrors()) {
099                            if(lockEntity(lockEntity)) {
100                                edit = getEdit();
101                                result.setEdit(edit);
102                                doLock(edit, baseEntity);
103                            } else {
104                                addExecutionError(ExecutionErrors.EntityLockFailed.name());
105                            }
106                        }
107
108                        fillInResult(result, baseEntity, lockEntity);
109                    }
110                }
111                case ABANDON -> unlockEntity(lockEntity);
112                case UPDATE -> {
113                    canEdit(baseEntity);
114
115                    // canEdit(...) may set both SecurityMessages and ExecutionErrors.
116                    if(!hasSecurityMessages()) {
117                        if(!hasExecutionErrors()) {
118                            canUpdate(baseEntity);
119
120                            if(!hasExecutionErrors()) {
121                                if(lockEntityForUpdate(lockEntity)) {
122                                    try {
123                                        doUpdate(baseEntity);
124                                    } finally {
125                                        unlockEntity(lockEntity);
126                                    }
127                                } else {
128                                    addExecutionError(ExecutionErrors.EntityLockStale.name());
129                                }
130                            }
131                        }
132
133                        if(hasExecutionErrors()) {
134                            fillInResult(result, baseEntity, lockEntity);
135                        }
136                    }
137                }
138            }
139        }
140        
141        return result;
142    }
143    
144    R errorResult = null;
145    
146    @Override
147    protected void saveResultAfterEditValidatorErrors() {
148        errorResult = getResult();
149
150        var baseEntity = getEntity(errorResult);
151        var lockEntity = getLockEntity(baseEntity);
152        
153        fillInResult(errorResult, baseEntity, lockEntity);
154    }
155    
156    @Override
157    protected BaseResult getBaseResultAfterErrors() {
158        return errorResult;
159    }
160    
161}