001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.connector.federation; 025 026 import java.util.Collections; 027 import java.util.HashMap; 028 import java.util.List; 029 import java.util.Map; 030 import java.util.concurrent.CopyOnWriteArrayList; 031 import java.util.concurrent.CountDownLatch; 032 import java.util.concurrent.TimeUnit; 033 import java.util.concurrent.atomic.AtomicBoolean; 034 import java.util.concurrent.atomic.AtomicInteger; 035 import net.jcip.annotations.ThreadSafe; 036 import org.jboss.dna.common.util.CheckArg; 037 import org.jboss.dna.graph.ExecutionContext; 038 import org.jboss.dna.graph.connector.RepositoryConnection; 039 import org.jboss.dna.graph.connector.RepositoryConnectionFactory; 040 import org.jboss.dna.graph.connector.RepositorySource; 041 import org.jboss.dna.graph.connector.RepositorySourceListener; 042 import org.jboss.dna.graph.request.processor.RequestProcessor; 043 044 /** 045 * The component that represents a single federated repository. The federated repository uses a set of {@link RepositorySource 046 * federated connectionFactory} as designated by name through the {@link #getWorkspaceConfigurations() configurations}, and 047 * provides the logic of interacting with those connectionFactory and presenting a single unified graph. 048 * 049 * @author Randall Hauch 050 */ 051 @ThreadSafe 052 public class FederatedRepository { 053 054 private final String name; 055 private final ExecutionContext context; 056 private final RepositoryConnectionFactory connectionFactory; 057 private final Map<String, FederatedWorkspace> workspaceConfigsByName; 058 private final FederatedWorkspace defaultWorkspace; 059 private final AtomicInteger openExecutors = new AtomicInteger(0); 060 private final CountDownLatch shutdownLatch = new CountDownLatch(1); 061 private final AtomicBoolean shutdownRequested = new AtomicBoolean(false); 062 private final CopyOnWriteArrayList<RepositorySourceListener> listeners = new CopyOnWriteArrayList<RepositorySourceListener>(); 063 064 /** 065 * Create a federated repository instance. 066 * 067 * @param repositoryName the name of the repository 068 * @param context the execution context 069 * @param connectionFactory the factory for {@link RepositoryConnection} instances that should be used 070 * @param workspaces the workspace configurations for this repository, with the default workspace being first; may not be null 071 * @throws IllegalArgumentException if any of the parameters are null, or if the name is blank 072 */ 073 public FederatedRepository( String repositoryName, 074 ExecutionContext context, 075 RepositoryConnectionFactory connectionFactory, 076 Iterable<FederatedWorkspace> workspaces ) { 077 CheckArg.isNotEmpty(repositoryName, "repositoryName"); 078 CheckArg.isNotNull(connectionFactory, "connectionFactory"); 079 CheckArg.isNotNull(context, "context"); 080 CheckArg.isNotNull(workspaces, "workspaces"); 081 this.name = repositoryName; 082 this.context = context; 083 this.connectionFactory = connectionFactory; 084 FederatedWorkspace defaultWorkspace = null; 085 Map<String, FederatedWorkspace> configsByName = new HashMap<String, FederatedWorkspace>(); 086 for (FederatedWorkspace workspace : workspaces) { 087 if (defaultWorkspace == null) defaultWorkspace = workspace; 088 configsByName.put(workspace.getName(), workspace); 089 } 090 this.workspaceConfigsByName = Collections.unmodifiableMap(configsByName); 091 this.defaultWorkspace = defaultWorkspace; 092 } 093 094 /** 095 * Get the name of this repository 096 * 097 * @return name 098 */ 099 public String getName() { 100 return name; 101 } 102 103 /** 104 * @return the execution context 105 */ 106 public ExecutionContext getExecutionContext() { 107 return context; 108 } 109 110 /** 111 * @return the connectionFactory 112 */ 113 protected RepositoryConnectionFactory getConnectionFactory() { 114 return connectionFactory; 115 } 116 117 /** 118 * Utility method called by the administrator. 119 */ 120 public synchronized void start() { 121 // Do not establish connections to the connectionFactory; these will be established as needed 122 } 123 124 /** 125 * Return true if this federated repository is running and ready for connections. 126 * 127 * @return true if running, or false otherwise 128 */ 129 public boolean isRunning() { 130 return this.shutdownRequested.get() != true; 131 } 132 133 /** 134 * Utility method called by the administrator. 135 */ 136 public synchronized void shutdown() { 137 this.shutdownRequested.set(true); 138 if (this.openExecutors.get() <= 0) shutdownLatch.countDown(); 139 } 140 141 /** 142 * Utility method called by the administrator. 143 * 144 * @param timeout 145 * @param unit 146 * @return true if all connections open at the time this method is called were {@link RepositoryConnection#close() closed} in 147 * the supplied time, or false if the timeout occurred before all the connections were closed 148 * @throws InterruptedException 149 */ 150 public boolean awaitTermination( long timeout, 151 TimeUnit unit ) throws InterruptedException { 152 // Await until all connections have been closed, or until the timeout occurs 153 return shutdownLatch.await(timeout, unit); 154 } 155 156 /** 157 * Return true if this federated repository has completed its termination and no longer has any open connections. 158 * 159 * @return true if terminated, or false otherwise 160 */ 161 public boolean isTerminated() { 162 return this.openExecutors.get() != 0; 163 } 164 165 /** 166 * Add a listener that is to receive notifications to changes to content within this repository. This method does nothing if 167 * the supplied listener is null. 168 * 169 * @param listener the new listener 170 * @return true if the listener was added, or false if the listener was not added (if reference is null, or if non-null 171 * listener is already an existing listener) 172 */ 173 public boolean addListener( RepositorySourceListener listener ) { 174 if (listener == null) return false; 175 return this.listeners.addIfAbsent(listener); 176 } 177 178 /** 179 * Remove the supplied listener. This method does nothing if the supplied listener is null. 180 * <p> 181 * This method can safely be called while the federation repository is in use. 182 * </p> 183 * 184 * @param listener the listener to remove 185 * @return true if the listener was removed, or false if the listener was not registered 186 */ 187 public boolean removeListener( RepositorySourceListener listener ) { 188 if (listener == null) return false; 189 return this.listeners.remove(listener); 190 } 191 192 /** 193 * Get the list of listeners, which is the actual list used by the repository. 194 * 195 * @return the listeners 196 */ 197 public List<RepositorySourceListener> getListeners() { 198 return this.listeners; 199 } 200 201 /** 202 * Authenticate the supplied username with the supplied credentials, and return whether authentication was successful. 203 * 204 * @param source the {@link RepositorySource} that should be affiliated with the resulting connection 205 * @param username the username 206 * @param credentials the credentials 207 * @return the repository connection if authentication succeeded, or null otherwise 208 */ 209 public RepositoryConnection createConnection( RepositorySource source, 210 String username, 211 Object credentials ) { 212 return new FederatedRepositoryConnection(this, source.getName()); 213 } 214 215 /** 216 * Get the configuration of this repository's workspaces. This set of configurations (as well as each configuration) is 217 * immutable. Therefore, when using a configuration and needing a consistent configuration, maintain a reference to the 218 * configuration during that time (as the actual configuration may be replaced at any time). 219 * 220 * @return the repository's worksapce configuration at the time this method is called. 221 */ 222 public Map<String, FederatedWorkspace> getWorkspaceConfigurations() { 223 return workspaceConfigsByName; 224 } 225 226 /** 227 * Called by {@link FederatedRepositoryConnection#execute(ExecutionContext, org.jboss.dna.graph.request.Request)}. 228 * 229 * @param context the execution context in which the executor will be run; may not be null 230 * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty 231 * @return the executor 232 */ 233 protected RequestProcessor getProcessor( ExecutionContext context, 234 String sourceName ) { 235 Map<String, FederatedWorkspace> workspaces = this.getWorkspaceConfigurations(); 236 return new FederatingRequestProcessor(context, sourceName, workspaces, defaultWorkspace, getConnectionFactory()); 237 } 238 239 /** 240 * Called by {@link FederatedRepositoryConnection#FederatedRepositoryConnection(FederatedRepository, String)}. 241 * 242 * @param connection the connection being opened 243 */ 244 /*package*/void register( FederatedRepositoryConnection connection ) { 245 openExecutors.incrementAndGet(); 246 } 247 248 /** 249 * Called by {@link FederatedRepositoryConnection#close()}. 250 * 251 * @param connection the connection being closed 252 */ 253 /*package*/void unregister( FederatedRepositoryConnection connection ) { 254 if (openExecutors.decrementAndGet() <= 0 && shutdownRequested.get()) { 255 // Last connection, so turn out the lights ... 256 shutdownLatch.countDown(); 257 } 258 } 259 260 }