View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.util;
17  
18  import java.util.List;
19  import java.util.Map;
20  import java.util.concurrent.ConcurrentHashMap;
21  
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.commons.lang.Validate;
24  import org.kuali.rice.core.api.config.property.ConfigContext;
25  import org.kuali.rice.core.framework.util.ReflectionUtils;
26  import org.kuali.rice.krad.data.metadata.MetadataRepository;
27  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
28  import org.kuali.rice.krad.service.DataDictionaryService;
29  import org.springframework.util.ClassUtils;
30  
31  import com.google.common.collect.Lists;
32  
33  /**
34   * Utility class which is used to determine whether the given object or class has been configured in the "legacy" KRAD/KNS
35   * implementation, or for the new KRAD-Data layer.
36   *
37   * <ol>
38   *     <li>The configuration parameter "rice.krad.enableLegacyDataFramework" governs this determination. It is false unless the KNSConfigurer is loaded in which case it will be defaulted to true.
39   *         <ul>
40   *             <li>This indicates that if a given business/data object exists in both the old and new data layers that it will use the old one. This is important for classes like DocumentHeader and the other KRAD classes because they are mapped and loaded into both.</li>
41   *             <li>KRAD appliations which are not using legacy KNS (and therefore not loading the KNSConfigurer) but still using the KRAD data layer as it exists today, need to manually set this to true in order for their applications to load properly.</li>
42   *             <li>If it's set to false then OJB metadata won't even be loaded for KRAD objects like DocumentHeader, Notes, Attachments, etc.</li>
43   *         </ul>
44   *     </li>
45   *     <li>The logic for determining whether to an object should be handled by the legacy or new data layers is as follows:
46   *         <ul>
47   *             <li>If rice.krad.enableLegacyDataFramework is false, use new framework (DataObjectService)</li>
48   *             <li>If rice.krad.enableLegacyDataFramework is true, check if object/class is in OJB metadata loaded by legacy framework:
49   *                 <ul>
50   *                     <li>if it is, use legacy framework (BusinessObjectService, etc.)</li>
51   *                     <li>if not, use new framework (DataObjectService)</li>
52   *                 </ul>
53   *             </li>
54   *             <li>In order to check if an object/class is in OJB metadata that was loaded by the legacy framework:</li>
55   *                 <ul>
56   *                     <li>Check for OJB MetadataManager on classpath using reflection to avoid compile-time dependency</li>
57   *                     <li>This may mean we need to enhance something like ModuleConfiguration so that when it loads the given OJB files via the OjbConfigurer that it keeps track of entities loaded this way (to distinguish those laoded by the legacy framework vs. those loaded by the OJB PersistenceProvider)</li>
58   *                 </ul>
59   *             </li>
60   *         </ul>
61   *     </li>
62   *     <li>If rice.krad.enableLegacyDataFramework is false, then calls to deprecated legacy data services (such as BusinessObjectService) should throw an exception indicating that legacy data framework support is disabled and rice.krad.enableLegacyDataFramework must be set to true in order to use these services</li>
63   * </ol>
64   */
65  class LegacyDetector {
66      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LegacyDetector.class);
67      /**
68       * The "legacy" (OJB) metadata provider class
69       */
70      private static final String OJB_METADATA_MANAGER_CLASS = "org.apache.ojb.broker.metadata.MetadataManager";
71  
72      private final MetadataRepository metadataRepository;
73      private final DataDictionaryService dataDictionaryService;
74  
75      private final Map<Class<?>, Boolean> legacyLoadedCache = new ConcurrentHashMap<Class<?>, Boolean>();
76  
77      private static ThreadLocal<Integer> legacyContext = new ThreadLocal<Integer>();
78  
79      LegacyDetector(MetadataRepository metadataRepository, DataDictionaryService dataDictionaryService) {
80          Validate.notNull(metadataRepository, "The metadataRepository must not be null");
81          Validate.notNull(dataDictionaryService, "The dataDictionaryService must not be null");
82          this.metadataRepository = metadataRepository;
83          this.dataDictionaryService = dataDictionaryService;
84      }
85  
86      public boolean isInLegacyContext() {
87          return legacyContext.get() != null;
88      }
89  
90      public void beginLegacyContext() {
91          Integer count = legacyContext.get();
92          if (count == null) {
93              // if attempting to establish legacy context, the legacy data framework must be enabled
94              if (!isLegacyDataFrameworkEnabled()) {
95                  throw new IllegalStateException(
96                          "Attempting to enter legacy data context without the legacy data framework enabled."
97                                  + " To enable, please load the KNS module or set "
98                                  + KRADConstants.Config.ENABLE_LEGACY_DATA_FRAMEWORK
99                                  + " config property to 'true'.");
100             }
101             legacyContext.set(new Integer(0));
102         } else {
103             legacyContext.set(new Integer(count.intValue() + 1));
104         }
105     }
106 
107     public void endLegacyContext() {
108         Integer count = legacyContext.get();
109         if (count == null) {
110             throw new IllegalStateException("Attempting to end a non-existent legacy context!");
111         } else if (count.intValue() == 0) {
112             legacyContext.set(null);
113         } else {
114             legacyContext.set(new Integer(count.intValue() - 1));
115         }
116     }
117 
118     public boolean isLegacyManaged(Class<?> type) {
119         BusinessObjectEntry businessObjectEntry =
120                 dataDictionaryService.getDataDictionary().getBusinessObjectEntry(type.getName());
121         return businessObjectEntry != null || isOjbLoadedClass(type);
122     }
123 
124     /**
125      * A type is considered krad-data managed if it is included in the metadata repository.
126      * @param type data type
127      * @return true if the type is krad-data managed
128      */
129     public boolean isKradDataManaged(Class<?> type) {
130         return metadataRepository.contains(type);
131     }
132 
133     /**
134      * Returns whether or not the KNS module of Rice has been loaded. This is based on whether or not the KNS_ENABLED
135      * flag has been set in the Config by the KRADConfigurer.
136      *
137      * @return true if the KNS module has been loaded, false otherwsie
138      */
139     public boolean isKnsEnabled() {
140         return ConfigContext.getCurrentContextConfig().getBooleanProperty(KRADConstants.Config.KNS_ENABLED, false);
141     }
142 
143     /**
144      * Return whether the legacy data framework is enabled.
145      *
146      * @return true if the legacy data framework has been enabled, false otherwise
147      */
148     public boolean isLegacyDataFrameworkEnabled() {
149         return ConfigContext.getCurrentContextConfig().getBooleanProperty(KRADConstants.Config.ENABLE_LEGACY_DATA_FRAMEWORK, isKnsEnabled());
150     }
151 
152     /**
153      * Determines whether the given class is loaded into OJB. Accesses the OJB metadata manager via reflection to avoid
154      * compile-time dependency. If null is passed to this method, it will always return false.
155      *
156      * @param dataObjectClass the data object class which may be loaded by the legacy framework
157      * @return true if the legacy data framework is present and has loaded the specified class, false otherwise
158      */
159     public boolean isOjbLoadedClass(Class<?> dataObjectClass) {
160         if (dataObjectClass == null) {
161             return false;
162         }
163         // some OJB objects may come in as proxies, we need to clear the CGLIB portion of the generated class name
164         // before we can check it properly
165         String dataObjectClassName = dataObjectClass.getName().replaceAll("\\$\\$EnhancerByCGLIB\\$\\$[0-9a-f]{0,8}", "" );
166         try {
167             dataObjectClass = Class.forName(dataObjectClassName);
168         } catch (ClassNotFoundException ex) {
169             LOG.warn( "Unable to resolve converted class name: " + dataObjectClassName + " from original: " + dataObjectClass + " -- Using as is" );
170         }
171         Boolean isLegacyLoaded = legacyLoadedCache.get(dataObjectClass);
172         if (isLegacyLoaded == null) {
173             if ( dataObjectClass.getPackage() != null
174                     && StringUtils.startsWith( dataObjectClass.getPackage().getName(), "org.apache.ojb." ) ) {
175                 isLegacyLoaded = Boolean.TRUE;
176             } else {
177                 try {
178                     Class<?> metadataManager = Class.forName(OJB_METADATA_MANAGER_CLASS, false, ClassUtils.getDefaultClassLoader());
179 
180                     // determine, via reflection, whether the legacy persistence layer has loaded the given class
181                     Object metadataManagerInstance = ReflectionUtils.invokeViaReflection(metadataManager, (Object) null, "getInstance", null);
182                     Validate.notNull(metadataManagerInstance , "unable to obtain " + OJB_METADATA_MANAGER_CLASS + " instance");
183 
184                     Object descriptorRepository = ReflectionUtils.invokeViaReflection(metadataManagerInstance, "getGlobalRepository", null);
185                     Validate.notNull(descriptorRepository, "unable to invoke legacy metadata provider (" + OJB_METADATA_MANAGER_CLASS + ")");
186 
187                     isLegacyLoaded = ReflectionUtils.invokeViaReflection(descriptorRepository, "hasDescriptorFor", new Class[] { Class.class }, dataObjectClass);
188 
189                 } catch (ClassNotFoundException e) {
190                     // the legacy provider does not exist, so this class can't possibly have been loaded through it
191                     isLegacyLoaded = Boolean.FALSE;
192                 }
193             }
194             legacyLoadedCache.put(dataObjectClass, isLegacyLoaded);
195         }
196         return isLegacyLoaded.booleanValue();
197     }
198 
199     /**
200      * Return whether objects of the given class should be handled via the legacy data framework
201      * @param dataObjectClass the data object class
202      * @return whether objects of the given class should be handled via the legacy data framework
203      */
204     public boolean useLegacy(Class<?> dataObjectClass) {
205         // if we are in a legacy context, always use the legacy framework, if they are using stuff that's not mapped
206         // up properly then they are doing it wrong
207         boolean ojbLoadedClass = isOjbLoadedClass(dataObjectClass);
208         if (isInLegacyContext() && ojbLoadedClass) {
209             return true;
210         }
211         // if it's only loaded in legacy, then we can indicate to use the legacy framework
212         //ADDED hack to handle classes like PersonImpl that are not in OJB but are Legacy and should
213         //goto that adapter
214         if (isLegacyDataFrameworkEnabled() &&
215                 (ojbLoadedClass || isTransientBO(dataObjectClass)) &&
216                 !isKradDataManaged(dataObjectClass)) {
217             return true;
218         }
219         // default to non-legacy when in a non-legacy context
220         return false;
221     }
222 
223     /**
224      * Confirm if this is a BO that is not mapped by OJB but should be handled by the Legacy Adapter.
225      *
226      * @param dataObjectClass the data object class
227      *
228      * @return true if {@code dataObjectClass} should be handled by the Legacy Adapter, false otherwise
229      */
230     private boolean isTransientBO(Class dataObjectClass) {
231         boolean isTransientBo = false;
232 
233         List<String> transientClassNames = Lists.newArrayList(
234                 "org.kuali.rice.krad.bo.TransientBusinessObjectBase");
235 
236         try {
237             Object dataObject = dataObjectClass.newInstance();
238 
239             for (String transientClassName : transientClassNames) {
240                 Class<?> transientClass = Class.forName(transientClassName);
241 
242                 if (transientClass.isInstance(dataObject)) {
243                     isTransientBo = true;
244                     break;
245                 }
246             }
247         } catch (Exception e) {
248             return false;
249         }
250 
251         return isTransientBo;
252     }
253 
254     /**
255      * Return whether the object should be handled via the legacy data framework
256      * @param dataObject the data object
257      * @return whether the object should be handled via the legacy data framework
258      */
259     public boolean useLegacyForObject(Object dataObject) {
260         Validate.notNull(dataObject, "Data Object must not be null");
261         if (dataObject instanceof Class) {
262             throw new IllegalArgumentException("Passed a Class object to useLegacyForObject, call useLegacy instead!");
263         }
264         return useLegacy(dataObject.getClass());
265     }
266 }