001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.inject;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023import org.apache.juneau.commons.reflect.*;
024
025/**
026 * FQN-based support for JSR-style annotation detection.
027 *
028 * <p>
029 * Centralizes all framework annotation-name matching for:
030 * <ul>
031 *    <li>JSR-330 style injection annotations (<c>jakarta.inject</c> / <c>javax.inject</c>).
032 *    <li>JSR-250 lifecycle annotations (<c>jakarta.annotation</c> / <c>javax.annotation</c>).
033 *    <li>Spring's <c>Autowired</c> annotation.
034 * </ul>
035 *
036 * <p>
037 * Juneau intentionally matches by annotation class name so modules can interoperate with these
038 * annotations without compile-time dependencies on their APIs.
039 */
040public final class JsrSupport {
041
042   // JSR-330 / Spring.
043   public static final String JUNEAU_INJECT = "org.apache.juneau.commons.inject.Inject";
044   public static final String JAKARTA_INJECT = "jakarta.inject.Inject";
045   public static final String JAVAX_INJECT = "javax.inject.Inject";
046   public static final String SPRING_AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
047   public static final String JUNEAU_NAMED = "org.apache.juneau.commons.inject.Named";
048   public static final String JAKARTA_NAMED = "jakarta.inject.Named";
049   public static final String JAVAX_NAMED = "javax.inject.Named";
050   public static final String JUNEAU_QUALIFIER = "org.apache.juneau.commons.inject.Qualifier";
051   public static final String JAKARTA_QUALIFIER = "jakarta.inject.Qualifier";
052   public static final String JAVAX_QUALIFIER = "javax.inject.Qualifier";
053   public static final String JUNEAU_SINGLETON = "org.apache.juneau.commons.inject.Singleton";
054   public static final String JAKARTA_SINGLETON = "jakarta.inject.Singleton";
055   public static final String JAVAX_SINGLETON = "javax.inject.Singleton";
056   public static final String JUNEAU_PROVIDER = "org.apache.juneau.commons.inject.Provider";
057   public static final String JAKARTA_PROVIDER = "jakarta.inject.Provider";
058   public static final String JAVAX_PROVIDER = "javax.inject.Provider";
059
060   // JSR-250 lifecycle.
061   public static final String JUNEAU_POSTCONSTRUCT = "org.apache.juneau.commons.inject.PostConstruct";
062   public static final String JAKARTA_POSTCONSTRUCT = "jakarta.annotation.PostConstruct";
063   public static final String JAVAX_POSTCONSTRUCT = "javax.annotation.PostConstruct";
064   public static final String JUNEAU_PREDESTROY = "org.apache.juneau.commons.inject.PreDestroy";
065   public static final String JAKARTA_PREDESTROY = "jakarta.annotation.PreDestroy";
066   public static final String JAVAX_PREDESTROY = "javax.annotation.PreDestroy";
067
068   // Config-value injection (Juneau + Spring).  Detected by FQN so juneau-commons stays free of a
069   // compile-time Spring dependency, mirroring the SPRING_AUTOWIRED trick above.
070   public static final String JUNEAU_VALUE = "org.apache.juneau.commons.inject.Value";
071   public static final String SPRING_VALUE = "org.springframework.beans.factory.annotation.Value";
072
073   // Jakarta Bean Validation 3.x (and the older javax. namespace).  Detected by FQN so juneau-commons
074   // stays free of a compile-time jakarta.validation dependency; the opt-in marker is recognized whether
075   // the consumer pulls in jakarta.validation-api 3.x, javax.validation:validation-api 2.x, or Spring's
076   // own @Validated.
077   public static final String JAKARTA_VALID = "jakarta.validation.Valid";
078   public static final String JAVAX_VALID = "javax.validation.Valid";
079   public static final String SPRING_VALIDATED = "org.springframework.validation.annotation.Validated";
080
081   private JsrSupport() {}
082
083   /**
084    * Returns <jk>true</jk> if the annotation is an inject marker.
085    *
086    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
087    * @return <jk>true</jk> if the annotation is an inject marker.
088    */
089   public static boolean isInjectAnnotation(AnnotationInfo<?> annotation) {
090      var name = annotation.getName();
091      return eqa(name, JUNEAU_INJECT, JAKARTA_INJECT, JAVAX_INJECT, SPRING_AUTOWIRED);
092   }
093
094   /**
095    * Returns <jk>true</jk> if the annotation is a named qualifier.
096    *
097    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
098    * @return <jk>true</jk> if the annotation is a named qualifier.
099    */
100   public static boolean isNamedAnnotation(AnnotationInfo<?> annotation) {
101      var name = annotation.getName();
102      return eqa(name, JUNEAU_NAMED, JAKARTA_NAMED, JAVAX_NAMED);
103   }
104
105   /**
106    * Returns <jk>true</jk> if this annotation type itself is annotated with a qualifier marker.
107    *
108    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
109    * @return <jk>true</jk> if this annotation type itself is annotated with a qualifier marker.
110    */
111   public static boolean isQualifierMeta(AnnotationInfo<?> annotation) {
112      return Arrays.stream(annotation.annotationType().getAnnotations())
113         .map(a -> a.annotationType().getName())
114         .anyMatch(n -> eqa(n, JUNEAU_QUALIFIER, JAKARTA_QUALIFIER, JAVAX_QUALIFIER));
115   }
116
117   /**
118    * Returns <jk>true</jk> if the annotation is a singleton marker.
119    *
120    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
121    * @return <jk>true</jk> if the annotation is a singleton marker.
122    */
123   public static boolean isSingletonAnnotation(AnnotationInfo<?> annotation) {
124      var name = annotation.getName();
125      return eqa(name, JUNEAU_SINGLETON, JAKARTA_SINGLETON, JAVAX_SINGLETON);
126   }
127
128   /**
129    * Returns <jk>true</jk> if the class is a supported provider interface.
130    *
131    * @param type The class to inspect.  Can be <jk>null</jk>, in which case <jk>false</jk> is returned.
132    * @return <jk>true</jk> if the class is a supported provider interface.
133    */
134   public static boolean isProviderType(Class<?> type) {
135      return type != null && eqa(type.getName(), JUNEAU_PROVIDER, JAKARTA_PROVIDER, JAVAX_PROVIDER);
136   }
137
138   /**
139    * Returns <jk>true</jk> if the annotation is a post-construct lifecycle marker.
140    *
141    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
142    * @return <jk>true</jk> if the annotation is a post-construct lifecycle marker.
143    */
144   public static boolean isPostConstructAnnotation(AnnotationInfo<?> annotation) {
145      var name = annotation.getName();
146      return eqa(name, JUNEAU_POSTCONSTRUCT, JAKARTA_POSTCONSTRUCT, JAVAX_POSTCONSTRUCT);
147   }
148
149   /**
150    * Returns <jk>true</jk> if the annotation is a pre-destroy lifecycle marker.
151    *
152    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
153    * @return <jk>true</jk> if the annotation is a pre-destroy lifecycle marker.
154    */
155   public static boolean isPreDestroyAnnotation(AnnotationInfo<?> annotation) {
156      var name = annotation.getName();
157      return eqa(name, JUNEAU_PREDESTROY, JAKARTA_PREDESTROY, JAVAX_PREDESTROY);
158   }
159
160   /**
161    * Reads qualifier value from an annotation when supported.
162    *
163    * <p>
164    * For <c>@Named</c>, returns the annotation value directly.  For qualifier-meta annotations,
165    * this returns the annotation's own <c>value()</c> attribute if present and string-typed.
166    *
167    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
168    * @return The qualifier value, or <jk>null</jk> if the annotation carries no qualifier value.
169    */
170   public static String qualifierValue(AnnotationInfo<?> annotation) {
171      if (isNamedAnnotation(annotation))
172         return annotation.getValue().orElse(null);
173      if (isQualifierMeta(annotation))
174         return annotation.getValue().orElse(null);
175      return null;
176   }
177
178   /**
179    * Returns <jk>true</jk> if the annotation is a config-value marker.
180    *
181    * <p>
182    * Recognizes both Juneau's {@code @Value} and Spring's
183    * {@code org.springframework.beans.factory.annotation.Value} by FQN — no compile-time Spring
184    * dependency in {@code juneau-commons}.
185    *
186    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
187    * @return <jk>true</jk> if {@code annotation} is one of the recognized {@code @Value} variants.
188    */
189   public static boolean isValueAnnotation(AnnotationInfo<?> annotation) {
190      var name = annotation.getName();
191      return eqa(name, JUNEAU_VALUE, SPRING_VALUE);
192   }
193
194   /**
195    * Returns the configuration expression carried by a {@code @Value} annotation, or {@code null}
196    * if the annotation is not a recognized {@code @Value}.
197    *
198    * <p>
199    * Both Juneau's {@code @Value} and Spring's {@code @Value} expose a single {@code String value()}
200    * attribute, so a uniform {@code getValue()} lookup against the annotation works for either.
201    *
202    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
203    * @return The {@code value()} string, or {@code null} if {@code annotation} is not a {@code @Value}.
204    */
205   public static String valueExpression(AnnotationInfo<?> annotation) {
206      if (! isValueAnnotation(annotation))
207         return null;
208      return annotation.getValue().orElse(null);
209   }
210
211   /**
212    * Returns <jk>true</jk> if the annotation is a Jakarta Bean Validation opt-in marker.
213    *
214    * <p>
215    * Recognizes Jakarta 3.x ({@code jakarta.validation.Valid}), the older Javax 2.x equivalent
216    * ({@code javax.validation.Valid}), and Spring's {@code @Validated} group-selector by FQN &mdash; no
217    * compile-time Jakarta Validation or Spring dependency in {@code juneau-commons}.
218    *
219    * @param annotation The annotation to inspect.  Must not be <jk>null</jk>.
220    * @return <jk>true</jk> if {@code annotation} is one of the recognized validation opt-in markers.
221    */
222   public static boolean isValidAnnotation(AnnotationInfo<?> annotation) {
223      var name = annotation.getName();
224      return eqa(name, JAKARTA_VALID, JAVAX_VALID, SPRING_VALIDATED);
225   }
226}