1 // Copyright 2012 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.tools
.development
.agent
.runtime
;
4 import com
.google
.apphosting
.api
.AppEngineInternal
;
6 import java
.util
.Collections
;
7 import java
.util
.HashMap
;
8 import java
.util
.LinkedList
;
11 import java
.util
.logging
.Logger
;
14 * The only reason this class is split out from {@link Runtime} is so that we
15 * can have tests that don't need to worry about Runtime's crazy static
16 * initialization business.
20 private static final Logger logger
= Logger
.getLogger(RuntimeHelper
.class.getName());
22 static final Map
<String
, AppEngineInternal
> internalAnnotationCache
=
23 Collections
.synchronizedMap(new HashMap
<String
, AppEngineInternal
>());
25 private static final boolean appEngineInternalOnClasspath
;
28 boolean exists
= false;
30 AppEngineInternal
.class.getName();
32 } catch (NoClassDefFoundError ncdf
) {
34 appEngineInternalOnClasspath
= exists
;
37 private RuntimeHelper() { }
40 * Check to see if the class identified by {@code classStr} is a restricted
41 * class. If it is, either output a warning or throw
42 * {@link NoClassDefFoundError}, depending on the value of
43 * {@code violationIsError}.
45 * @param violationIsError If {@code true}, throw
46 * {@link NoClassDefFoundError} if the class is restricted, otherwise log a
48 * @param classStr The class to check.
49 * @param callingClassStr The class that is referencing {@code classStr}.
50 * @param callingClassCodeSource The codesource of the class that is
51 * referencing {@code classStr}.
53 public static void checkRestricted(boolean violationIsError
, String classStr
,
54 String callingClassStr
, String callingClassCodeSource
) {
55 if (!appEngineInternalOnClasspath
) {
58 if (classStr
.startsWith("java.") || classStr
.startsWith("javax.")) {
62 Class
<?
> cls
= Class
.forName(classStr
);
63 AppEngineInternal anno
= getAppEngineInternalAnnotation(cls
);
65 String errorMsg
= String
.format("Class %s loaded from %s has a dependency on class %s "
66 + "loaded from %s, which is not part of App Engine's supported API.", callingClassStr
,
67 callingClassCodeSource
, cls
.getName(), cls
.getProtectionDomain().getCodeSource());
68 if (!anno
.message().isEmpty()) {
69 errorMsg
+= "\n" + anno
.message();
71 if (violationIsError
) {
72 throw new NoClassDefFoundError(errorMsg
);
74 logger
.warning(errorMsg
+ "\nYou are strongly discouraged "
75 + "from using this class - your app may stop working in production at any moment.");
78 } catch (ClassNotFoundException e
) {
83 * @param cls The for which we are trying to locate the annotation.
84 * @return The annotation. Can be null.
86 static AppEngineInternal
getAppEngineInternalAnnotation(Class
<?
> cls
) {
87 List
<String
> namesExamined
= new LinkedList
<String
>();
88 String name
= cls
.getName();
89 boolean firstPass
= true;
90 while (name
!= null) {
91 if (internalAnnotationCache
.containsKey(name
)) {
92 AppEngineInternal anno
= internalAnnotationCache
.get(name
);
93 updateInternalAnnotationCache(namesExamined
, anno
);
97 namesExamined
.add(name
);
99 cls
= Class
.forName(name
+ ".package-info");
102 AppEngineInternal anno
= cls
.getAnnotation(AppEngineInternal
.class);
104 updateInternalAnnotationCache(namesExamined
, anno
);
107 } catch (ClassNotFoundException cnfe
) {
109 name
= getOwningPackage(name
);
112 updateInternalAnnotationCache(namesExamined
, null);
117 * Update the internal annotation cache. Since the cache represents a tree
118 * hierarchy, we want to update the entire branch we just walked. Consider
119 * the case of starting at package a.b.c.d and finding the annotation at
120 * a.b. We want to cache the annotation at a.b, a.b.c and a.b.c.d. This
121 * way if our next lookup just starts at a.b.c we will find it immediately.
123 * @param namesToUpdate The list of names to update with the annotation.
124 * @param anno The annotation that we found. May be null.
126 static void updateInternalAnnotationCache(List
<String
> namesToUpdate
,
127 AppEngineInternal anno
) {
128 for (String name
: namesToUpdate
) {
129 internalAnnotationCache
.put(name
, anno
);
134 * Get the name of the package that owns the provided resource.
135 * The resource can be another package or a class name. Returns {@code null}
136 * if there is no owning package.
138 static String
getOwningPackage(String resource
) {
139 int lastDot
= resource
.lastIndexOf('.');
143 return resource
.substring(0, lastDot
);