001    /*
002     * Copyright 2005-2009 The Kuali Foundation
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License"); you may not use this file except in
005     * compliance with the License. You may obtain a copy of the License at
006     * 
007     * http://www.opensource.org/licenses/ecl2.php
008     * 
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS
010     * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
011     * language governing permissions and limitations under the License.
012     */
013    package org.kuali.rice.krad.service.impl;
014    
015    import com.google.common.collect.MapMaker;
016    import org.kuali.rice.core.api.component.Component;
017    import org.kuali.rice.core.api.namespace.Namespace;
018    import org.kuali.rice.core.api.namespace.NamespaceService;
019    import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
020    import org.kuali.rice.core.api.search.SearchOperator;
021    import org.kuali.rice.krad.datadictionary.AttributeDefinition;
022    import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
023    import org.kuali.rice.krad.service.RiceApplicationConfigurationMediationService;
024    import org.kuali.rice.krad.service.RiceApplicationConfigurationService;
025    import org.kuali.rice.ksb.api.KsbApiServiceLocator;
026    import org.kuali.rice.ksb.api.bus.Endpoint;
027    
028    import javax.xml.namespace.QName;
029    import java.util.ArrayList;
030    import java.util.HashSet;
031    import java.util.List;
032    import java.util.Set;
033    import java.util.concurrent.ConcurrentMap;
034    import java.util.concurrent.TimeUnit;
035    
036    //@Transactional
037    public class RiceApplicationConfigurationMediationServiceImpl implements RiceApplicationConfigurationMediationService {
038        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RiceApplicationConfigurationMediationServiceImpl.class);
039         
040     // Max age defined in seconds
041        protected int configurationParameterCacheMaxSize = 200;
042        protected int configurationParameterCacheMaxAgeSeconds = 3600;
043        protected int nonDatabaseComponentsCacheMaxSize = 50;
044        protected int nonDatabaseComponentsCacheMaxAgeSeconds = 3600;
045    
046        //TODO: use the concurrentMap properties rather than synchronized blocks
047        protected final ConcurrentMap<String, String> configurationParameterCache = new MapMaker().maximumSize(configurationParameterCacheMaxSize).expireAfterAccess(configurationParameterCacheMaxAgeSeconds, TimeUnit.SECONDS).softValues().makeMap();
048        protected final ConcurrentMap<String,List<Component>> nonDatabaseComponentsCache = new MapMaker().maximumSize(nonDatabaseComponentsCacheMaxSize).expireAfterAccess(nonDatabaseComponentsCacheMaxAgeSeconds, TimeUnit.SECONDS).softValues().makeMap();
049        protected final ConcurrentMap<String,RiceApplicationConfigurationService> responsibleServiceByPackageClass = new MapMaker().maximumSize(configurationParameterCacheMaxSize).expireAfterAccess(configurationParameterCacheMaxAgeSeconds, TimeUnit.SECONDS).softValues().makeMap();
050        
051        public String getConfigurationParameter( String namespaceCode, String parameterName ){
052            
053            String parameterValue = null;
054            if ( namespaceCode != null ) {
055                String parameterKey = (new StringBuffer(namespaceCode).append(SearchOperator.OR.op()).append(parameterName)).toString();
056                parameterValue = getParameterValueFromConfigurationParameterCache( parameterKey );
057                if ( parameterValue != null ) {
058                    return parameterValue;
059                }
060                NamespaceService nsService = KRADServiceLocatorInternal.getNamespaceService();
061                final String applicationNamespaceCode;
062                Namespace namespace = nsService.getNamespace(namespaceCode);
063                if (namespace != null) {
064                    applicationNamespaceCode = namespace.getApplicationId();
065                } else {
066                    applicationNamespaceCode = namespaceCode;
067                }
068                            if (applicationNamespaceCode != null) {
069                                    RiceApplicationConfigurationService rac = findRiceApplicationConfigurationService(applicationNamespaceCode);
070                                    if (rac != null) {
071                                            parameterValue = rac.getConfigurationParameter(parameterName);
072                                    }
073                            }
074                            if (parameterValue != null){
075                                    synchronized (configurationParameterCache) {
076                                        configurationParameterCache.put( parameterKey, parameterValue);
077                                    }
078                            }
079                    }
080            return parameterValue;
081        }
082        
083    
084        protected String getParameterValueFromConfigurationParameterCache(String parameterKey) {
085            return configurationParameterCache.get( parameterKey );
086        }
087        
088        protected List<Component> getComponentListFromNonDatabaseComponentsCache(String nonDatabaseServiceNameKey) {
089            return nonDatabaseComponentsCache.get( nonDatabaseServiceNameKey );
090        }
091    
092        public List<Component> getNonDatabaseComponents() {
093            
094                    // TODO I think the code that's below here will actually pull in more than
095                    // one reference to a particular application's config service if it is deployed
096                    // in a cluster, it needs to only pull a single RiceApplicationConfigurationService
097                    // implementation per application id.  Also, may want to consider load balancing
098                    // and failover in those cases?  It might be best to try and utilize the client-side
099                    // KSB proxies that handle a lot of this stuff for us
100            
101    
102            Set<QName> serviceNames = findApplicationConfigurationServices();
103                    
104                    List<Component> nonDatabaseComponents = new ArrayList<Component>();
105                    //add cache per serviceName
106                    for ( QName serviceName : serviceNames ) {
107                        List<Component> nonDatabaseComponentFromCache = this.getComponentListFromNonDatabaseComponentsCache(serviceName.toString());
108                    if (nonDatabaseComponentFromCache != null) {
109                        nonDatabaseComponents.addAll(nonDatabaseComponentFromCache);
110                    } else {
111                            RiceApplicationConfigurationService rac = findRiceApplicationConfigurationService(serviceName);
112                            try {
113                                    if (rac != null) {
114                                            nonDatabaseComponents.addAll(rac.getNonDatabaseComponents());
115                                            synchronized (nonDatabaseComponentsCache) {
116                                nonDatabaseComponentsCache.put(serviceName.toString(), rac.getNonDatabaseComponents() );
117                                            }
118                                    }
119                            } catch (Exception e) {
120                                //TODO : Need a better way to catch if service is not active (404 error)
121                                LOG.warn("Invalid RiceApplicationConfigurationService with name: " + serviceName + ".  ");
122                            }
123                    }
124                            
125                    }
126                    
127                    return nonDatabaseComponents;
128        }
129        
130        protected Set<QName> findApplicationConfigurationServices() {
131            Set<QName> names = new HashSet<QName>();
132            List<Endpoint> allEndpoints = KsbApiServiceLocator.getServiceBus().getAllEndpoints();
133            for (Endpoint endpoint : allEndpoints) {
134                    QName serviceName = endpoint.getServiceConfiguration().getServiceName();
135                    if (serviceName.getLocalPart().equals(KRADServiceLocatorInternal.RICE_APPLICATION_CONFIGURATION_SERVICE)) {
136                            names.add(serviceName);
137                    }
138            }
139            return names;
140        }
141        
142        protected RiceApplicationConfigurationService findRiceApplicationConfigurationService(QName serviceName) {
143            try {
144                    return (RiceApplicationConfigurationService) GlobalResourceLoader.getService(serviceName);
145            } catch (Exception e) {
146                    // if the service doesn't exist an exception is thrown
147                    LOG.warn("Failed to locate RiceApplicationConfigurationService with name: " + serviceName,e);
148            }
149            return null;
150        }
151        
152        protected RiceApplicationConfigurationService findRiceApplicationConfigurationService(String namespace) {
153            try {
154                    return (RiceApplicationConfigurationService)GlobalResourceLoader.getService(new QName(namespace, KRADServiceLocatorInternal.RICE_APPLICATION_CONFIGURATION_SERVICE));
155            } catch (Exception e) {
156                    // if the service doesn't exist an exception is thrown
157                    LOG.warn("Failed to locate RiceApplicationConfigurationService with namespace: " + namespace,e);
158            }
159            return null;
160        }
161    
162    
163        public void setConfigurationParameterCacheMaxSize(
164                int configurationParameterCacheMaxSize) {
165            this.configurationParameterCacheMaxSize = configurationParameterCacheMaxSize;
166        }
167    
168    
169        public void setConfigurationParameterCacheMaxAgeSeconds(
170                int configurationParameterCacheMaxAgeSeconds) {
171            this.configurationParameterCacheMaxAgeSeconds = configurationParameterCacheMaxAgeSeconds;
172        }
173    
174    
175        public void setNonDatabaseComponentsCacheMaxSize(
176                int nonDatabaseComponentsCacheMaxSize) {
177            this.nonDatabaseComponentsCacheMaxSize = nonDatabaseComponentsCacheMaxSize;
178        }
179    
180    
181        public void setNonDatabaseComponentsCacheMaxAgeSeconds(
182                int nonDatabaseComponentsCacheMaxAgeSeconds) {
183            this.nonDatabaseComponentsCacheMaxAgeSeconds = nonDatabaseComponentsCacheMaxAgeSeconds;
184        }
185        
186        /**
187         * Call each available service to see if it's responsible for the given package.  When found, cache the result
188         * to prevent need for future service lookups for the same package.
189         */
190        protected RiceApplicationConfigurationService findServiceResponsibleForPackageOrClass( String packageOrClassName ) {
191            if ( LOG.isDebugEnabled() ) {
192                    LOG.debug( "Checking for app config service responsible for: " + packageOrClassName );
193            }
194            RiceApplicationConfigurationService racService = responsibleServiceByPackageClass.get(packageOrClassName);
195    
196    
197            if ( racService != null ) {
198                if ( LOG.isDebugEnabled() ) {
199                    LOG.debug( "Service found in cache: " + racService.getClass().getName() );
200                }
201            }
202    
203            if ( racService == null ) {
204                    Set<QName> serviceNames = findApplicationConfigurationServices();
205                            for ( QName serviceName : serviceNames ) {
206                                    racService = findRiceApplicationConfigurationService(serviceName);
207                                    if ( racService != null ) {
208                                    
209                                            try {
210                                                    if ( racService.isResponsibleForPackage(packageOrClassName) ) {
211                                                    if ( LOG.isDebugEnabled() ) {
212                                                            LOG.debug( "Found responsible class on bus with name: " + serviceName );
213                                                    }               
214                                                            responsibleServiceByPackageClass.put(packageOrClassName, racService );
215                                                            break;
216                                                    } else {
217                                                            racService = null; // null it out in case this is the last iteration
218                                                    }
219                                            } catch (Exception e) {
220                                                    LOG.warn( "Assuming this racService is not responsible for the package or class.  racService: "  +
221                                                                    racService.toString() + " ;  packageOrClassName: " + packageOrClassName);
222                                            }
223                                    }
224                            }
225            }
226            if ( racService == null ) {
227                    LOG.warn( "Unable to find service which handles package/class: " + packageOrClassName + " -- returning null." );
228            }
229                    return racService;
230        }
231        
232        /**
233         * @see org.kuali.rice.krad.service.RiceApplicationConfigurationMediationService#getBaseInquiryUrl(java.lang.String)
234         */
235        public String getBaseInquiryUrl(String businessObjectClassName) {
236            RiceApplicationConfigurationService racService = findServiceResponsibleForPackageOrClass(businessObjectClassName);
237            if ( racService != null ) {
238                    return racService.getBaseInquiryUrl(businessObjectClassName);
239            }
240            return null;
241        }
242    
243        /**
244         * @see org.kuali.rice.krad.service.RiceApplicationConfigurationMediationService#getBaseLookupUrl(java.lang.String)
245         */
246        public String getBaseLookupUrl(String businessObjectClassName) {
247            RiceApplicationConfigurationService racService = findServiceResponsibleForPackageOrClass(businessObjectClassName);
248            if ( racService != null ) {
249                    return racService.getBaseLookupUrl(businessObjectClassName);
250            }
251            return null;
252        }
253        
254        /**
255         * @see org.kuali.rice.krad.service.RiceApplicationConfigurationMediationService#getBaseHelpUrl(java.lang.String)
256         */
257        public String getBaseHelpUrl(String businessObjectClassName) {
258            RiceApplicationConfigurationService racService = findServiceResponsibleForPackageOrClass(businessObjectClassName);
259            if ( racService != null ) {
260                    return racService.getBaseHelpUrl(businessObjectClassName);
261            }
262            return null;
263        }
264        
265        /**
266         * @see org.kuali.rice.krad.service.RiceApplicationConfigurationMediationService#getBusinessObjectAttributeDefinition(java.lang.String, java.lang.String)
267         */
268        public AttributeDefinition getBusinessObjectAttributeDefinition(String businessObjectClassName, String attributeName) {
269            if ( LOG.isDebugEnabled() ) {
270                    LOG.debug( "Querying for an AttributeDefinition for: " + businessObjectClassName + " / " + attributeName );
271            }
272            RiceApplicationConfigurationService racService = findServiceResponsibleForPackageOrClass(businessObjectClassName);
273            if ( racService != null ) {
274                    return racService.getBusinessObjectAttributeDefinition(businessObjectClassName, attributeName);
275            }
276            return null;
277        }
278    }