View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.spring.implementation;
20  
21  import groovy.lang.GroovyClassLoader;
22  import java.lang.reflect.Modifier;
23  import java.lang.reflect.ParameterizedType;
24  import java.lang.reflect.Type;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.function.Consumer;
30  import java.util.function.Supplier;
31  import org.apache.commons.lang3.tuple.Pair;
32  import org.apache.syncope.common.lib.command.CommandArgs;
33  import org.apache.syncope.common.lib.policy.AccountRuleConf;
34  import org.apache.syncope.common.lib.policy.PasswordRuleConf;
35  import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
36  import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
37  import org.apache.syncope.common.lib.report.ReportConf;
38  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
39  import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
40  import org.apache.syncope.core.persistence.api.entity.Implementation;
41  import org.apache.syncope.core.provisioning.api.ImplementationLookup;
42  import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
43  import org.apache.syncope.core.provisioning.api.rules.AccountRule;
44  import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
45  import org.apache.syncope.core.provisioning.api.rules.PullCorrelationRule;
46  import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRule;
47  import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
48  import org.apache.syncope.core.spring.ApplicationContextProvider;
49  
50  public final class ImplementationManager {
51  
52      private static final GroovyClassLoader GROOVY_CLASSLOADER = new GroovyClassLoader();
53  
54      private static final Map<String, Class<?>> CLASS_CACHE = Collections.synchronizedMap(new HashMap<>());
55  
56      @SuppressWarnings("unchecked")
57      public static Optional<ReportJobDelegate> buildReportJobDelegate(
58              final Implementation impl,
59              final Supplier<ReportJobDelegate> cacheGetter,
60              final Consumer<ReportJobDelegate> cachePutter)
61              throws ClassNotFoundException {
62  
63          switch (impl.getEngine()) {
64              case GROOVY:
65                  return Optional.of(build(impl, cacheGetter, cachePutter));
66  
67              case JAVA:
68              default:
69                  ReportConf conf = POJOHelper.deserialize(impl.getBody(), ReportConf.class);
70                  Class<ReportJobDelegate> clazz =
71                          (Class<ReportJobDelegate>) ApplicationContextProvider.getApplicationContext().
72                                  getBean(ImplementationLookup.class).getReportClass(conf.getClass());
73  
74                  if (clazz == null) {
75                      return Optional.empty();
76                  }
77  
78                  ReportJobDelegate report = build(clazz, true, cacheGetter, cachePutter);
79                  report.setConf(conf);
80                  return Optional.of(report);
81          }
82      }
83  
84      @SuppressWarnings("unchecked")
85      public static Optional<AccountRule> buildAccountRule(
86              final Implementation impl,
87              final Supplier<AccountRule> cacheGetter,
88              final Consumer<AccountRule> cachePutter)
89              throws ClassNotFoundException {
90  
91          switch (impl.getEngine()) {
92              case GROOVY:
93                  return Optional.of(build(impl, cacheGetter, cachePutter));
94  
95              case JAVA:
96              default:
97                  AccountRuleConf conf = POJOHelper.deserialize(impl.getBody(), AccountRuleConf.class);
98                  Class<AccountRule> clazz = (Class<AccountRule>) ApplicationContextProvider.getApplicationContext().
99                          getBean(ImplementationLookup.class).getAccountRuleClass(conf.getClass());
100 
101                 if (clazz == null) {
102                     return Optional.empty();
103                 }
104 
105                 AccountRule rule = build(clazz, true, cacheGetter, cachePutter);
106                 rule.setConf(conf);
107                 return Optional.of(rule);
108         }
109     }
110 
111     @SuppressWarnings("unchecked")
112     public static Optional<PasswordRule> buildPasswordRule(
113             final Implementation impl,
114             final Supplier<PasswordRule> cacheGetter,
115             final Consumer<PasswordRule> cachePutter)
116             throws ClassNotFoundException {
117 
118         switch (impl.getEngine()) {
119             case GROOVY:
120                 return Optional.of(build(impl, cacheGetter, cachePutter));
121 
122             case JAVA:
123             default:
124                 PasswordRuleConf conf = POJOHelper.deserialize(impl.getBody(), PasswordRuleConf.class);
125                 Class<PasswordRule> clazz = (Class<PasswordRule>) ApplicationContextProvider.getApplicationContext().
126                         getBean(ImplementationLookup.class).getPasswordRuleClass(conf.getClass());
127 
128                 if (clazz == null) {
129                     return Optional.empty();
130                 }
131 
132                 PasswordRule rule = build(clazz, true, cacheGetter, cachePutter);
133                 rule.setConf(conf);
134                 return Optional.of(rule);
135         }
136     }
137 
138     @SuppressWarnings("unchecked")
139     public static Optional<PullCorrelationRule> buildPullCorrelationRule(
140             final Implementation impl,
141             final Supplier<PullCorrelationRule> cacheGetter,
142             final Consumer<PullCorrelationRule> cachePutter)
143             throws ClassNotFoundException {
144 
145         switch (impl.getEngine()) {
146             case GROOVY:
147                 return Optional.of(build(impl, cacheGetter, cachePutter));
148 
149             case JAVA:
150             default:
151                 PullCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PullCorrelationRuleConf.class);
152                 Class<PullCorrelationRule> clazz =
153                         (Class<PullCorrelationRule>) ApplicationContextProvider.getApplicationContext().
154                                 getBean(ImplementationLookup.class).getPullCorrelationRuleClass(conf.getClass());
155 
156                 if (clazz == null) {
157                     return Optional.empty();
158                 }
159 
160                 PullCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
161                 rule.setConf(conf);
162                 return Optional.of(rule);
163         }
164     }
165 
166     @SuppressWarnings("unchecked")
167     public static Optional<PushCorrelationRule> buildPushCorrelationRule(
168             final Implementation impl,
169             final Supplier<PushCorrelationRule> cacheGetter,
170             final Consumer<PushCorrelationRule> cachePutter)
171             throws ClassNotFoundException {
172 
173         switch (impl.getEngine()) {
174             case GROOVY:
175                 return Optional.of(build(impl, cacheGetter, cachePutter));
176 
177             case JAVA:
178             default:
179                 PushCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PushCorrelationRuleConf.class);
180                 Class<PushCorrelationRule> clazz =
181                         (Class<PushCorrelationRule>) ApplicationContextProvider.getApplicationContext().
182                                 getBean(ImplementationLookup.class).getPushCorrelationRuleClass(conf.getClass());
183 
184                 if (clazz == null) {
185                     return Optional.empty();
186                 }
187 
188                 PushCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
189                 rule.setConf(conf);
190                 return Optional.of(rule);
191         }
192     }
193 
194     @SuppressWarnings({ "unchecked", "rawtypes" })
195     private static Class<? extends CommandArgs> findCommandArgsClass(final Type type) {
196         if (type.getTypeName().startsWith(
197                 ImplementationTypesHolder.getInstance().getValues().get(IdRepoImplementationType.COMMAND) + "<")) {
198 
199             return (Class<? extends CommandArgs>) ((ParameterizedType) type).getActualTypeArguments()[0];
200         }
201 
202         if (type instanceof Class) {
203             for (Type i : ((Class) type).getGenericInterfaces()) {
204                 Class<? extends CommandArgs> r = findCommandArgsClass(i);
205                 if (r != null) {
206                     return r;
207                 }
208             }
209         }
210 
211         return null;
212     }
213 
214     public static CommandArgs emptyArgs(final Implementation impl) throws Exception {
215         if (!IdRepoImplementationType.COMMAND.equals(impl.getType())) {
216             throw new IllegalArgumentException("This method can be only called on implementations");
217         }
218 
219         Class<Object> commandClass = getClass(impl).getLeft();
220 
221         Class<? extends CommandArgs> commandArgsClass = findCommandArgsClass(commandClass);
222         if (commandArgsClass != null
223                 && (commandArgsClass.getEnclosingClass() == null
224                 || Modifier.isStatic(commandArgsClass.getModifiers()))) {
225 
226             return commandArgsClass.getDeclaredConstructor().newInstance();
227         }
228 
229         throw new IllegalArgumentException(
230                 CommandArgs.class.getName() + " shall be either declared as independent or nested static");
231     }
232 
233     @SuppressWarnings("unchecked")
234     private static <T> Pair<Class<T>, Boolean> getClass(final Implementation impl) throws ClassNotFoundException {
235         if (CLASS_CACHE.containsKey(impl.getKey())) {
236             return Pair.of((Class<T>) CLASS_CACHE.get(impl.getKey()), true);
237         }
238 
239         Class<?> clazz;
240         switch (impl.getEngine()) {
241             case GROOVY:
242                 clazz = GROOVY_CLASSLOADER.parseClass(impl.getBody());
243                 break;
244 
245             case JAVA:
246             default:
247                 clazz = Class.forName(impl.getBody());
248         }
249 
250         CLASS_CACHE.put(impl.getKey(), clazz);
251         return Pair.of((Class<T>) clazz, false);
252     }
253 
254     @SuppressWarnings("unchecked")
255     public static <T> T build(final Implementation impl) throws ClassNotFoundException {
256         return (T) ApplicationContextProvider.getBeanFactory().createBean(getClass(impl).getLeft());
257     }
258 
259     @SuppressWarnings("unchecked")
260     private static <T> T build(
261             final Class<T> clazz,
262             final boolean classCached,
263             final Supplier<T> cacheGetter,
264             final Consumer<T> cachePutter) {
265 
266         boolean perContext = Optional.ofNullable(clazz.getAnnotation(SyncopeImplementation.class)).
267                 map(ann -> ann.scope() == InstanceScope.PER_CONTEXT).
268                 orElse(true);
269         T instance = null;
270         if (perContext && classCached) {
271             instance = cacheGetter.get();
272         }
273         if (instance == null) {
274             instance = ApplicationContextProvider.getBeanFactory().createBean(clazz);
275 
276             if (perContext) {
277                 cachePutter.accept(instance);
278             }
279         }
280 
281         return instance;
282     }
283 
284     public static <T> T build(final Implementation impl, final Supplier<T> cacheGetter, final Consumer<T> cachePutter)
285             throws ClassNotFoundException {
286 
287         Pair<Class<T>, Boolean> clazz = getClass(impl);
288 
289         return build(clazz.getLeft(), clazz.getRight(), cacheGetter, cachePutter);
290     }
291 
292     public static Class<?> purge(final String implementation) {
293         return CLASS_CACHE.remove(implementation);
294     }
295 
296     private ImplementationManager() {
297         // private constructor for static utility class
298     }
299 }