blob: b07f4f837df77df2f2b8f1873c9c594b37d63ec8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ace.repository.impl;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_BASE_DIR;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_CUSTOMER;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_FILE_EXTENSION;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_INITIAL_CONTENT;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_LIMIT;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_MASTER;
import static org.apache.ace.repository.RepositoryConstants.REPOSITORY_NAME;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Dictionary;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.ace.repository.Repository;
import org.apache.ace.repository.RepositoryReplication;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.log.LogService;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import org.osgi.service.prefs.PreferencesService;
/**
* A <code>ManagedServiceFactory</code> responsible for creating a (<code>Replication</code>)<code>Repository</code>
* instance for each valid configuration that is received from the <code>ConfigurationAdmin</code>.
*/
public class RepositoryFactory implements ManagedServiceFactory {
private final ConcurrentMap<String, Component> m_instances = new ConcurrentHashMap<String, Component>();
private final DependencyManager m_manager;
/* injected by dependency manager */
private volatile LogService m_log;
private volatile BundleContext m_context;
private volatile PreferencesService m_prefsService;
private File m_tempDir;
public RepositoryFactory(DependencyManager manager) {
m_manager = manager;
}
public void deleted(String pid) {
// remove repository service...
Component service = m_instances.remove(pid);
if (service != null) {
File repoDir = ((RepositoryImpl) service.getInstance()).getDir();
m_manager.remove(service);
// remove persisted data...
deleteRepositoryStore(pid, repoDir);
deleteRepositoryPrefs(pid);
}
}
@Override
public String getName() {
return "RepositoryFactory";
}
public void init() {
m_tempDir = ensureDirectoryAvailable(m_context.getDataFile("tmp"));
}
/**
* Creates a new instance if the supplied dictionary contains a valid configuration. A configuration is valid if
* <code>RepositoryConstants.REPOSITORY_NAME</code> and <code>RepositoryConstants.REPOSITORY_CUSTOMER</code>
* properties are present, not empty and the combination of the two is unique in respect to other previously created
* instances. Finally a property <code>RepositoryConstants.REPOSITORY_MASTER</code> should be present and be either
* <code>true</code> or <code>false</code>.
*
* @param pid
* A unique identifier for the instance, generated by <code>ConfigurationAdmin</code> normally.
* @param dict
* The configuration properties for the instance, see description above.
* @throws ConfigurationException
* If any of the above explanation fails <b>or</b>when there is an internal error creating the
* repository.
*/
public void updated(String pid, Dictionary dict) throws ConfigurationException {
String customer = (String) dict.get(REPOSITORY_CUSTOMER);
if ((customer == null) || "".equals(customer)) {
throw new ConfigurationException(REPOSITORY_CUSTOMER, "Repository customer has to be specified.");
}
String name = (String) dict.get(REPOSITORY_NAME);
if ((name == null) || "".equals(name)) {
throw new ConfigurationException(REPOSITORY_NAME, "Repository name has to be specified.");
}
String master = (String) dict.get(REPOSITORY_MASTER);
if (!("false".equalsIgnoreCase(master.trim()) || "true".equalsIgnoreCase(master.trim()))) {
throw new ConfigurationException(REPOSITORY_MASTER, "Have to specify whether the repository is the master or a slave.");
}
boolean isMaster = Boolean.parseBoolean(master);
String fileExtension = (String) dict.get(REPOSITORY_FILE_EXTENSION);
if ((fileExtension == null) || "".equals(fileExtension.trim())) {
fileExtension = "";
}
String baseDirName = (String) dict.get(REPOSITORY_BASE_DIR);
File baseDir;
if (baseDirName == null || "".equals(baseDirName.trim())) {
baseDir = m_context.getDataFile("repos");
}
else {
baseDir = new File(baseDirName);
}
String limit = (String) dict.get(REPOSITORY_LIMIT);
long limitValue = Long.MAX_VALUE;
if (limit != null) {
try {
limitValue = Long.parseLong(limit);
}
catch (NumberFormatException nfe) {
throw new ConfigurationException(REPOSITORY_LIMIT, "Limit has to be a number, was: " + limit);
}
if (limitValue < 1) {
throw new ConfigurationException(REPOSITORY_LIMIT, "Limit has to be at least 1, was " + limit);
}
}
String initialContents = (String) dict.get(REPOSITORY_INITIAL_CONTENT);
Component service = m_manager.createComponent()
.setInterface(new String[] { RepositoryReplication.class.getName(), Repository.class.getName() }, dict)
.setImplementation(createRepositoryStore(pid, baseDir, isMaster, limitValue, fileExtension, initialContents))
.add(m_manager.createServiceDependency().setService(LogService.class).setRequired(false));
Component oldService = m_instances.putIfAbsent(pid, service);
if (oldService == null) {
// new instance...
createRepositoryPrefs(pid, customer, name);
m_manager.add(service);
}
else {
// update existing instance...
RepositoryImpl store = (RepositoryImpl) oldService.getInstance();
// be a little pedantic about the ignored properties...
if (!baseDir.equals(store.getDir())) {
m_log.log(LogService.LOG_WARNING, "Cannot update base directory of repository from " + store.getDir() + " to " + baseDir);
}
if (!fileExtension.equals(store.getFileExtension())) {
m_log.log(LogService.LOG_WARNING, "Cannot update file extension of repository from " + store.getFileExtension() + " to " + fileExtension);
}
store.updated(isMaster, limitValue);
}
}
private void createRepositoryPrefs(String pid, String customer, String name) throws ConfigurationException {
Preferences systemPrefs = m_prefsService.getSystemPreferences();
String[] nodes;
try {
nodes = systemPrefs.childrenNames();
}
catch (BackingStoreException e) {
throw new ConfigurationException(null, "Internal error while validating configuration.");
}
for (String nodeName : nodes) {
if (pid.equals(nodeName)) {
// avoid failing on our own node (in case we're updating)...
continue;
}
Preferences prefs = systemPrefs.node(nodeName);
String repoName = prefs.get(REPOSITORY_NAME, "");
String repoCustomer = prefs.get(REPOSITORY_CUSTOMER, "");
if (name.equalsIgnoreCase(repoName) && name.equalsIgnoreCase(repoCustomer)) {
throw new ConfigurationException(null, "Name and customer combination already exists");
}
}
Preferences node = systemPrefs.node(pid);
node.put(REPOSITORY_NAME, name);
node.put(REPOSITORY_CUSTOMER, customer);
}
private RepositoryImpl createRepositoryStore(String pid, File baseDir, boolean isMaster, long limitValue, String fileExtension, String initialContents) {
File dir = ensureDirectoryAvailable(new File(baseDir, pid));
RepositoryImpl store = new RepositoryImpl(dir, m_tempDir, fileExtension, isMaster, limitValue);
if ((initialContents != null) && isMaster) {
try {
// Do not even try to commit initial contents for existing repositories...
if (store.getRange().getHigh() == 0L) {
store.commit(new ByteArrayInputStream(initialContents.getBytes()), 0L);
}
}
catch (IOException e) {
m_log.log(LogService.LOG_ERROR, "Unable to set initial contents of the repository.", e);
}
}
return store;
}
private void deleteRepositoryPrefs(String pid) {
try {
Preferences prefs = m_prefsService.getSystemPreferences();
prefs.node(pid).removeNode();
prefs.sync();
}
catch (BackingStoreException e) {
// Not much we can do
}
}
private void deleteRepositoryStore(String pid, File repoDir) {
if (repoDir.exists() && repoDir.isDirectory()) {
File[] files = repoDir.listFiles();
for (int i = 0; (files != null) && (i < files.length); i++) {
files[i].delete();
}
if (!repoDir.delete()) {
m_log.log(LogService.LOG_WARNING, "Unable to clean up files in " + repoDir.getAbsolutePath() + " after removing repository!");
}
}
}
private File ensureDirectoryAvailable(File dir) {
if (dir == null) {
throw new IllegalArgumentException("Unable to use file system!");
}
if (dir.exists() && dir.isFile()) {
dir.delete();
}
if (!dir.exists() && !dir.mkdirs()) {
throw new IllegalArgumentException("Unable to create directory: " + dir.getAbsolutePath() + "!");
}
return dir;
}
}