/*******************************************************************************
* 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.repository.strategies.writeable;
import java.util.ArrayList;
import java.util.List;
import com.ibm.ws.repository.common.enums.State;
import com.ibm.ws.repository.exceptions.RepositoryBackendException;
import com.ibm.ws.repository.exceptions.RepositoryBadDataException;
import com.ibm.ws.repository.exceptions.RepositoryResourceException;
import com.ibm.ws.repository.exceptions.RepositoryResourceValidationException;
import com.ibm.ws.repository.resources.RepositoryResource;
import com.ibm.ws.repository.resources.internal.RepositoryResourceImpl;
import com.ibm.ws.repository.resources.internal.RepositoryResourceImpl.AttachmentResourceImpl;
/**
* This strategy will check if there is a matching resource.<br>
* If the resource is equivalent, it will not upload anything.<br>
* If the resource is different then a new resource is added to the repo and the original resource is deleted<br>
* If there is no matching resource then the resource is added.<br>
* <br>
* The resource is then moved to the desired state based on the values passed to the constructor and whether a matching
* resource was found. If there is no matching resource use desiredStateIfNoMatchingFound. If there is a matching
* resource and a desiredStateIfMatchingFound state has been set then use it. If a desiredStateIfMatchingFound has not
* been set (it's null) then set the new resource's state to match the state of the matching resource found in the
* repo.
*/
public class AddThenDeleteStrategy extends AddNewStrategy {
private boolean _forceReplace;
private List<RepositoryResourceImpl> _matchingResources = null;
private List<RepositoryResource> _deletedResources = null;
/**
* Delegate to super class for states
*/
public AddThenDeleteStrategy() {}
/**
* Sets the desired state of the asset after uploading it
*
* @param desiredStateIfMatchingFound Set the resource to this state if a matching resource was found. If this
* is set to null then it will set the state to whatever state the matching resource is set to.
* @param desiredStateIfNoMatchingFound If no matching resource is found then set the state to this value
* @param forceReplace Set to true if you wish to perform a replace even if a matching resource was found.
*/
public AddThenDeleteStrategy(State desiredStateIfMatchingFound, State desiredStateIfNoMatchingFound, boolean forceReplace) {
this(desiredStateIfMatchingFound, desiredStateIfNoMatchingFound, forceReplace, null);
}
/**
* Sets the desired state of the asset after uploading it
*
* @param desiredStateIfMatchingFound Set the resource to this state if a matching resource was found. If this
* is set to null then it will set the state to whatever state the matching resource is set to.
* @param desiredStateIfNoMatchingFound If no matching resource is found then set the state to this value
* @param forceReplace Set to true if you wish to perform a replace even if a matching resource was found.
* @param matchingResource Set this if you wish to specify the resource to be replaced rather than letting
* the resource try and find a matching. This is of use in scenarios where there may be more than one matching
* resource and the caller can decide which one to use, as the resource logic will select the first matching one
* it finds
* @param deletedResources Set this if you wish to track which resources are deleted by this strategy. Deleted resources will
* be added to this list. If the list is not thread-safe then this strategy will not be thread-safe.
*/
public AddThenDeleteStrategy(State desiredStateIfMatchingFound, State desiredStateIfNoMatchingFound, boolean forceReplace,
RepositoryResourceImpl matchingResource) {
this(desiredStateIfMatchingFound, desiredStateIfNoMatchingFound, forceReplace, matchingResource, null);
}
/**
* Sets the desired state of the asset after uploading it
*
* @param desiredStateIfMatchingFound Set the resource to this state if a matching resource was found. If this
* is set to null then it will set the state to whatever state the matching resource is set to.
* @param desiredStateIfNoMatchingFound If no matching resource is found then set the state to this value
* @param forceReplace Set to true if you wish to perform a replace even if a matching resource was found.
* @param matchingResource Set this if you wish to specify the resource to be replaced rather than letting
* the resource try and find a matching. This is of use in scenarios where there may be more than one matching
* resource and the caller can decide which one to use, as the resource logic will select the first matching one
* it finds
* @param deletedResources Set this if you wish to track which resources are deleted by this strategy. Deleted resources will
* be added to this list. If the list is not thread-safe then this strategy will not be thread-safe.
*/
public AddThenDeleteStrategy(State desiredStateIfMatchingFound, State desiredStateIfNoMatchingFound, boolean forceReplace,
RepositoryResourceImpl matchingResource, List<RepositoryResource> deletedResources) {
super(desiredStateIfMatchingFound, desiredStateIfNoMatchingFound);
_forceReplace = forceReplace;
if (matchingResource != null) {
_matchingResources = new ArrayList<RepositoryResourceImpl>();
_matchingResources.add(matchingResource);
}
_deletedResources = deletedResources;
}
/**
* {@inheritDoc}
*
* @throws RepositoryResourceException
* @throws RepositoryBackendException
*/
@Override
public void uploadAsset(RepositoryResourceImpl resource, List<RepositoryResourceImpl> matchingResources) throws RepositoryBackendException, RepositoryResourceException {
boolean doUpdate = false;
boolean deleteOriginal = false;
RepositoryResourceImpl firstMatch = (matchingResources == null || matchingResources.isEmpty()) ? null : matchingResources.get(0);
// Check if we need to do an update
if (_forceReplace) {
doUpdate = true;
deleteOriginal = firstMatch == null ? false : true;
} else {
// First check assert itself for changes
switch (resource.updateRequired(firstMatch)) {
// This strategy will add a new asset instead of overwriting an existing one
// Update should fall through to add
case UPDATE:
deleteOriginal = true;
case ADD:
doUpdate = true;
break;
default:
// Nothing to do but have to include this to stop findbugs crying
}
// If the asset is the same, check attachments just in case they have changed
if (!doUpdate) {
// Then check each attachment
loop: for (AttachmentResourceImpl attachment : resource.getAttachmentImpls()) {
switch (attachment.updateRequired(firstMatch)) {
case UPDATE:
case ADD:
deleteOriginal = true;
doUpdate = true;
break loop;
default:
// Nothing to do but have to include this to stop findbugs crying
}
}
}
}
// Only do an update if needed
if (doUpdate) {
super.uploadAsset(resource, matchingResources);
// If the action was an update to an existing resource then delete the original now
if (deleteOriginal) {
for (RepositoryResourceImpl massiveResource : matchingResources) {
massiveResource.delete();
if (_deletedResources != null) {
_deletedResources.add(massiveResource);
}
}
}
} else {
// Use the asset from massive, although it's identical as far as we are concerned it
// also has the fields massive sets onto an asset when it gets uploaded, including
// the id itself.
resource.copyAsset(firstMatch);
// Also remove duplicates so there is only one asset left. NOTE starting at 1, don't delete the first one as this is what we are using
if (matchingResources.size() > 1) {
for (int i = 1; i < matchingResources.size(); i++) {
RepositoryResourceImpl res = matchingResources.get(i);
res.delete();
if (_deletedResources != null) {
_deletedResources.add(res);
}
}
}
// Finally, make sure the existing asset is in the desired state
resource.moveToState(getTargetState(firstMatch));
}
resource.refreshFromMassive();
}
@Override
public List<RepositoryResourceImpl> findMatchingResources(RepositoryResourceImpl resource) throws RepositoryResourceValidationException,
RepositoryBackendException, RepositoryBadDataException {
if (_matchingResources != null) {
return _matchingResources;
} else {
return resource.findMatchingResource();
}
}
/**
* The target state is dependent on if a matching resource is found. See {@link AddThenDeleteStrategy} class doc
*/
@Override
protected State getTargetState(RepositoryResourceImpl matchingResource) {
return calculateTargetState(matchingResource);
}
}