blob: 260e82922ae4adda499e581e54bfdc30c34ef19e [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.qpid.server.store;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.qpid.server.configuration.updater.Task;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.ConfiguredObjectFactory;
import org.apache.qpid.server.util.ServerScopedRuntimeException;
public class GenericRecoverer
{
private static final Logger LOGGER = LoggerFactory.getLogger(GenericRecoverer.class);
private final ConfiguredObject<?> _root;
public GenericRecoverer(ConfiguredObject<?> root)
{
_root = root;
}
public void recover(final List<ConfiguredObjectRecord> records, final boolean isNew)
{
_root.getTaskExecutor().run(new Task<Void, RuntimeException>()
{
@Override
public Void execute()
{
performRecover(records, isNew);
return null;
}
@Override
public String getObject()
{
return _root.toString();
}
@Override
public String getAction()
{
return "recover";
}
@Override
public String getArguments()
{
return null;
}
});
}
private void performRecover(List<ConfiguredObjectRecord> records, final boolean isNew)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Recovering the children of " + _root);
}
records = resolveDiscontinuity(records);
resolveObjects(_root, records, isNew);
}
private List<ConfiguredObjectRecord> resolveDiscontinuity(final List<ConfiguredObjectRecord> records)
{
Collection<Class<? extends ConfiguredObject>> childTypesOfRoot = _root.getModel().getChildTypes(_root.getCategoryClass());
List<ConfiguredObjectRecord> newRecords = new ArrayList<>(records.size());
for (ConfiguredObjectRecord record : records)
{
if (record.getId().equals(_root.getId()))
{
// If the parent is already in the records, we skip it, this supports partial recovery
// (required when restarting a virtualhost). In the long term, when the objects take responsibility
// for the recovery of immediate descendants only, this will disappear.
}
else if ((record.getParents() == null || record.getParents().size() == 0))
{
if (containsCategory(childTypesOfRoot, record.getType()))
{
String parentOfRootCategory = _root.getCategoryClass().getSimpleName();
Map<String, UUID> rootParents = Collections.singletonMap(parentOfRootCategory, _root.getId());
newRecords.add(new ConfiguredObjectRecordImpl(record.getId(), record.getType(), record.getAttributes(), rootParents));
}
else
{
throw new IllegalArgumentException("Recovered configured object record " + record
+ " has no recorded parents and is not a valid child type"
+ " [" + Arrays.toString(childTypesOfRoot.toArray()) + "]"
+ " for the root " + _root);
}
}
else
{
newRecords.add(record);
}
}
return newRecords;
}
private boolean containsCategory(Collection<Class<? extends ConfiguredObject>> childCategories, String categorySimpleName)
{
for (Class<? extends ConfiguredObject> child : childCategories)
{
if (child.getSimpleName().equals(categorySimpleName))
{
return true;
}
}
return false;
}
private void resolveObjects(ConfiguredObject<?> parentObject,
List<ConfiguredObjectRecord> records,
final boolean isNew)
{
ConfiguredObjectFactory factory = parentObject.getObjectFactory();
Map<UUID, ConfiguredObject<?>> resolvedObjects = new HashMap<UUID, ConfiguredObject<?>>();
resolvedObjects.put(parentObject.getId(), parentObject);
Collection<ConfiguredObjectRecord> recordsWithUnresolvedParents = new ArrayList<ConfiguredObjectRecord>(records);
Collection<UnresolvedConfiguredObject<? extends ConfiguredObject>> recordsWithUnresolvedDependencies =
new ArrayList<UnresolvedConfiguredObject<? extends ConfiguredObject>>();
boolean updatesMade;
do
{
updatesMade = false;
Iterator<ConfiguredObjectRecord> iter = recordsWithUnresolvedParents.iterator();
while (iter.hasNext())
{
ConfiguredObjectRecord record = iter.next();
Collection<ConfiguredObject<?>> parents = new ArrayList<ConfiguredObject<?>>();
boolean foundParents = true;
for (UUID parentId : record.getParents().values())
{
if (!resolvedObjects.containsKey(parentId))
{
foundParents = false;
break;
}
else
{
parents.add(resolvedObjects.get(parentId));
}
}
if (parents.size() > 1)
{
throw new IllegalStateException(String.format("Unexpected number of parents %d for record %s ", parents.size(), record));
}
if (foundParents)
{
iter.remove();
ConfiguredObject<?>[] parentArray = parents.toArray(new ConfiguredObject<?>[parents.size()]);
UnresolvedConfiguredObject<? extends ConfiguredObject> recovered = factory.recover(record, parentArray[0]);
Collection<ConfiguredObjectDependency<?>> dependencies = recovered.getUnresolvedDependencies();
if (dependencies.isEmpty())
{
updatesMade = true;
ConfiguredObject<?> resolved = recovered.resolve();
if(!isNew)
{
resolved.decryptSecrets();
}
resolvedObjects.put(resolved.getId(), resolved);
}
else
{
recordsWithUnresolvedDependencies.add(recovered);
}
}
}
Iterator<UnresolvedConfiguredObject<? extends ConfiguredObject>> unresolvedIter = recordsWithUnresolvedDependencies.iterator();
while(unresolvedIter.hasNext())
{
UnresolvedConfiguredObject<? extends ConfiguredObject> unresolvedObject = unresolvedIter.next();
Collection<ConfiguredObjectDependency<?>> dependencies =
new ArrayList<ConfiguredObjectDependency<?>>(unresolvedObject.getUnresolvedDependencies());
for(ConfiguredObjectDependency dependency : dependencies)
{
if(dependency instanceof ConfiguredObjectIdDependency)
{
UUID id = ((ConfiguredObjectIdDependency)dependency).getId();
if(resolvedObjects.containsKey(id))
{
dependency.resolve(resolvedObjects.get(id));
}
}
else if(dependency instanceof ConfiguredObjectNameDependency)
{
ConfiguredObject<?> dependentObject = null;
ConfiguredObject<?> parent = unresolvedObject.getParent();
dependentObject = parent.findConfiguredObject(dependency.getCategoryClass(), ((ConfiguredObjectNameDependency)dependency).getName());
if(dependentObject != null)
{
dependency.resolve(dependentObject);
}
}
else
{
throw new ServerScopedRuntimeException("Unknown dependency type " + dependency.getClass().getSimpleName());
}
}
if(unresolvedObject.getUnresolvedDependencies().isEmpty())
{
updatesMade = true;
unresolvedIter.remove();
ConfiguredObject<?> resolved = unresolvedObject.resolve();
if (!isNew)
{
resolved.decryptSecrets();
}
resolvedObjects.put(resolved.getId(), resolved);
}
}
} while(updatesMade && !(recordsWithUnresolvedDependencies.isEmpty() && recordsWithUnresolvedParents.isEmpty()));
if(!recordsWithUnresolvedDependencies.isEmpty())
{
throw new IllegalArgumentException("Cannot resolve some objects: " + recordsWithUnresolvedDependencies);
}
if(!recordsWithUnresolvedParents.isEmpty())
{
throw new IllegalArgumentException("Cannot resolve object because their parents cannot be found" + recordsWithUnresolvedParents);
}
}
}