]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
3147500e24ccca00815b6c9577e237536fdbf0f4
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
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.
23  */
24 package org.collectionspace.services.common.config;
25
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.security.DigestInputStream;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 import java.util.ArrayList;
34 import java.util.Enumeration;
35 import java.util.Hashtable;
36 import java.util.List;
37
38 import org.apache.commons.codec.binary.Hex;
39 import org.apache.commons.codec.digest.DigestUtils;
40 import org.apache.commons.io.FileUtils;
41 import org.collectionspace.services.common.api.JEEServerDeployment;
42 import org.collectionspace.services.common.api.Tools;
43 import org.collectionspace.services.config.service.ServiceBindingType;
44 import org.collectionspace.services.config.service.ServiceObjectType;
45 import org.collectionspace.services.config.tenant.RepositoryDomainType;
46 import org.collectionspace.services.config.tenant.TenantBindingConfig;
47 import org.collectionspace.services.config.tenant.TenantBindingType;
48 import org.collectionspace.services.config.types.PropertyItemType;
49
50 import ch.elca.el4j.services.xmlmerge.Configurer;
51 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
52 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
53
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * ServicesConfigReader reads service layer specific configuration
59  * 
60  * $LastChangedRevision: $ $LastChangedDate: $
61  */
62 public class TenantBindingConfigReaderImpl extends AbstractConfigReaderImpl<List<TenantBindingType>> {
63         final public static boolean INCLUDE_CREATE_DISABLED_TENANTS = true;
64         final public static boolean EXCLUDE_CREATE_DISABLED_TENANTS = false;
65
66         final private static String TENANT_BINDINGS_ERROR = "Tenant bindings error(s) for tenant: ";
67         final private static String TENANT_BINDINGS_DELTA_FILENAME = JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX
68                         + ".delta.xml";
69         final private static String MERGED_SUFFIX = ".merged.xml";
70         private static final String NO_SERVICE_BINDINGS_FOUND_ERR = "No Service bindings found.";
71
72         private static final Logger logger = LoggerFactory.getLogger(TenantBindingConfigReaderImpl.class);
73         private List<TenantBindingType> tenantBindingTypeList;
74         // tenant id, tenant binding, for tenants not marked as createDisabled
75         private Hashtable<String, TenantBindingType> enabledTenantBindings = new Hashtable<String, TenantBindingType>();
76         // tenant id, tenant binding
77         private Hashtable<String, TenantBindingType> allTenantBindings = new Hashtable<String, TenantBindingType>();
78         // repository domains
79         private Hashtable<String, RepositoryDomainType> domains = new Hashtable<String, RepositoryDomainType>();
80         // tenant-qualified servicename, service binding
81         private Hashtable<String, ServiceBindingType> serviceBindings = new Hashtable<String, ServiceBindingType>();
82
83         // tenant-qualified service object name to service name, service binding
84         private Hashtable<String, ServiceBindingType> docTypes = new Hashtable<String, ServiceBindingType>();
85
86         public TenantBindingConfigReaderImpl(String serverRootDir) {
87                 super(serverRootDir);
88         }
89
90         @Override
91         public String getFileName() {
92                 return TENANT_BINDINGS_DELTA_FILENAME;
93         }
94
95         private String getFileName(String tenantName, boolean useAppGeneratedBindings) {
96                 String result = getFileName();
97
98                 if (useAppGeneratedBindings == true) {
99                         result = tenantName + "-" + result;
100                 }
101
102                 return result;
103         }
104
105         protected File getTenantsRootDir() {
106                 File result = null;
107                 String errMessage = null;
108                 try {
109                         String tenantsRootPath = getConfigRootDir() + File.separator
110                                         + JEEServerDeployment.TENANT_BINDINGS_ROOTDIRNAME;
111                         File tenantsRootDir = new File(tenantsRootPath);
112                         if (tenantsRootDir.exists() == true) {
113                                 result = tenantsRootDir;
114                                 if (logger.isDebugEnabled() == true) {
115                                         logger.debug("The home directory for all tenants is at: " + result.getCanonicalPath());
116                                 }
117                         } else {
118                                 errMessage = "The home directory for all tenants is missing or inaccessible: ";
119                                 try {
120                                         errMessage = errMessage + tenantsRootDir.getCanonicalPath();
121                                 } catch (IOException ioException) {
122                                         errMessage = errMessage + tenantsRootDir.getAbsolutePath();
123                                 }
124                         }
125                 } catch (IOException e) {
126                         // Log this exception, but continue anyway. Caller should handle the
127                         // null result gracefully.
128                         logger.equals(e);
129                 }
130
131                 if (errMessage != null) {
132                         logger.error(errMessage);
133                 }
134
135                 return result;
136         }
137
138         /*
139          * Take the directory of the prototype bindings and the directory of the
140          * delta bindings. Merge the two and create (replace) a file named
141          * "tenant-bindings.xml"
142          */
143
144         private InputStream merge(File srcFile, File deltaFile) throws IOException {
145                 InputStream result = null;
146                 try {
147                         FileInputStream srcStream = new FileInputStream(srcFile);
148                         FileInputStream deltaStream = new FileInputStream(deltaFile);
149                         InputStream[] inputStreamArray = { srcStream, deltaStream };
150
151                         Configurer configurer = new AttributeMergeConfigurer();
152                         result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
153                 } catch (Exception e) {
154                         logger.error("Could not merge tenant configuration delta file: " + deltaFile.getCanonicalPath(), e);
155                 }
156                 //
157                 // Try to save the merge output to a file that is suffixed with
158                 // ".merged.xml" in the same directory
159                 // as the delta file.
160                 //
161                 if (result != null) {
162                         File outputDir = deltaFile.getParentFile();
163                         String mergedFileName = outputDir.getAbsolutePath() + File.separator
164                                         + JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX + MERGED_SUFFIX;
165                         File mergedOutFile = new File(mergedFileName);
166                         try {
167                                 FileUtils.copyInputStreamToFile(result, mergedOutFile); // Save the merge file for debugging
168                         } catch (IOException e) {
169                                 logger.warn("Could not create a copy of the merged tenant configuration at: " + mergedFileName, e);
170                         }
171                         result.reset(); // reset the stream even if the file create failed.
172                 }
173
174                 return result;
175         }
176
177         @Override
178         public void read(boolean useAppGeneratedBindings) throws Exception {
179                 String tenantsRootPath = getTenantsRootDir().getAbsolutePath();
180                 read(tenantsRootPath, useAppGeneratedBindings);
181         }
182
183         @Override
184         public void read(String tenantRootDirPath, boolean useAppGeneratedBindings) throws Exception {
185                 File tenantsRootDir = new File(tenantRootDirPath);
186                 if (tenantsRootDir.exists() == false) {
187                         throw new Exception("Cound not find tenant bindings root directory: " + tenantRootDirPath);
188                 }
189
190                 List<File> tenantDirs = getDirectories(tenantsRootDir);
191                 tenantBindingTypeList = readTenantConfigs(new File(tenantRootDirPath), tenantDirs, useAppGeneratedBindings);
192                 if (tenantBindingTypeList == null || tenantBindingTypeList.size() < 1) {
193                         throw new Exception(NO_SERVICE_BINDINGS_FOUND_ERR);
194                 }
195
196                 for (TenantBindingType tenantBinding : tenantBindingTypeList) {
197                         if (allTenantBindings.get(tenantBinding.getId()) != null) {
198                                 TenantBindingType tenantBindingOld = allTenantBindings.get(tenantBinding.getId());
199                                 logger.error("Ignoring duplicate binding definition for tenant id=" + tenantBinding.getId()
200                                                 + " existing name=" + tenantBindingOld.getName() + " conflicting (ignored) name="
201                                                 + tenantBinding.getName());
202                                 continue;
203                         }
204                         allTenantBindings.put(tenantBinding.getId(), tenantBinding);
205                         if (!tenantBinding.isCreateDisabled()) {
206                                 enabledTenantBindings.put(tenantBinding.getId(), tenantBinding);
207                         } else {
208                                 logger.warn(String.format("The tenant '%s':'%s' is marked as disabled in its bindings file.",
209                                                 tenantBinding.getName(), tenantBinding.getId()));
210                         }
211                         readDomains(tenantBinding);
212                         readServiceBindings(tenantBinding);
213                         if (logger.isInfoEnabled()) {
214                                 logger.info("Finished reading tenant bindings for tenant id=" + tenantBinding.getId() + " name="
215                                                 + tenantBinding.getName());
216                                 if (tenantBinding.isCreateDisabled())
217                                         logger.info("Tenant tenant id={} is marked createDisabled.", tenantBinding.getId());
218                         }
219                 }
220                 
221                 //
222                 // Ensure that at least one tenant is enabled, otherwise abort the startup.
223                 //
224                 if (enabledTenantBindings.isEmpty() == true) {
225                         throw new Exception("All of the configured tenants are marked as disabled in their tenant bindings.  At least one tenant needs to be enabled.");
226                 }
227         }
228
229         /*
230          * Take the directory of the prototype bindings and the directory of the
231          * delta bindings. Merge the two and create (replace) a file named
232          * "tenant-bindings.xml"
233          * 
234          * private static String merge(String original, String patch) { InputStream
235          * result = null; try { Configurer configurer = new
236          * AttributeMergeConfigurer();
237          * 
238          * 
239          * FileInputStream ins1 = new
240          * FileInputStream(".\\src\\main\\resources\\File1.xml"); FileInputStream
241          * ins2 = new FileInputStream(".\\src\\main\\resources\\File2.xml");
242          * InputStream[] inputStreamArray = {ins1, ins2};
243          * 
244          * result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray); //
245          * result = new ConfigurableXmlMerge(configurer).merge(new String[]
246          * {original, patch}); } catch (Exception e) { // TODO Auto-generated catch
247          * block e.printStackTrace(); } File mergedOutFile = new
248          * File(".\\target\\merged.xml"); try {
249          * FileUtils.copyInputStreamToFile(result, mergedOutFile); } catch
250          * (IOException e) { // TODO Auto-generated catch block e.printStackTrace();
251          * }
252          * 
253          * return null; }
254          */
255
256         /**
257          * Merge and read the prototype bindsings with each tenant specific bindings
258          * delta to create the final tenant bindings.
259          * 
260          * @param protoBindingsFile
261          *            - The prototypical bindings file.
262          * @param tenantDirList
263          *            - The list of tenant directories containing tenant specific
264          *            bindings
265          * @return A list of tenant bindings.
266          * @throws IOException
267          *             Signals that an I/O exception has occurred.
268          */
269         List<TenantBindingType> readTenantConfigs(File protoBindingsDir, List<File> tenantDirList,
270                         boolean useAppGeneratedBindings) throws IOException {
271                 List<TenantBindingType> result = new ArrayList<TenantBindingType>();
272
273                 //
274                 // Iterate through a list of directories.
275                 //
276                 for (File tenantDir : tenantDirList) {
277                         boolean found = false;
278                         String errMessage = null;
279
280                         File tenantBindingsProtoFile = null;
281                         String tenantName = tenantDir.getName();        // By convention, the
282                                                                                                                 // directory name should
283                                                                                                                 // be the tenant name
284                         if (useAppGeneratedBindings == true) {
285                                 tenantBindingsProtoFile = new File(protoBindingsDir.getAbsolutePath() + File.separator + tenantName
286                                                 + "-" + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
287                         } else {
288                                 tenantBindingsProtoFile = new File(protoBindingsDir + File.separator
289                                                 + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
290                         }
291
292                         if (tenantBindingsProtoFile.exists() == true) {
293                                 File configFile = new File(tenantDir.getAbsoluteFile() + File.separator
294                                                 + getFileName(tenantName, useAppGeneratedBindings));
295                                 if (configFile.exists() == true) {
296                                         InputStream tenantBindingsStream = this.merge(tenantBindingsProtoFile, configFile);
297                                         TenantBindingConfig tenantBindingConfig = null;
298                                         try {
299                                                 tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
300                                                                 TenantBindingConfig.class);
301                                                 //
302                                                 // Compute the MD5 hash of the tenant's binding file.  We'll persist this a little later during startup.  If the value hasn't
303                                                 // changed since we last startedup, we can skip some of the startup steps.
304                                                 //
305                                                 tenantBindingsStream.reset();
306                                                 String md5hash = new String(Hex.encodeHex(DigestUtils.md5(tenantBindingsStream)));
307                                                 tenantBindingConfig.getTenantBinding().setConfigMD5Hash(md5hash); // use this to compare with the last persisted one and to persist as the new hash
308                                         } catch (Exception e) {
309                                                 logger.error("Could not parse the merged tenant bindings.", e);
310                                         }
311                                         if (tenantBindingConfig != null) {
312                                                 TenantBindingType binding = tenantBindingConfig.getTenantBinding();
313                                                 if (binding != null) {
314                                                         result.add(binding);
315                                                         found = true;
316                                                         if (logger.isInfoEnabled() == true) {
317                                                                 logger.info("Parsed tenant configuration for: " + binding.getDisplayName());
318                                                         }
319                                                 } else {
320                                                         errMessage = "Cound not parse the tenant bindings in: ";
321                                                 }
322                                         } else {
323                                                 errMessage = "Could not parse the tenant bindings file: ";
324                                         }
325                                 } else {
326                                         errMessage = "Expected to, but could not, find the tenant delta configuration file: "
327                                                         + configFile.getAbsolutePath();
328                                 }
329                         } else {
330                                 errMessage = "Expected to, but could not, find the tenant proto configuration file: "
331                                                 + tenantBindingsProtoFile.getAbsolutePath();
332                         }
333
334                         if (found == false) {
335                                 if (logger.isErrorEnabled() == true) {
336                                         errMessage = errMessage != null ? errMessage : TENANT_BINDINGS_ERROR;
337                                         logger.error(errMessage + " - For tenant: " + tenantName);
338                                 }
339                         }
340                 } // else-for
341
342                 return result;
343         }
344
345         private void readDomains(TenantBindingType tenantBinding) throws Exception {
346                 for (RepositoryDomainType domain : tenantBinding.getRepositoryDomain()) {
347                         String key = getTenantQualifiedIdentifier(tenantBinding.getId(), domain.getName());
348                         domains.put(key, domain);
349                 }
350         }
351
352         private void readServiceBindings(TenantBindingType tenantBinding) throws Exception {
353                 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
354                         String key = getTenantQualifiedServiceName(tenantBinding.getId(), serviceBinding.getName());
355                         if (key == null) {
356                                 continue;
357                         }
358                         serviceBindings.put(key, serviceBinding);
359
360                         if (serviceBinding != null) {
361                                 ServiceObjectType objectType = serviceBinding.getObject();
362                                 if (objectType != null) {
363                                         String docType = objectType.getName();
364                                         String docTypeKey = getTenantQualifiedIdentifier(tenantBinding.getId(), docType);
365                                         docTypes.put(docTypeKey, serviceBinding);
366                                 }
367                         }
368                         if (logger.isTraceEnabled()) {
369                                 logger.trace("readServiceBindings() added service " + " name=" + key + " workspace="
370                                                 + serviceBinding.getName());
371                         }
372                 }
373         }
374
375         @Override
376         public List<TenantBindingType> getConfiguration() {
377                 return tenantBindingTypeList;
378         }
379
380         /**
381          * getTenantBindings returns all the tenant bindings read from configuration
382          * 
383          * @return
384          */
385         public Hashtable<String, TenantBindingType> getTenantBindings() {
386                 return getTenantBindings(EXCLUDE_CREATE_DISABLED_TENANTS);
387         }
388
389         /**
390          * getTenantBindings returns all the tenant bindings read from configuration
391          * 
392          * @return
393          */
394         public Hashtable<String, TenantBindingType> getTenantBindings(boolean includeDisabled) {
395                 return includeDisabled ? allTenantBindings : enabledTenantBindings;
396         }
397
398         /**
399          * getTenantBinding gets tenant binding for given tenant
400          * 
401          * @param tenantId
402          * @return
403          */
404         public TenantBindingType getTenantBinding(String tenantId) {
405                 return allTenantBindings.get(tenantId);
406         }
407
408         /**
409          * getRepositoryDomain gets repository domain configuration for the given
410          * name
411          * 
412          * @param domainName
413          * @return
414          */
415         public RepositoryDomainType getRepositoryDomain(String domainName) {
416                 return domains.get(domainName.trim());
417         }
418
419         /**
420          * getRepositoryDomain gets repository domain configuration for the given
421          * service and given tenant id
422          * 
423          * @param tenantId
424          * @param serviceName
425          * @return
426          */
427         public RepositoryDomainType getRepositoryDomain(String tenantId, String serviceName) {
428                 ServiceBindingType serviceBinding = getServiceBinding(tenantId, serviceName);
429                 if (serviceBinding == null) {
430                         throw new IllegalArgumentException("no service binding found for " + serviceName + " of tenant with id="
431                                         + tenantId);
432                 }
433                 String repoDomain = serviceBinding.getRepositoryDomain();
434                 if (repoDomain == null) {
435                         if (logger.isTraceEnabled()) {
436                                 logger.trace("No repository domain configured for " + serviceName + " of tenant with id=" + tenantId);
437                         }
438                         return null;
439                 }
440                 String key = this.getTenantQualifiedIdentifier(tenantId, repoDomain.trim());
441                 return domains.get(key);
442         }
443
444         /**
445          * getServiceBinding gets service binding for given tenant for a given
446          * service
447          * 
448          * @param tenantId
449          * @param serviceName
450          * @return
451          */
452         public ServiceBindingType getServiceBinding(String tenantId, String serviceName) {
453                 String key = getTenantQualifiedServiceName(tenantId, serviceName);
454                 return serviceBindings.get(key);
455         }
456
457         /**
458          * getServiceBinding gets service binding for given tenant for a given
459          * service
460          * 
461          * @param tenantId
462          * @param docType
463          * @return
464          */
465         public ServiceBindingType getServiceBindingForDocType(String tenantId, String docType) {
466                 String key = getTenantQualifiedIdentifier(tenantId, docType);
467                 return docTypes.get(key);
468         }
469
470         /**
471          * getServiceBinding gets service binding for given tenant for a given
472          * service
473          * 
474          * @param tenantId
475          * @param serviceName
476          * @return
477          */
478         public List<ServiceBindingType> getServiceBindingsByType(String tenantId, String serviceType) {
479                 List<String> serviceTypes = new ArrayList<String>(1);
480                 serviceTypes.add(serviceType);
481                 return getServiceBindingsByType(tenantId, serviceTypes);
482         }
483
484         /**
485          * getServiceBindingsByType gets service bindings for a given tenant for the
486          * services that fall within a supplied set of service type(s)
487          * 
488          * @param tenantId
489          * @param serviceTypes
490          * @return
491          */
492         public List<ServiceBindingType> getServiceBindingsByType(String tenantId, List<String> serviceTypes) {
493                 ArrayList<ServiceBindingType> list = null;
494                 TenantBindingType tenant = allTenantBindings.get(tenantId);
495                 if (tenant != null) {
496                         for (ServiceBindingType sb : tenant.getServiceBindings()) {
497                                 if (serviceTypes.contains(sb.getType())) {
498                                         if (list == null) {
499                                                 list = new ArrayList<ServiceBindingType>();
500                                         }
501                                         list.add(sb);
502                                 }
503                         }
504                 }
505                 return list;
506         }
507
508         /**
509          * @param tenantId
510          * @param serviceName
511          * @return the properly qualified service name
512          */
513         public static String getTenantQualifiedServiceName(String tenantId, String serviceName) {
514                 String result = null;
515
516                 if (serviceName != null) {
517                         logger.trace(String.format(" * tenant:serviceBindings '%s'", serviceName));
518                         result = getTenantQualifiedIdentifier(tenantId, serviceName.toLowerCase());
519                 }
520
521                 return result;
522         }
523
524         public static String getTenantQualifiedIdentifier(String tenantId, String identifier) {
525                 return tenantId + "." + identifier;
526         }
527
528         /**
529          * Sets properties in the passed list on the local properties for this
530          * TenantBinding. Note: will only set properties not already set on the
531          * TenantBinding.
532          * 
533          * @param propList
534          * @param propagateToServices
535          *            If true, recurses to set set properties on the associated
536          *            services.
537          */
538         public void setDefaultPropertiesOnTenants(List<PropertyItemType> propList, boolean propagateToServices) {
539                 // For each tenant, set properties in list that are not already set
540                 if (propList == null || propList.isEmpty()) {
541                         return;
542                 }
543                 for (TenantBindingType tenant : allTenantBindings.values()) {
544                         for (PropertyItemType prop : propList) {
545                                 TenantBindingUtils.setPropertyValue(tenant, prop, TenantBindingUtils.SET_PROP_IF_MISSING);
546                         }
547                         if (propagateToServices) {
548                                 TenantBindingUtils.propagatePropertiesToServices(tenant, TenantBindingUtils.SET_PROP_IF_MISSING);
549                         }
550                 }
551         }
552
553         public String getResourcesDir() {
554                 return getConfigRootDir() + File.separator + RESOURCES_DIR_NAME;
555         }
556
557         /**
558          * Returns a list of tenant identifiers (tenant IDs).
559          * 
560          * @return a list of tenant IDs
561          */
562         public List<String> getTenantIds() {
563                 return getTenantIds(EXCLUDE_CREATE_DISABLED_TENANTS);
564         }
565
566         /**
567          * Returns a list of tenant identifiers (tenant IDs).
568          * 
569          * @return a list of tenant IDs
570          */
571         public List<String> getTenantIds(boolean includeDisabled) {
572                 List<String> tenantIds = new ArrayList<String>();
573                 String tenantId;
574                 Hashtable<String, TenantBindingType> tenantBindings = getTenantBindings(includeDisabled);
575                 if (tenantBindings != null && !tenantBindings.isEmpty()) {
576                         Enumeration keys = tenantBindings.keys();
577                         while (keys.hasMoreElements()) {
578                                 tenantId = (String) keys.nextElement();
579                                 if (Tools.notBlank(tenantId)) {
580                                         tenantIds.add(tenantId);
581                                 }
582                         }
583                 }
584                 return tenantIds;
585         }
586 }