2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common.config;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.ArrayList;
31 import java.util.Hashtable;
32 import java.util.List;
34 import org.apache.commons.io.FileUtils;
35 import org.collectionspace.services.common.service.ServiceBindingType;
36 import org.collectionspace.services.common.service.ServiceObjectType;
37 import org.collectionspace.services.common.tenant.RepositoryDomainType;
38 import org.collectionspace.services.common.tenant.TenantBindingType;
39 import org.collectionspace.services.common.tenant.TenantBindingConfig;
40 import org.collectionspace.services.common.types.PropertyItemType;
42 import ch.elca.el4j.services.xmlmerge.Configurer;
43 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
44 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * ServicesConfigReader reads service layer specific configuration
52 * $LastChangedRevision: $
55 public class TenantBindingConfigReaderImpl
56 extends AbstractConfigReaderImpl<List<TenantBindingType>> {
57 final private static String TENANT_BINDINGS_ERROR = "Tenant bindings error: ";
58 final private static String TENANT_BINDINGS_ROOTDIRNAME = "tenants";
59 final private static String TENANT_BINDINGS_FILENAME_PREFIX = "tenant-bindings";
60 final private static String TENANT_BINDINGS_PROTOTYPE_FILENAME = TENANT_BINDINGS_FILENAME_PREFIX + "-proto.xml";
61 final private static String TENANT_BINDINGS_DELTA_FILENAME = TENANT_BINDINGS_FILENAME_PREFIX + ".delta.xml";
62 final private static String MERGED_SUFFIX = ".merged.xml";
64 final Logger logger = LoggerFactory.getLogger(TenantBindingConfigReaderImpl.class);
65 private List<TenantBindingType> tenantBindingTypeList;
66 //tenant id, tenant binding
67 private Hashtable<String, TenantBindingType> tenantBindings =
68 new Hashtable<String, TenantBindingType>();
70 private Hashtable<String, RepositoryDomainType> domains =
71 new Hashtable<String, RepositoryDomainType>();
72 //tenant-qualified servicename, service binding
73 private Hashtable<String, ServiceBindingType> serviceBindings =
74 new Hashtable<String, ServiceBindingType>();
76 //tenant-qualified service object name to service name, service binding
77 private Hashtable<String, ServiceBindingType> docTypes =
78 new Hashtable<String, ServiceBindingType>();
81 public TenantBindingConfigReaderImpl(String serverRootDir) {
86 public String getFileName() {
87 return TENANT_BINDINGS_DELTA_FILENAME;
90 protected File getTenantsRootDir() {
92 String errMessage = null;
94 String tenantsRootPath = getConfigRootDir() + File.separator + TENANT_BINDINGS_ROOTDIRNAME;
95 File tenantsRootDir = new File(tenantsRootPath);
96 if (tenantsRootDir.exists() == true) {
97 result = tenantsRootDir;
98 if (logger.isDebugEnabled() == true) {
99 logger.debug("The home directory for all tenants is at: " + result.getCanonicalPath());
102 errMessage = "The home directory for all tenants is missing or inaccesible: ";
104 errMessage = errMessage + tenantsRootDir.getCanonicalPath();
105 } catch (IOException ioException) {
106 errMessage = errMessage + tenantsRootDir.getAbsolutePath();
109 } catch (IOException e) {
110 // Log this exception, but continue anyway. Caller should handle the null result gracefully.
114 if (errMessage != null) {
115 logger.error(errMessage);
122 * Take the directory of the prototype bindings and the directory of the delta bindings. Merge the two and create (replace) a file
123 * named "tenant-bindings.xml"
126 private InputStream merge(File srcFile, File deltaFile) throws IOException {
127 InputStream result = null;
129 FileInputStream srcStream = new FileInputStream(srcFile);
130 FileInputStream deltaStream = new FileInputStream(deltaFile);
131 InputStream[] inputStreamArray = {srcStream, deltaStream};
133 Configurer configurer = new AttributeMergeConfigurer();
134 result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
135 } catch (Exception e) {
136 logger.error("Could not merge tenant configuration delta file: " +
137 deltaFile.getCanonicalPath(), e);
140 // Try to save the merge output to a file that is suffixed with ".merged.xml" in the same directory
141 // as the delta file.
143 if (result != null) {
144 File outputDir = deltaFile.getParentFile();
145 String mergedFileName = outputDir.getAbsolutePath() + File.separator +
146 TenantBindingConfigReaderImpl.TENANT_BINDINGS_FILENAME_PREFIX + MERGED_SUFFIX;
147 File mergedOutFile = new File(mergedFileName);
149 FileUtils.copyInputStreamToFile(result, mergedOutFile);
150 } catch (IOException e) {
151 logger.warn("Could not create a copy of the merged tenant configuration at: " +
154 result.reset(); //reset the stream even if the file create failed.
161 public void read() throws Exception {
162 String tenantsRootPath = getTenantsRootDir().getAbsolutePath();
163 read(tenantsRootPath);
167 public void read(String tenantRootDirPath) throws Exception {
168 File tenantsRootDir = new File(tenantRootDirPath);
169 if (tenantsRootDir.exists() == false) {
170 throw new Exception("Cound not find tenant bindings root directory: " +
173 File protoBindingsFile = new File(tenantRootDirPath + File.separator +
174 TENANT_BINDINGS_PROTOTYPE_FILENAME);
176 List<File> tenantDirs = getDirectories(tenantsRootDir);
177 tenantBindingTypeList = readTenantConfigs(protoBindingsFile, tenantDirs);
179 for (TenantBindingType tenantBinding : tenantBindingTypeList) {
180 if(tenantBindings.get(tenantBinding.getId()) != null) {
181 TenantBindingType tenantBindingOld = tenantBindings.get(tenantBinding.getId());
182 logger.error("Ignoring duplicate binding definition for tenant id="
183 + tenantBinding.getId()
184 + " existing name=" + tenantBindingOld.getName()
185 + " conflicting (ignored) name=" + tenantBinding.getName());
188 tenantBindings.put(tenantBinding.getId(), tenantBinding);
189 readDomains(tenantBinding);
190 readServiceBindings(tenantBinding);
191 if (logger.isInfoEnabled()) {
192 logger.info("Finished reading tenant bindings for tenant id=" + tenantBinding.getId()
193 + " name=" + tenantBinding.getName());
199 * Take the directory of the prototype bindings and the directory of the delta bindings. Merge the two and create (replace) a file
200 * named "tenant-bindings.xml"
202 private static String merge(String original, String patch) {
203 InputStream result = null;
205 Configurer configurer = new AttributeMergeConfigurer();
208 FileInputStream ins1 = new FileInputStream(".\\src\\main\\resources\\File1.xml");
209 FileInputStream ins2 = new FileInputStream(".\\src\\main\\resources\\File2.xml");
210 InputStream[] inputStreamArray = {ins1, ins2};
212 result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
213 // result = new ConfigurableXmlMerge(configurer).merge(new String[] {original, patch});
214 } catch (Exception e) {
215 // TODO Auto-generated catch block
218 File mergedOutFile = new File(".\\target\\merged.xml");
220 FileUtils.copyInputStreamToFile(result, mergedOutFile);
221 } catch (IOException e) {
222 // TODO Auto-generated catch block
231 * Merge and read the prototype bindsings with each tenant specific bindings delta to create the final
234 * @param protoBindingsFile - The prototypical bindings file.
235 * @param tenantDirList - The list of tenant directories containing tenant specific bindings
236 * @return A list of tenant bindings.
237 * @throws IOException Signals that an I/O exception has occurred.
239 List<TenantBindingType> readTenantConfigs(File protoBindingsFile, List<File> tenantDirList) throws IOException {
240 List<TenantBindingType> result = new ArrayList<TenantBindingType>();
242 // Iterate through a list of directories.
244 for (File tenantDir : tenantDirList) {
245 boolean found = false;
246 String errMessage = null;
247 File configFile = new File(tenantDir.getAbsoluteFile() + File.separator + getFileName());
248 if (configFile.exists() == true) {
249 InputStream tenantBindingsStream = this.merge(protoBindingsFile, configFile);
250 TenantBindingConfig tenantBindingConfig = null;
252 tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
253 TenantBindingConfig.class);
254 } catch (Exception e) {
255 logger.error("Could not parse the merged tenant bindings.", e);
257 if (tenantBindingConfig != null) {
258 TenantBindingType binding = tenantBindingConfig.getTenantBinding();
259 if (binding != null) {
262 if (logger.isInfoEnabled() == true) {
263 logger.info("Parsed tenant configureation for: " + binding.getDisplayName());
266 errMessage = "Cound not parse the tentant bindings in: ";
269 errMessage = "Could not parse the tenant bindings file: ";
272 errMessage = "Cound not find a tenant configuration file: ";
274 if (found == false) {
275 if (logger.isErrorEnabled() == true) {
276 errMessage = errMessage != null ? errMessage : TENANT_BINDINGS_ERROR;
277 logger.error(errMessage + configFile.getAbsolutePath());
285 private void readDomains(TenantBindingType tenantBinding) throws Exception {
286 for (RepositoryDomainType domain : tenantBinding.getRepositoryDomain()) {
287 String key = getTenantQualifiedIdentifier(tenantBinding.getId(),
289 domains.put(key, domain);
293 private void readServiceBindings(TenantBindingType tenantBinding) throws Exception {
294 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
295 String key = getTenantQualifiedServiceName(tenantBinding.getId(),
296 serviceBinding.getName());
297 serviceBindings.put(key, serviceBinding);
299 if (serviceBinding!=null){
300 ServiceObjectType objectType = serviceBinding.getObject();
301 if (objectType!=null){
302 String docType = objectType.getName();
303 String docTypeKey = getTenantQualifiedIdentifier(tenantBinding.getId(), docType);
304 docTypes.put(docTypeKey, serviceBinding);
307 if (logger.isTraceEnabled()) {
308 logger.trace("readServiceBindings() added service "
310 + " workspace=" + serviceBinding.getName());
316 public List<TenantBindingType> getConfiguration() {
317 return tenantBindingTypeList;
321 * getTenantBindings returns all the tenant bindings read from configuration
324 public Hashtable<String, TenantBindingType> getTenantBindings() {
325 return tenantBindings;
329 * getTenantBinding gets tenant binding for given tenant
333 public TenantBindingType getTenantBinding(
335 return tenantBindings.get(tenantId);
339 * getRepositoryDomain gets repository domain configuration for the given name
343 public RepositoryDomainType getRepositoryDomain(String domainName) {
344 return domains.get(domainName.trim());
348 * getRepositoryDomain gets repository domain configuration for the given service
349 * and given tenant id
354 public RepositoryDomainType getRepositoryDomain(String tenantId, String serviceName) {
355 ServiceBindingType serviceBinding = getServiceBinding(tenantId, serviceName);
356 if (serviceBinding == null) {
357 throw new IllegalArgumentException("no service binding found for " + serviceName
358 + " of tenant with id=" + tenantId);
360 String repoDomain = serviceBinding.getRepositoryDomain();
361 if (repoDomain == null) {
362 /* This is excessive - every call to a JPA based service dumps this msg.
363 if (logger.isDebugEnabled()) {
364 logger.debug("No repository domain configured for " + serviceName
365 + " of tenant with id=" + tenantId);
370 String key = this.getTenantQualifiedIdentifier(tenantId, repoDomain.trim());
371 return domains.get(key);
375 * getServiceBinding gets service binding for given tenant for a given service
380 public ServiceBindingType getServiceBinding(
381 String tenantId, String serviceName) {
382 String key = getTenantQualifiedServiceName(tenantId, serviceName);
383 return serviceBindings.get(key);
387 * getServiceBinding gets service binding for given tenant for a given service
392 public ServiceBindingType getServiceBindingForDocType (String tenantId, String docType) {
393 String key = getTenantQualifiedIdentifier(tenantId, docType);
394 return docTypes.get(key);
398 * getServiceBinding gets service binding for given tenant for a given service
403 public List<ServiceBindingType> getServiceBindingsByType(
404 String tenantId, String serviceType) {
405 List<String> serviceTypes = new ArrayList<String>(1);
406 serviceTypes.add(serviceType);
407 return getServiceBindingsByType(tenantId, serviceTypes);
410 * getServiceBinding gets service binding for given tenant for a given service
415 public List<ServiceBindingType> getServiceBindingsByType(
416 String tenantId, List<String> serviceTypes) {
417 ArrayList<ServiceBindingType> list = null;
418 TenantBindingType tenant = tenantBindings.get(tenantId);
419 if (tenant != null) {
420 for (ServiceBindingType sb : tenant.getServiceBindings()) {
421 if (serviceTypes.contains(sb.getType())) {
423 list = new ArrayList<ServiceBindingType>();
435 * @return the properly qualified service name
437 public static String getTenantQualifiedServiceName(
438 String tenantId, String serviceName) {
439 // return tenantId + "." + serviceName.toLowerCase();
440 return getTenantQualifiedIdentifier(tenantId, serviceName.toLowerCase());
443 public static String getTenantQualifiedIdentifier(String tenantId, String identifier) {
444 return tenantId + "." + identifier;
447 * Sets properties in the passed list on the local properties for this TenantBinding.
448 * Note: will only set properties not already set on the TenantBinding.
451 * @param propagateToServices If true, recurses to set set properties
452 * on the associated services.
454 public void setDefaultPropertiesOnTenants(List<PropertyItemType> propList,
455 boolean propagateToServices) {
456 // For each tenant, set properties in list that are not already set
457 if (propList == null || propList.isEmpty()) {
460 for (TenantBindingType tenant : tenantBindings.values()) {
461 for (PropertyItemType prop : propList) {
462 TenantBindingUtils.setPropertyValue(tenant,
463 prop, TenantBindingUtils.SET_PROP_IF_MISSING);
465 if (propagateToServices) {
466 TenantBindingUtils.propagatePropertiesToServices(tenant,
467 TenantBindingUtils.SET_PROP_IF_MISSING);
472 public String getResourcesDir(){
473 return getConfigRootDir() + File.separator + "resources";