mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 16:05:44 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -52,7 +52,6 @@ import sonia.scm.plugin.ExtensionProcessor;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.schedule.Scheduler;
|
||||
import sonia.scm.upgrade.UpgradeManager;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
@@ -102,17 +101,8 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
||||
}
|
||||
|
||||
private void beforeInjectorCreation() {
|
||||
upgradeIfNecessary();
|
||||
}
|
||||
|
||||
private void upgradeIfNecessary() {
|
||||
if (!hasStartupErrors()) {
|
||||
UpgradeManager upgradeHandler = new UpgradeManager();
|
||||
|
||||
upgradeHandler.doUpgrade();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasStartupErrors() {
|
||||
return SCMContext.getContext().getStartupError() != null;
|
||||
}
|
||||
@@ -132,9 +122,6 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
||||
List<Module> moduleList = Lists.newArrayList();
|
||||
|
||||
moduleList.add(new ResteasyModule());
|
||||
moduleList.add(new ScmInitializerModule());
|
||||
moduleList.add(new ScmEventBusModule());
|
||||
moduleList.add(new EagerSingletonModule());
|
||||
moduleList.add(ShiroWebModule.guiceFilterModule());
|
||||
moduleList.add(new WebElementModule(pluginLoader));
|
||||
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
|
||||
|
||||
@@ -53,8 +53,6 @@ import sonia.scm.group.GroupDisplayManager;
|
||||
import sonia.scm.group.GroupManager;
|
||||
import sonia.scm.group.GroupManagerProvider;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.net.SSLContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.ContentTransformer;
|
||||
@@ -63,7 +61,6 @@ import sonia.scm.net.ahc.JsonContentTransformer;
|
||||
import sonia.scm.net.ahc.XmlContentTransformer;
|
||||
import sonia.scm.plugin.DefaultPluginLoader;
|
||||
import sonia.scm.plugin.DefaultPluginManager;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.repository.DefaultRepositoryManager;
|
||||
import sonia.scm.repository.DefaultRepositoryProvider;
|
||||
@@ -87,23 +84,11 @@ import sonia.scm.schedule.QuartzScheduler;
|
||||
import sonia.scm.schedule.Scheduler;
|
||||
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||
import sonia.scm.security.AuthorizationChangedEventProducer;
|
||||
import sonia.scm.security.CipherHandler;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.security.ConfigurableLoginAttemptHandler;
|
||||
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
|
||||
import sonia.scm.security.DefaultKeyGenerator;
|
||||
import sonia.scm.security.DefaultSecuritySystem;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
import sonia.scm.security.LoginAttemptHandler;
|
||||
import sonia.scm.security.SecuritySystem;
|
||||
import sonia.scm.store.BlobStoreFactory;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.store.DataStoreFactory;
|
||||
import sonia.scm.store.FileBlobStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
import sonia.scm.store.JAXBDataStoreFactory;
|
||||
import sonia.scm.template.MustacheTemplateEngine;
|
||||
import sonia.scm.template.TemplateEngine;
|
||||
import sonia.scm.template.TemplateEngineFactory;
|
||||
|
||||
@@ -63,9 +63,7 @@ public class IndexDtoGenerator extends HalAppenderMapper {
|
||||
|
||||
builder.single(link("repositoryTypes", resourceLinks.repositoryTypeCollection().self()));
|
||||
builder.single(link("namespaceStrategies", resourceLinks.namespaceStrategies().self()));
|
||||
if (RepositoryRolePermissions.read().isPermitted()) {
|
||||
builder.single(link("repositoryRoles", resourceLinks.repositoryRoleCollection().self()));
|
||||
}
|
||||
builder.single(link("repositoryRoles", resourceLinks.repositoryRoleCollection().self()));
|
||||
} else {
|
||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||
}
|
||||
|
||||
@@ -7,12 +7,15 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PermissionListDto extends HalRepresentation {
|
||||
|
||||
@NotNull
|
||||
private String[] permissions;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,7 @@ public class RepositoryRoleCollectionToDtoMapper extends BasicCollectionToDtoMap
|
||||
}
|
||||
|
||||
Optional<String> createCreateLink() {
|
||||
return RepositoryRolePermissions.modify().isPermitted() ? of(resourceLinks.repositoryRoleCollection().create()): empty();
|
||||
return RepositoryRolePermissions.write().isPermitted() ? of(resourceLinks.repositoryRoleCollection().create()): empty();
|
||||
}
|
||||
|
||||
String createSelfLink() {
|
||||
|
||||
@@ -27,7 +27,7 @@ public abstract class RepositoryRoleToRepositoryRoleDtoMapper extends BaseMapper
|
||||
@ObjectFactory
|
||||
RepositoryRoleDto createDto(RepositoryRole repositoryRole) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repositoryRole().self(repositoryRole.getName()));
|
||||
if (!"system".equals(repositoryRole.getType()) && RepositoryRolePermissions.modify().isPermitted()) {
|
||||
if (!"system".equals(repositoryRole.getType()) && RepositoryRolePermissions.write().isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.repositoryRole().delete(repositoryRole.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.repositoryRole().update(repositoryRole.getName())));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import sonia.scm.security.PermissionDescriptor;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
@@ -69,7 +70,7 @@ public class UserPermissionResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response overwritePermissions(@PathParam("id") String id, PermissionListDto newPermissions) {
|
||||
public Response overwritePermissions(@PathParam("id") String id, @Valid PermissionListDto newPermissions) {
|
||||
Collection<PermissionDescriptor> permissionDescriptors = Arrays.stream(newPermissions.getPermissions())
|
||||
.map(PermissionDescriptor::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -39,14 +39,18 @@ import com.google.inject.Module;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.EagerSingletonModule;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.ScmContextListener;
|
||||
import sonia.scm.ScmEventBusModule;
|
||||
import sonia.scm.ScmInitializerModule;
|
||||
import sonia.scm.Stage;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.plugin.DefaultPluginLoader;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.PluginException;
|
||||
import sonia.scm.plugin.PluginLoadException;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.PluginsInternal;
|
||||
import sonia.scm.plugin.SmpArchive;
|
||||
@@ -134,6 +138,19 @@ public class BootstrapContextListener implements ServletContextListener {
|
||||
|
||||
File pluginDirectory = getPluginDirectory();
|
||||
|
||||
createContextListener(pluginDirectory);
|
||||
|
||||
contextListener.contextInitialized(sce);
|
||||
|
||||
// register for restart events
|
||||
if (!registered && (SCMContext.getContext().getStage() == Stage.DEVELOPMENT)) {
|
||||
logger.info("register for restart events");
|
||||
ScmEventBus.getInstance().register(this);
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void createContextListener(File pluginDirectory) {
|
||||
try {
|
||||
if (!isCorePluginExtractionDisabled()) {
|
||||
extractCorePlugins(context, pluginDirectory);
|
||||
@@ -145,12 +162,9 @@ public class BootstrapContextListener implements ServletContextListener {
|
||||
|
||||
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl, pluginDirectory.toPath());
|
||||
|
||||
DefaultPluginLoader pluginLoader = new DefaultPluginLoader(context, cl, plugins);
|
||||
PluginLoader pluginLoader = new DefaultPluginLoader(context, cl, plugins);
|
||||
|
||||
Module scmContextListenerModule = new ScmContextListenerModule();
|
||||
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
|
||||
|
||||
Injector bootstrapInjector = Guice.createInjector(bootstrapModule, scmContextListenerModule);
|
||||
Injector bootstrapInjector = createBootstrapInjector(pluginLoader);
|
||||
|
||||
processUpdates(pluginLoader, bootstrapInjector);
|
||||
|
||||
@@ -158,19 +172,25 @@ public class BootstrapContextListener implements ServletContextListener {
|
||||
} catch (IOException ex) {
|
||||
throw new PluginLoadException("could not load plugins", ex);
|
||||
}
|
||||
|
||||
contextListener.contextInitialized(sce);
|
||||
|
||||
// register for restart events
|
||||
if (!registered
|
||||
&& (SCMContext.getContext().getStage() == Stage.DEVELOPMENT)) {
|
||||
logger.info("register for restart events");
|
||||
ScmEventBus.getInstance().register(this);
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void processUpdates(DefaultPluginLoader pluginLoader, Injector bootstrapInjector) {
|
||||
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
|
||||
Module scmContextListenerModule = new ScmContextListenerModule();
|
||||
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
|
||||
ScmInitializerModule scmInitializerModule = new ScmInitializerModule();
|
||||
EagerSingletonModule eagerSingletonModule = new EagerSingletonModule();
|
||||
ScmEventBusModule scmEventBusModule = new ScmEventBusModule();
|
||||
|
||||
return Guice.createInjector(
|
||||
bootstrapModule,
|
||||
scmContextListenerModule,
|
||||
scmEventBusModule,
|
||||
scmInitializerModule,
|
||||
eagerSingletonModule
|
||||
);
|
||||
}
|
||||
|
||||
private void processUpdates(PluginLoader pluginLoader, Injector bootstrapInjector) {
|
||||
Injector updateInjector = bootstrapInjector.createChildInjector(new UpdateStepModule(pluginLoader));
|
||||
|
||||
UpdateEngine updateEngine = updateInjector.getInstance(UpdateEngine.class);
|
||||
@@ -390,7 +410,6 @@ public class BootstrapContextListener implements ServletContextListener {
|
||||
private static class ScmContextListenerModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
||||
install(new FactoryModuleBuilder().build(ScmContextListener.Factory.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import sonia.scm.SCMContext;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.plugin.DefaultPluginLoader;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
@@ -33,7 +32,7 @@ public class BootstrapModule extends AbstractModule {
|
||||
private final ClassOverrides overrides;
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
BootstrapModule(DefaultPluginLoader pluginLoader) {
|
||||
BootstrapModule(PluginLoader pluginLoader) {
|
||||
this.overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
|
||||
this.pluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
|
||||
|
||||
return managerDaoAdapter.create(
|
||||
repositoryRole,
|
||||
RepositoryRolePermissions::modify,
|
||||
RepositoryRolePermissions::write,
|
||||
newRepositoryRole -> fireEvent(HandlerEventType.BEFORE_CREATE, newRepositoryRole),
|
||||
newRepositoryRole -> fireEvent(HandlerEventType.CREATE, newRepositoryRole)
|
||||
);
|
||||
@@ -100,7 +100,7 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
|
||||
logger.info("delete repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType());
|
||||
managerDaoAdapter.delete(
|
||||
repositoryRole,
|
||||
RepositoryRolePermissions::modify,
|
||||
RepositoryRolePermissions::write,
|
||||
toDelete -> fireEvent(HandlerEventType.BEFORE_DELETE, toDelete),
|
||||
toDelete -> fireEvent(HandlerEventType.DELETE, toDelete)
|
||||
);
|
||||
@@ -116,7 +116,7 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
|
||||
logger.info("modify repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType());
|
||||
managerDaoAdapter.modify(
|
||||
repositoryRole,
|
||||
x -> RepositoryRolePermissions.modify(),
|
||||
x -> RepositoryRolePermissions.write(),
|
||||
notModified -> fireEvent(HandlerEventType.BEFORE_MODIFY, repositoryRole, notModified),
|
||||
notModified -> fireEvent(HandlerEventType.MODIFY, repositoryRole, notModified));
|
||||
}
|
||||
@@ -125,7 +125,6 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
|
||||
public void refresh(RepositoryRole repositoryRole) {
|
||||
logger.info("refresh repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType());
|
||||
|
||||
RepositoryRolePermissions.read().check();
|
||||
RepositoryRole fresh = repositoryRoleDAO.get(repositoryRole.getName());
|
||||
|
||||
if (fresh == null) {
|
||||
@@ -135,8 +134,6 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
|
||||
|
||||
@Override
|
||||
public RepositoryRole get(String id) {
|
||||
RepositoryRolePermissions.read().check();
|
||||
|
||||
return findSystemRole(id).orElse(findCustomRole(id));
|
||||
}
|
||||
|
||||
@@ -168,9 +165,6 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
|
||||
public List<RepositoryRole> getAll() {
|
||||
List<RepositoryRole> repositoryRoles = new ArrayList<>();
|
||||
|
||||
if (!RepositoryRolePermissions.read().isPermitted()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
for (RepositoryRole repositoryRole : repositoryPermissionProvider.availableRoles()) {
|
||||
repositoryRoles.add(repositoryRole.clone());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package sonia.scm.update.group;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
public class XmlGroupV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlGroupV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlGroupDAO groupDAO;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@Inject
|
||||
public XmlGroupV1UpdateStep(
|
||||
SCMContextProvider contextProvider,
|
||||
XmlGroupDAO groupDAO,
|
||||
ConfigurationEntryStoreFactory configurationEntryStoreFactory
|
||||
) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.groupDAO = groupDAO;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("group-properties-v1")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
Optional<Path> v1GroupsFile = determineV1File();
|
||||
if (!v1GroupsFile.isPresent()) {
|
||||
LOG.info("no v1 file for groups found");
|
||||
return;
|
||||
}
|
||||
XmlGroupV1UpdateStep.V1GroupDatabase v1Database = readV1Database(v1GroupsFile.get());
|
||||
if (v1Database.groupList != null && v1Database.groupList.groups != null) {
|
||||
v1Database.groupList.groups.forEach(this::update);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.group.xml";
|
||||
}
|
||||
|
||||
private void update(V1Group v1Group) {
|
||||
LOG.debug("updating group {}", v1Group.name);
|
||||
Group group = new Group(
|
||||
v1Group.type,
|
||||
v1Group.name,
|
||||
v1Group.members);
|
||||
group.setDescription(v1Group.description);
|
||||
group.setCreationDate(v1Group.creationDate);
|
||||
group.setLastModified(v1Group.lastModified);
|
||||
groupDAO.add(group);
|
||||
|
||||
propertyStore.put(v1Group.name, v1Group.properties);
|
||||
}
|
||||
|
||||
private XmlGroupV1UpdateStep.V1GroupDatabase readV1Database(Path v1GroupsFile) throws JAXBException {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlGroupV1UpdateStep.V1GroupDatabase.class);
|
||||
return (XmlGroupV1UpdateStep.V1GroupDatabase) jaxbContext.createUnmarshaller().unmarshal(v1GroupsFile.toFile());
|
||||
}
|
||||
|
||||
private Optional<Path> determineV1File() {
|
||||
Path existingGroupsFile = resolveConfigFile("groups");
|
||||
Path groupsV1File = resolveConfigFile("groupsV1");
|
||||
if (existingGroupsFile.toFile().exists()) {
|
||||
try {
|
||||
Files.move(existingGroupsFile, groupsV1File);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move old groups file to " + groupsV1File.toAbsolutePath());
|
||||
}
|
||||
LOG.info("moved old groups file to {}", groupsV1File.toAbsolutePath());
|
||||
return of(groupsV1File);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private Path resolveConfigFile(String name) {
|
||||
return contextProvider
|
||||
.resolve(
|
||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(name + StoreConstants.FILE_EXTENSION)
|
||||
);
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "group")
|
||||
private static class V1Group {
|
||||
private V1Properties properties;
|
||||
private long creationDate;
|
||||
private String description;
|
||||
private Long lastModified;
|
||||
private String name;
|
||||
private String type;
|
||||
@XmlElement(name = "members")
|
||||
private List<String> members;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1Group{" +
|
||||
"properties=" + properties +
|
||||
", creationDate=" + creationDate + '\'' +
|
||||
", description=" + description + '\'' +
|
||||
", lastModified=" + lastModified + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class GroupList {
|
||||
@XmlElement(name = "group")
|
||||
private List<V1Group> groups;
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "group-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class V1GroupDatabase {
|
||||
private long creationTime;
|
||||
private Long lastModified;
|
||||
@XmlElement(name = "groups")
|
||||
private XmlGroupV1UpdateStep.GroupList groupList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.update.properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "properties")
|
||||
public class V1Properties {
|
||||
@XmlElement(name = "item")
|
||||
private List<V1Property> properties;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package sonia.scm.update.properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class V1Property {
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
abstract class BaseMigrationStrategy implements MigrationStrategy.Instance {
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
|
||||
BaseMigrationStrategy(SCMContextProvider contextProvider) {
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
Path getSourceDataPath(String name, String type) {
|
||||
return Arrays.stream(name.split("/"))
|
||||
.reduce(getTypeDependentPath(type), (path, namePart) -> path.resolve(namePart), (p1, p2) -> p1);
|
||||
}
|
||||
|
||||
Path getTypeDependentPath(String type) {
|
||||
return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type);
|
||||
}
|
||||
|
||||
Stream<Path> listSourceDirectory(Path sourceDirectory) {
|
||||
try {
|
||||
return Files.list(sourceDirectory);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not read original directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
void createDataDirectory(Path target) {
|
||||
try {
|
||||
Files.createDirectories(target);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not create data directory " + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
void moveFile(Path sourceFile, Path targetFile) {
|
||||
try {
|
||||
Files.move(sourceFile, targetFile);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move data file from " + sourceFile + " to " + targetFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
void copyFile(Path sourceFile, Path targetFile) {
|
||||
try {
|
||||
Files.copy(sourceFile, targetFile);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not copy original file from " + sourceFile + " to " + targetFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryDirectoryHandler;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class CopyMigrationStrategy extends BaseMigrationStrategy {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public CopyMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
|
||||
super(contextProvider);
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path migrate(String id, String name, String type) {
|
||||
Path repositoryBasePath = locationResolver.forClass(Path.class).getLocation(id);
|
||||
Path targetDataPath = repositoryBasePath
|
||||
.resolve(RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
Path sourceDataPath = getSourceDataPath(name, type);
|
||||
copyData(sourceDataPath, targetDataPath);
|
||||
return repositoryBasePath;
|
||||
}
|
||||
|
||||
private void copyData(Path sourceDirectory, Path targetDirectory) {
|
||||
createDataDirectory(targetDirectory);
|
||||
listSourceDirectory(sourceDirectory).forEach(
|
||||
sourceFile -> {
|
||||
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
|
||||
if (Files.isDirectory(sourceFile)) {
|
||||
copyData(sourceFile, targetFile);
|
||||
} else {
|
||||
copyFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryDirectoryHandler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class InlineMigrationStrategy extends BaseMigrationStrategy {
|
||||
|
||||
@Inject
|
||||
public InlineMigrationStrategy(SCMContextProvider contextProvider) {
|
||||
super(contextProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path migrate(String id, String name, String type) {
|
||||
Path repositoryBasePath = getSourceDataPath(name, type);
|
||||
Path targetDataPath = repositoryBasePath
|
||||
.resolve(RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
moveData(repositoryBasePath, targetDataPath);
|
||||
return repositoryBasePath;
|
||||
}
|
||||
|
||||
private void moveData(Path sourceDirectory, Path targetDirectory) {
|
||||
createDataDirectory(targetDirectory);
|
||||
listSourceDirectory(sourceDirectory)
|
||||
.filter(sourceFile -> !targetDirectory.equals(sourceFile))
|
||||
.forEach(
|
||||
sourceFile -> {
|
||||
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
|
||||
if (Files.isDirectory(sourceFile)) {
|
||||
moveData(sourceFile, targetFile);
|
||||
} else {
|
||||
moveFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.repository.update;
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -0,0 +1,26 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
enum MigrationStrategy {
|
||||
|
||||
COPY(CopyMigrationStrategy.class),
|
||||
MOVE(MoveMigrationStrategy.class),
|
||||
INLINE(InlineMigrationStrategy.class);
|
||||
|
||||
private Class<? extends Instance> implementationClass;
|
||||
|
||||
MigrationStrategy(Class<? extends Instance> implementationClass) {
|
||||
this.implementationClass = implementationClass;
|
||||
}
|
||||
|
||||
Instance from(Injector injector) {
|
||||
return injector.getInstance(implementationClass);
|
||||
}
|
||||
|
||||
interface Instance {
|
||||
Path migrate(String id, String name, String type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class MigrationStrategyDao {
|
||||
|
||||
private final RepositoryMigrationPlan plan;
|
||||
private final ConfigurationStore<RepositoryMigrationPlan> store;
|
||||
|
||||
@Inject
|
||||
public MigrationStrategyDao(ConfigurationStoreFactory storeFactory) {
|
||||
store = storeFactory.withType(RepositoryMigrationPlan.class).withName("migration-plan").build();
|
||||
this.plan = store.getOptional().orElse(new RepositoryMigrationPlan());
|
||||
}
|
||||
|
||||
public Optional<MigrationStrategy> get(String id) {
|
||||
return plan.get(id);
|
||||
}
|
||||
|
||||
public void set(String repositoryId, MigrationStrategy strategy) {
|
||||
plan.set(repositoryId, strategy);
|
||||
store.set(plan);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryDirectoryHandler;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class MoveMigrationStrategy extends BaseMigrationStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MoveMigrationStrategy.class);
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public MoveMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
|
||||
super(contextProvider);
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path migrate(String id, String name, String type) {
|
||||
Path repositoryBasePath = locationResolver.forClass(Path.class).getLocation(id);
|
||||
Path targetDataPath = repositoryBasePath
|
||||
.resolve(RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
Path sourceDataPath = getSourceDataPath(name, type);
|
||||
moveData(sourceDataPath, targetDataPath);
|
||||
deleteOldDataDir(getTypeDependentPath(type), name);
|
||||
return repositoryBasePath;
|
||||
}
|
||||
|
||||
private void deleteOldDataDir(Path rootPath, String name) {
|
||||
delete(rootPath, asList(name.split("/")));
|
||||
}
|
||||
|
||||
private void delete(Path rootPath, List<String> directories) {
|
||||
if (directories.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Path directory = rootPath.resolve(directories.get(0));
|
||||
delete(directory, directories.subList(1, directories.size()));
|
||||
try {
|
||||
Files.deleteIfExists(directory);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not delete source repository directory {}", directory);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveData(Path sourceDirectory, Path targetDirectory) {
|
||||
createDataDirectory(targetDirectory);
|
||||
listSourceDirectory(sourceDirectory).forEach(
|
||||
sourceFile -> {
|
||||
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
|
||||
if (Files.isDirectory(sourceFile)) {
|
||||
moveData(sourceFile, targetFile);
|
||||
} else {
|
||||
moveFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
);
|
||||
try {
|
||||
Files.delete(sourceDirectory);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not delete source repository directory {}", sourceDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "repository-migration")
|
||||
class RepositoryMigrationPlan {
|
||||
|
||||
private List<RepositoryEntry> entries;
|
||||
|
||||
RepositoryMigrationPlan() {
|
||||
this(new RepositoryEntry[0]);
|
||||
}
|
||||
|
||||
RepositoryMigrationPlan(RepositoryEntry... entries) {
|
||||
this.entries = new ArrayList<>(asList(entries));
|
||||
}
|
||||
|
||||
Optional<MigrationStrategy> get(String repositoryId) {
|
||||
return findEntry(repositoryId)
|
||||
.map(RepositoryEntry::getDataMigrationStrategy);
|
||||
}
|
||||
|
||||
public void set(String repositoryId, MigrationStrategy strategy) {
|
||||
Optional<RepositoryEntry> entry = findEntry(repositoryId);
|
||||
if (entry.isPresent()) {
|
||||
entry.get().setStrategy(strategy);
|
||||
} else {
|
||||
entries.add(new RepositoryEntry(repositoryId, strategy));
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<RepositoryEntry> findEntry(String repositoryId) {
|
||||
return entries.stream()
|
||||
.filter(repositoryEntry -> repositoryId.equals(repositoryEntry.repositoryId))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "entries")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
static class RepositoryEntry {
|
||||
|
||||
private String repositoryId;
|
||||
private MigrationStrategy dataMigrationStrategy;
|
||||
|
||||
RepositoryEntry() {
|
||||
}
|
||||
|
||||
RepositoryEntry(String repositoryId, MigrationStrategy dataMigrationStrategy) {
|
||||
this.repositoryId = repositoryId;
|
||||
this.dataMigrationStrategy = dataMigrationStrategy;
|
||||
}
|
||||
|
||||
public MigrationStrategy getDataMigrationStrategy() {
|
||||
return dataMigrationStrategy;
|
||||
}
|
||||
|
||||
private void setStrategy(MigrationStrategy strategy) {
|
||||
this.dataMigrationStrategy = strategy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.repository.update;
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
public class RepositoryUpdates {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
/**
|
||||
* Moves an existing <code>repositories.xml</code> file to <code>repository-paths.xml</code>.
|
||||
* Note that this has to run <em>after</em> an old v1 repository database has been migrated to v2
|
||||
* (see {@link XmlRepositoryV1UpdateStep}).
|
||||
*/
|
||||
@Extension
|
||||
public class XmlRepositoryFileNameUpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlRepositoryFileNameUpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlRepositoryDAO repositoryDAO;
|
||||
|
||||
@Inject
|
||||
public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO repositoryDAO) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.repositoryDAO = repositoryDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws IOException {
|
||||
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||
Path oldRepositoriesFile = configDir.resolve("repositories.xml");
|
||||
Path newRepositoryPathsFile = configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + StoreConstants.FILE_EXTENSION);
|
||||
if (Files.exists(oldRepositoriesFile)) {
|
||||
LOG.info("moving old repositories database files to repository-paths file");
|
||||
Files.move(oldRepositoriesFile, newRepositoryPathsFile);
|
||||
repositoryDAO.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.repository.xml";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
/**
|
||||
* Migrates SCM-Manager v1 repository data structure to SCM-Manager v2 data structure.
|
||||
* That is:
|
||||
* <ul>
|
||||
* <li>The old <code>repositories.xml</code> file is read</li>
|
||||
* <li>For each repository in this database,
|
||||
* <ul>
|
||||
* <li>a new entry in the new <code>repository-paths.xml</code> database is written,</li>
|
||||
* <li>the data directory is moved or copied to a SCM v2 consistent directory. How this is done
|
||||
* can be specified by a strategy (@see {@link MigrationStrategy}), that has to be set in
|
||||
* a database file named <code>migration-plan.xml</code></li> (to create this file, use {@link MigrationStrategyDao}),
|
||||
* and
|
||||
* <li>the new <code>metadata.xml</code> file is created.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Extension
|
||||
public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlRepositoryDAO repositoryDao;
|
||||
private final MigrationStrategyDao migrationStrategyDao;
|
||||
private final Injector injector;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@Inject
|
||||
public XmlRepositoryV1UpdateStep(
|
||||
SCMContextProvider contextProvider,
|
||||
XmlRepositoryDAO repositoryDao,
|
||||
MigrationStrategyDao migrationStrategyDao,
|
||||
Injector injector,
|
||||
ConfigurationEntryStoreFactory configurationEntryStoreFactory
|
||||
) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.repositoryDao = repositoryDao;
|
||||
this.migrationStrategyDao = migrationStrategyDao;
|
||||
this.injector = injector;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("repository-properties-v1")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.repository.xml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
if (!resolveV1File().exists()) {
|
||||
LOG.info("no v1 repositories database file found");
|
||||
return;
|
||||
}
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
|
||||
readV1Database(jaxbContext).ifPresent(
|
||||
v1Database -> {
|
||||
v1Database.repositoryList.repositories.forEach(this::readMigrationStrategy);
|
||||
v1Database.repositoryList.repositories.forEach(this::update);
|
||||
backupOldRepositoriesFile();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void backupOldRepositoriesFile() {
|
||||
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||
Path oldRepositoriesFile = configDir.resolve("repositories.xml");
|
||||
Path backupFile = configDir.resolve("repositories.xml.v1.backup");
|
||||
LOG.info("moving old repositories database files to backup file {}", backupFile);
|
||||
try {
|
||||
Files.move(oldRepositoriesFile, backupFile);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not backup old repository database file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void update(V1Repository v1Repository) {
|
||||
Path destination = handleDataDirectory(v1Repository);
|
||||
Repository repository = new Repository(
|
||||
v1Repository.id,
|
||||
v1Repository.type,
|
||||
getNamespace(v1Repository),
|
||||
getName(v1Repository),
|
||||
v1Repository.contact,
|
||||
v1Repository.description,
|
||||
createPermissions(v1Repository));
|
||||
LOG.info("creating new repository {} with id {} from old repository {} in directory {}", repository.getNamespaceAndName(), repository.getId(), v1Repository.name, destination);
|
||||
repositoryDao.add(repository, destination);
|
||||
propertyStore.put(v1Repository.id, v1Repository.properties);
|
||||
}
|
||||
|
||||
private Path handleDataDirectory(V1Repository v1Repository) {
|
||||
MigrationStrategy dataMigrationStrategy = readMigrationStrategy(v1Repository);
|
||||
return dataMigrationStrategy.from(injector).migrate(v1Repository.id, v1Repository.name, v1Repository.type);
|
||||
}
|
||||
|
||||
private MigrationStrategy readMigrationStrategy(V1Repository v1Repository) {
|
||||
return migrationStrategyDao.get(v1Repository.id)
|
||||
.orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
|
||||
}
|
||||
|
||||
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
|
||||
if (v1Repository.permissions == null) {
|
||||
return new RepositoryPermission[0];
|
||||
}
|
||||
return v1Repository.permissions
|
||||
.stream()
|
||||
.map(this::createPermission)
|
||||
.toArray(RepositoryPermission[]::new);
|
||||
}
|
||||
|
||||
private RepositoryPermission createPermission(V1Permission v1Permission) {
|
||||
LOG.info("creating permission {} for {}", v1Permission.type, v1Permission.name);
|
||||
return new RepositoryPermission(v1Permission.name, v1Permission.type, v1Permission.groupPermission);
|
||||
}
|
||||
|
||||
private String getNamespace(V1Repository v1Repository) {
|
||||
String[] nameParts = getNameParts(v1Repository.name);
|
||||
return nameParts.length > 1 ? nameParts[0] : v1Repository.type;
|
||||
}
|
||||
|
||||
private String getName(V1Repository v1Repository) {
|
||||
String[] nameParts = getNameParts(v1Repository.name);
|
||||
return nameParts.length == 1 ? nameParts[0] : concatPathElements(nameParts);
|
||||
}
|
||||
|
||||
private String concatPathElements(String[] nameParts) {
|
||||
return Arrays.stream(nameParts).skip(1).collect(Collectors.joining("_"));
|
||||
}
|
||||
|
||||
private String[] getNameParts(String v1Name) {
|
||||
return v1Name.split("/");
|
||||
}
|
||||
|
||||
private Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext) throws JAXBException {
|
||||
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
|
||||
if (unmarshal instanceof V1RepositoryDatabase) {
|
||||
return of((V1RepositoryDatabase) unmarshal);
|
||||
} else {
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
private File resolveV1File() {
|
||||
return contextProvider
|
||||
.resolve(
|
||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve("repositories" + StoreConstants.FILE_EXTENSION)
|
||||
).toFile();
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "permissions")
|
||||
private static class V1Permission {
|
||||
private boolean groupPermission;
|
||||
private String name;
|
||||
private String type;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "repositories")
|
||||
private static class V1Repository {
|
||||
private String contact;
|
||||
private long creationDate;
|
||||
private Long lastModified;
|
||||
private String description;
|
||||
private String id;
|
||||
private String name;
|
||||
private boolean isPublic;
|
||||
private boolean archived;
|
||||
private String type;
|
||||
private List<V1Permission> permissions;
|
||||
private V1Properties properties;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1Repository{" +
|
||||
", contact='" + contact + '\'' +
|
||||
", creationDate=" + creationDate +
|
||||
", lastModified=" + lastModified +
|
||||
", description='" + description + '\'' +
|
||||
", id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", isPublic=" + isPublic +
|
||||
", archived=" + archived +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class RepositoryList {
|
||||
@XmlElement(name = "repository")
|
||||
private List<V1Repository> repositories;
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "repository-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class V1RepositoryDatabase {
|
||||
private long creationTime;
|
||||
private Long lastModified;
|
||||
@XmlElement(name = "repositories")
|
||||
private RepositoryList repositoryList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package sonia.scm.update.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
public class XmlSecurityV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlSecurityV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final ConfigurationEntryStoreFactory configurationEntryStoreFactory;
|
||||
|
||||
@Inject
|
||||
public XmlSecurityV1UpdateStep(SCMContextProvider contextProvider, ConfigurationEntryStoreFactory configurationEntryStoreFactory) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
ConfigurationEntryStore<AssignedPermission> securityStore = createSecurityStore();
|
||||
|
||||
forAllAdmins(user -> createSecurityEntry(user, false, securityStore),
|
||||
group -> createSecurityEntry(group, true, securityStore));
|
||||
}
|
||||
|
||||
private void forAllAdmins(Consumer<String> userConsumer, Consumer<String> groupConsumer) throws JAXBException {
|
||||
Path configDirectory = determineConfigDirectory();
|
||||
Path existingConfigFile = configDirectory.resolve("config" + StoreConstants.FILE_EXTENSION);
|
||||
if (existingConfigFile.toFile().exists()) {
|
||||
forAllAdmins(existingConfigFile, userConsumer, groupConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
private void forAllAdmins(
|
||||
Path existingConfigFile, Consumer<String> userConsumer, Consumer<String> groupConsumer
|
||||
) throws JAXBException {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlSecurityV1UpdateStep.V1Configuration.class);
|
||||
V1Configuration v1Configuration = (V1Configuration) jaxbContext.createUnmarshaller().unmarshal(existingConfigFile.toFile());
|
||||
|
||||
ofNullable(v1Configuration.adminUsers).ifPresent(users -> forAll(users, userConsumer));
|
||||
ofNullable(v1Configuration.adminGroups).ifPresent(groups -> forAll(groups, groupConsumer));
|
||||
}
|
||||
|
||||
private void forAll(String entries, Consumer<String> consumer) {
|
||||
Arrays.stream(entries.split(",")).forEach(consumer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.security.xml";
|
||||
}
|
||||
|
||||
private void createSecurityEntry(String name, boolean group, ConfigurationEntryStore<AssignedPermission> securityStore) {
|
||||
LOG.debug("setting admin permissions for {} {}", group? "group": "user", name);
|
||||
securityStore.put(new AssignedPermission(name, group, "*"));
|
||||
}
|
||||
|
||||
private ConfigurationEntryStore<AssignedPermission> createSecurityStore() {
|
||||
return configurationEntryStoreFactory.withType(AssignedPermission.class).withName("security").build();
|
||||
}
|
||||
|
||||
private Path determineConfigDirectory() {
|
||||
return new File(contextProvider.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME).toPath();
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "scm-config")
|
||||
private static class V1Configuration {
|
||||
@XmlElement(name = "admin-users")
|
||||
private String adminUsers;
|
||||
@XmlElement(name = "admin-groups")
|
||||
private String adminGroups;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package sonia.scm.update.user;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.xml.XmlUserDAO;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
public class XmlUserV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlUserV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlUserDAO userDAO;
|
||||
private final ConfigurationEntryStoreFactory configurationEntryStoreFactory;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@Inject
|
||||
public XmlUserV1UpdateStep(SCMContextProvider contextProvider, XmlUserDAO userDAO, ConfigurationEntryStoreFactory configurationEntryStoreFactory) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.userDAO = userDAO;
|
||||
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("user-properties-v1")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
Optional<Path> v1UsersFile = determineV1File();
|
||||
if (!v1UsersFile.isPresent()) {
|
||||
LOG.info("no v1 file for users found");
|
||||
return;
|
||||
}
|
||||
XmlUserV1UpdateStep.V1UserDatabase v1Database = readV1Database(v1UsersFile.get());
|
||||
ConfigurationEntryStore<AssignedPermission> securityStore = createSecurityStore();
|
||||
v1Database.userList.users.forEach(user -> update(user, securityStore));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.user.xml";
|
||||
}
|
||||
|
||||
private void update(V1User v1User, ConfigurationEntryStore<AssignedPermission> securityStore) {
|
||||
LOG.debug("updating user {}", v1User.name);
|
||||
User user = new User(
|
||||
v1User.name,
|
||||
v1User.displayName,
|
||||
v1User.mail,
|
||||
v1User.password,
|
||||
v1User.type,
|
||||
v1User.active);
|
||||
user.setCreationDate(v1User.creationDate);
|
||||
user.setLastModified(v1User.lastModified);
|
||||
userDAO.add(user);
|
||||
|
||||
if (v1User.admin) {
|
||||
LOG.debug("setting admin permissions for user {}", v1User.name);
|
||||
securityStore.put(new AssignedPermission(v1User.name, "*"));
|
||||
}
|
||||
|
||||
propertyStore.put(v1User.name, v1User.properties);
|
||||
}
|
||||
|
||||
private XmlUserV1UpdateStep.V1UserDatabase readV1Database(Path v1UsersFile) throws JAXBException {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlUserV1UpdateStep.V1UserDatabase.class);
|
||||
return (XmlUserV1UpdateStep.V1UserDatabase) jaxbContext.createUnmarshaller().unmarshal(v1UsersFile.toFile());
|
||||
}
|
||||
|
||||
private ConfigurationEntryStore<AssignedPermission> createSecurityStore() {
|
||||
return configurationEntryStoreFactory.withType(AssignedPermission.class).withName("security").build();
|
||||
}
|
||||
|
||||
private Optional<Path> determineV1File() {
|
||||
Path existingUsersFile = resolveConfigFile("users");
|
||||
Path usersV1File = resolveConfigFile("usersV1");
|
||||
if (existingUsersFile.toFile().exists()) {
|
||||
try {
|
||||
Files.move(existingUsersFile, usersV1File);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move old users file to " + usersV1File.toAbsolutePath());
|
||||
}
|
||||
LOG.info("moved old users file to {}", usersV1File.toAbsolutePath());
|
||||
return of(usersV1File);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private Path resolveConfigFile(String name) {
|
||||
return contextProvider
|
||||
.resolve(
|
||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(name + StoreConstants.FILE_EXTENSION)
|
||||
);
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "user")
|
||||
private static class V1User {
|
||||
private V1Properties properties;
|
||||
private boolean admin;
|
||||
private long creationDate;
|
||||
private String displayName;
|
||||
private Long lastModified;
|
||||
private String mail;
|
||||
private String name;
|
||||
private String password;
|
||||
private String type;
|
||||
private boolean active;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1User{" +
|
||||
"properties=" + properties +
|
||||
", admin='" + admin + '\'' +
|
||||
", creationDate=" + creationDate + '\'' +
|
||||
", displayName=" + displayName + '\'' +
|
||||
", lastModified=" + lastModified + '\'' +
|
||||
", mail='" + mail + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
", active='" + active + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class UserList {
|
||||
@XmlElement(name = "user")
|
||||
private List<V1User> users;
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "user-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class V1UserDatabase {
|
||||
private long creationTime;
|
||||
private Long lastModified;
|
||||
@XmlElement(name = "users")
|
||||
private XmlUserV1UpdateStep.UserList userList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.upgrade;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public final class ClientDateFormatConverter
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final String SINGLECHAR_REGEX = "(^|[^%s])[%s]($|[^%s])";
|
||||
|
||||
/**
|
||||
* the logger for DateFormatConverter
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ClientDateFormatConverter.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
private ClientDateFormatConverter() {}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Documentations:
|
||||
* - Extjs: http://trac.geoext.org/browser/ext/3.4.0/docs/source/Date.html
|
||||
* - Moments: http://momentjs.com/docs/#/displaying/format
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String extjsToMoments(String value)
|
||||
{
|
||||
logger.trace(
|
||||
"try to convert extjs date format \"{}\" to moments date format", value);
|
||||
|
||||
String result = replaceDateFormatChars(value, "d", "DD");
|
||||
|
||||
result = replaceDateFormatChars(result, "D", "ddd");
|
||||
result = replaceDateFormatChars(result, "j", "D");
|
||||
result = replaceDateFormatChars(result, "l", "dddd");
|
||||
|
||||
// no replacement found for 1-7, only 0-6 found
|
||||
result = replaceDateFormatChars(result, "N", "d");
|
||||
result = replaceDateFormatChars(result, "w", "d");
|
||||
result = replaceDateFormatChars(result, "z", "DDDD");
|
||||
result = replaceDateFormatChars(result, "W", "ww");
|
||||
result = replaceDateFormatChars(result, "M", "MMM");
|
||||
result = replaceDateFormatChars(result, "F", "MMMM");
|
||||
result = replaceDateFormatChars(result, "m", "MM");
|
||||
result = replaceDateFormatChars(result, "n", "M");
|
||||
result = replaceDateFormatChars(result, "Y", "YYYY");
|
||||
result = replaceDateFormatChars(result, "o", "YYYY");
|
||||
result = replaceDateFormatChars(result, "y", "YY");
|
||||
result = replaceDateFormatChars(result, "H", "HH");
|
||||
result = replaceDateFormatChars(result, "h", "hh");
|
||||
result = replaceDateFormatChars(result, "g", "h");
|
||||
result = replaceDateFormatChars(result, "G", "H");
|
||||
result = replaceDateFormatChars(result, "i", "mm");
|
||||
result = replaceDateFormatChars(result, "s", "ss");
|
||||
result = replaceDateFormatChars(result, "O", "ZZ");
|
||||
result = replaceDateFormatChars(result, "P", "Z");
|
||||
result = replaceDateFormatChars(result, "T", "z");
|
||||
|
||||
logger.debug(
|
||||
"converted extjs date format \"{}\" to moments date format \"{}\"",
|
||||
value, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
* @param c
|
||||
* @param replacement
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String replaceDateFormatChars(String value, String c,
|
||||
String replacement)
|
||||
{
|
||||
Pattern p = Pattern.compile(String.format(SINGLECHAR_REGEX, c, c, c));
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
Matcher m = p.matcher(value);
|
||||
|
||||
while (m.find())
|
||||
{
|
||||
m.appendReplacement(buffer, "$1" + replacement + "$2");
|
||||
}
|
||||
|
||||
m.appendTail(buffer);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.upgrade;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class ClientDateFormatUpgradeHandler extends XmlUpgradeHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for ClientDateFormatUpgradeHandler
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ClientDateFormatUpgradeHandler.class);
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param homeDirectory
|
||||
* @param configDirectory
|
||||
* @param oldVersion
|
||||
* @param newVersion
|
||||
*/
|
||||
@Override
|
||||
public void doUpgrade(File homeDirectory, File configDirectory,
|
||||
Version oldVersion, Version newVersion)
|
||||
{
|
||||
if (oldVersion.isOlder("1.23"))
|
||||
{
|
||||
if (logger.isInfoEnabled())
|
||||
{
|
||||
logger.info("data format is older than 1.23, upgrade to version {}",
|
||||
SCMContext.getContext().getVersion());
|
||||
}
|
||||
|
||||
updateClientDateFormat(homeDirectory, configDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param document
|
||||
*/
|
||||
private void fixClientDateFormat(Document document)
|
||||
{
|
||||
NodeList nodes = document.getElementsByTagName("dateFormat");
|
||||
|
||||
if (nodes != null)
|
||||
{
|
||||
for (int i = 0; i < nodes.getLength(); i++)
|
||||
{
|
||||
Node node = nodes.item(i);
|
||||
String value = node.getTextContent();
|
||||
|
||||
value = ClientDateFormatConverter.extjsToMoments(value);
|
||||
node.setTextContent(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param baseDirectory
|
||||
* @param configDirectory
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void updateClientDateFormat(File baseDirectory, File configDirectory)
|
||||
{
|
||||
File configFile = new File(configDirectory, "config.xml");
|
||||
|
||||
if (configFile.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
// backup config.xml
|
||||
File backupDirectory = createBackupDirectory(baseDirectory,
|
||||
"upgrade to version {0}");
|
||||
|
||||
Files.copy(configFile, new File(backupDirectory, "config.xml"));
|
||||
|
||||
// change dateformat
|
||||
|
||||
DocumentBuilder builder =
|
||||
DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
Document document = builder.parse(configFile);
|
||||
|
||||
fixClientDateFormat(document);
|
||||
writeDocument(document, configFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("could not parse document", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.upgrade;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.version.Version;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class TimestampUpgradeHandler extends XmlUpgradeHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for TimestampUpgradeHandler
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(TimestampUpgradeHandler.class);
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param homeDirectory
|
||||
* @param configDirectory
|
||||
* @param oldVersion
|
||||
* @param newVersion
|
||||
*/
|
||||
@Override
|
||||
public void doUpgrade(File homeDirectory, File configDirectory,
|
||||
Version oldVersion, Version newVersion)
|
||||
{
|
||||
if (oldVersion.isOlder("1.2"))
|
||||
{
|
||||
if (logger.isInfoEnabled())
|
||||
{
|
||||
logger.info("data format is older than 1.2, upgrade to version {}",
|
||||
SCMContext.getContext().getVersion());
|
||||
}
|
||||
|
||||
fixDate(homeDirectory, configDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String convertDate(String value)
|
||||
{
|
||||
if (!Strings.isNullOrEmpty(value))
|
||||
{
|
||||
try
|
||||
{
|
||||
Date date = Util.parseDate(value);
|
||||
|
||||
if (date != null)
|
||||
{
|
||||
value = Long.toString(date.getTime());
|
||||
}
|
||||
}
|
||||
catch (ParseException ex)
|
||||
{
|
||||
logger.warn("could not parse date", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param baseDirectory
|
||||
* @param configDirectory
|
||||
*/
|
||||
private void fixDate(File baseDirectory, File configDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
DocumentBuilder builder =
|
||||
DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
File backupDirectory = createBackupDirectory(baseDirectory,
|
||||
"upgrade to version {0}");
|
||||
|
||||
fixDate(builder, configDirectory, backupDirectory, "users.xml");
|
||||
fixDate(builder, configDirectory, backupDirectory, "groups.xml");
|
||||
fixDate(builder, configDirectory, backupDirectory, "repositories.xml");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("could not parse document", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param builder
|
||||
* @param configDirectory
|
||||
* @param backupDirectory
|
||||
* @param filename
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws SAXException
|
||||
* @throws TransformerConfigurationException
|
||||
* @throws TransformerException
|
||||
*/
|
||||
private void fixDate(DocumentBuilder builder, File configDirectory,
|
||||
File backupDirectory, String filename)
|
||||
throws SAXException, IOException, TransformerConfigurationException,
|
||||
TransformerException
|
||||
{
|
||||
File configFile = new File(configDirectory, filename);
|
||||
File backupFile = new File(backupDirectory, filename);
|
||||
|
||||
IOUtil.copy(configFile, backupFile);
|
||||
|
||||
if (configFile.exists())
|
||||
{
|
||||
if (logger.isInfoEnabled())
|
||||
{
|
||||
logger.info("fix date elements of {}", configFile.getPath());
|
||||
}
|
||||
|
||||
Document document = builder.parse(configFile);
|
||||
|
||||
fixDate(document, "lastModified");
|
||||
fixDate(document, "creationDate");
|
||||
fixDate(document, "creationTime");
|
||||
writeDocument(document, configFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param document
|
||||
* @param element
|
||||
*/
|
||||
private void fixDate(Document document, String element)
|
||||
{
|
||||
NodeList nodes = document.getElementsByTagName(element);
|
||||
|
||||
if (nodes != null)
|
||||
{
|
||||
for (int i = 0; i < nodes.getLength(); i++)
|
||||
{
|
||||
Node node = nodes.item(i);
|
||||
String value = node.getTextContent();
|
||||
|
||||
value = convertDate(value);
|
||||
node.setTextContent(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.upgrade;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.version.Version;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class UpgradeManager
|
||||
{
|
||||
|
||||
/** the logger for ScmUpgradeHandler */
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(UpgradeManager.class);
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void doUpgrade()
|
||||
{
|
||||
File baseDirectory = SCMContext.getContext().getBaseDirectory();
|
||||
File configDirectory = new File(baseDirectory, "config");
|
||||
File versionFile = new File(configDirectory, "version.txt");
|
||||
|
||||
if (configDirectory.exists())
|
||||
{
|
||||
boolean writeVersionFile = false;
|
||||
|
||||
String newVersion = SCMContext.getContext().getVersion();
|
||||
|
||||
if (versionFile.exists())
|
||||
{
|
||||
|
||||
String oldVersion = getVersionString(versionFile);
|
||||
|
||||
if (!Strings.isNullOrEmpty(oldVersion) &&!oldVersion.equals(newVersion))
|
||||
{
|
||||
if (!newVersion.equals(oldVersion))
|
||||
{
|
||||
writeVersionFile = doUpgradesForOldVersion(baseDirectory,
|
||||
configDirectory, oldVersion, newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
writeVersionFile = doUpgradesForOldVersion(baseDirectory,
|
||||
configDirectory, "1.1", newVersion);
|
||||
}
|
||||
|
||||
if (writeVersionFile)
|
||||
{
|
||||
writeVersionFile(versionFile);
|
||||
logger.info("upgrade to version {} was successful", newVersion);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// fresh installation
|
||||
IOUtil.mkdirs(configDirectory);
|
||||
writeVersionFile(versionFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private List<UpgradeHandler> collectUpgradeHandlers()
|
||||
{
|
||||
|
||||
List<UpgradeHandler> upgradeHandlers = Lists.newArrayList();
|
||||
|
||||
upgradeHandlers.add(new TimestampUpgradeHandler());
|
||||
upgradeHandlers.add(new ClientDateFormatUpgradeHandler());
|
||||
|
||||
// TODO find upgrade handlers on classpath
|
||||
return upgradeHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param baseDirectory
|
||||
* @param configDirectory
|
||||
* @param versionString
|
||||
* @param oldVersionString
|
||||
* @param newVersionString
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean doUpgradesForOldVersion(File baseDirectory,
|
||||
File configDirectory, String oldVersionString, String newVersionString)
|
||||
{
|
||||
logger.info("start upgrade from version \"{}\" to \"{}\"",
|
||||
oldVersionString, newVersionString);
|
||||
|
||||
boolean writeVersionFile = false;
|
||||
|
||||
try
|
||||
{
|
||||
Version oldVersion = Version.parse(oldVersionString);
|
||||
Version newVersion = Version.parse(newVersionString);
|
||||
|
||||
doUpgradesForOldVersion(baseDirectory, configDirectory, oldVersion,
|
||||
newVersion);
|
||||
writeVersionFile = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("error upgrade failed", ex);
|
||||
}
|
||||
|
||||
return writeVersionFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param baseDirectory
|
||||
* @param configDirectory
|
||||
* @param version
|
||||
* @param oldVersion
|
||||
* @param newVersion
|
||||
*/
|
||||
private void doUpgradesForOldVersion(File baseDirectory,
|
||||
File configDirectory, Version oldVersion, Version newVersion)
|
||||
{
|
||||
List<UpgradeHandler> upgradeHandlers = collectUpgradeHandlers();
|
||||
|
||||
for (UpgradeHandler upgradeHandler : upgradeHandlers)
|
||||
{
|
||||
logger.trace("call upgrade handler {}", upgradeHandler.getClass());
|
||||
upgradeHandler.doUpgrade(baseDirectory, configDirectory, oldVersion,
|
||||
newVersion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param versionFile
|
||||
*/
|
||||
private void writeVersionFile(File versionFile)
|
||||
{
|
||||
OutputStream output = null;
|
||||
|
||||
try
|
||||
{
|
||||
output = new FileOutputStream(versionFile);
|
||||
output.write(SCMContext.getContext().getVersion().getBytes());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not write version file", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.close(output);
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param versionFile
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getVersionString(File versionFile)
|
||||
{
|
||||
String version = null;
|
||||
|
||||
try
|
||||
{
|
||||
version = Files.toString(versionFile, Charsets.UTF_8).trim();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not read version file", ex);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.upgrade;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public abstract class XmlUpgradeHandler implements UpgradeHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for XmlUpgradeHandler
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(XmlUpgradeHandler.class);
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param baseDirectory
|
||||
* @param note
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected File createBackupDirectory(File baseDirectory, String note)
|
||||
{
|
||||
String date = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
File backupDirectory = new File(baseDirectory,
|
||||
"backups".concat(File.separator).concat(date));
|
||||
|
||||
IOUtil.mkdirs(backupDirectory);
|
||||
|
||||
FileWriter writer = null;
|
||||
|
||||
note = MessageFormat.format(note, SCMContext.getContext().getVersion());
|
||||
|
||||
try
|
||||
{
|
||||
writer = new FileWriter(new File(backupDirectory, "note.txt"));
|
||||
writer.write(note);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not write note.txt for backup", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.close(writer);
|
||||
}
|
||||
|
||||
return backupDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param document
|
||||
* @param configFile
|
||||
*
|
||||
* @throws TransformerConfigurationException
|
||||
* @throws TransformerException
|
||||
*/
|
||||
protected void writeDocument(Document document, File configFile)
|
||||
throws TransformerConfigurationException, TransformerException
|
||||
{
|
||||
Transformer transformer = TransformerFactory.newInstance().newTransformer();
|
||||
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
|
||||
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.transform(new DOMSource(document),
|
||||
new StreamResult(configFile));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user