/*
*
* * Copyright 2015 the original author or authors.
* *
* * 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 org.springframework.xd.dirt.module;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.xd.dirt.core.RuntimeIOException;
import org.springframework.xd.module.ModuleDefinition;
import org.springframework.xd.module.ModuleType;
import org.springframework.xd.module.SimpleModuleDefinition;
/**
* A ModuleRegistry that is configured with two delegates: a remote (source) and a local (target) registry. This registry will return
* results that exist in the remote registry but will synchronize them with the local registry. Results returned are always
* from the <em>local</em> registry (unless composed). Mutative operations go through to the remote repository though.
*
* <p>This is useful as reading
* Boot uber-jars requires local {@code java.io.File} access, but modules may reside in a remote registry (<i>e.g.</i>
* backed by HDFS). For such a case, simply use this registry as the main registry, configuring it with the {@code hdfs://} registry as
* its source and the {@code file://} one as its target.</p>
*
* @since 1.2
* @author Eric Bottard
*/
public class SynchronizingModuleRegistry implements WritableModuleRegistry {
private final WritableModuleRegistry remoteRegistry;
private final WritableModuleRegistry localRegistry;
private final StalenessCheck staleness = new MD5StalenessCheck();
private ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
public SynchronizingModuleRegistry(WritableModuleRegistry remoteRegistry, WritableModuleRegistry localRegistry) {
Assert.notNull(remoteRegistry, "remoteRegistry cannot be null");
Assert.notNull(localRegistry, "localRegistry cannot be null");
this.remoteRegistry = remoteRegistry;
this.localRegistry = localRegistry;
}
@Override
public ModuleDefinition findDefinition(String name, ModuleType moduleType) {
ModuleDefinition remoteDefinition = remoteRegistry.findDefinition(name, moduleType);
if (remoteDefinition == null || remoteDefinition.isComposed()) {
return remoteDefinition;
}
copyIfStale((SimpleModuleDefinition) remoteDefinition);
return currentLocalDefinition(remoteDefinition);
}
@Override
public List<ModuleDefinition> findDefinitions(String name) {
List<ModuleDefinition> remoteDefinitions = remoteRegistry.findDefinitions(name);
return refresh(remoteDefinitions);
}
@Override
public List<ModuleDefinition> findDefinitions(ModuleType type) {
List<ModuleDefinition> remoteDefinitions = remoteRegistry.findDefinitions(type);
return refresh(remoteDefinitions);
}
@Override
public List<ModuleDefinition> findDefinitions() {
List<ModuleDefinition> remoteDefinitions = remoteRegistry.findDefinitions();
return refresh(remoteDefinitions);
}
/**
* Return a refreshed list of definitions from the target registry.
*/
private List<ModuleDefinition> refresh(List<ModuleDefinition> remoteDefinitions) {
List<ModuleDefinition> result = new ArrayList<ModuleDefinition>(remoteDefinitions.size());
for (ModuleDefinition remoteDefinition : remoteDefinitions) {
if (remoteDefinition.isComposed()) {
result.add(remoteDefinition);
}
else {
copyIfStale((SimpleModuleDefinition) remoteDefinition);
result.add(currentLocalDefinition(remoteDefinition));
}
}
return result;
}
/**
* Checks whether the target version of the definition is stale, and if it is, triggers an update (as a deletion then
* new registration).
*/
private synchronized void copyIfStale(SimpleModuleDefinition remoteDefinition) {
ModuleDefinition currentTargetDefinition = currentLocalDefinition(remoteDefinition);
SimpleModuleDefinition targetDefinition = (SimpleModuleDefinition) currentTargetDefinition;
if (staleness.isStale(targetDefinition, remoteDefinition)) {
InputStream is = null;
try {
is = resolver.getResource(remoteDefinition.getLocation()).getInputStream();
}
catch (IOException e) {
throw new RuntimeIOException("Error while copying module", e);
}
localRegistry.delete(remoteDefinition);
UploadedModuleDefinition definitionToInstall = new UploadedModuleDefinition(remoteDefinition.getName(), remoteDefinition.getType(), is);
localRegistry.registerNew(definitionToInstall);
}
}
/**
* Returns the current version corresponding to a given module definition, as seen by the local registry.
*/
private ModuleDefinition currentLocalDefinition(ModuleDefinition remoteDefinition) {
return localRegistry.findDefinition(remoteDefinition.getName(), remoteDefinition.getType());
}
@Override
public boolean delete(ModuleDefinition definition) {
return remoteRegistry.delete(definition);
}
@Override
public boolean registerNew(ModuleDefinition definition) {
return remoteRegistry.registerNew(definition);
}
}