Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / agent / runtime / RuntimeHelper.java
blob19d73e5bc053339ebfc4c66f51664724c14e5334
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;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.logging.Logger;
13 /**
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.
19 class RuntimeHelper {
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;
27 static {
28 boolean exists = false;
29 try {
30 AppEngineInternal.class.getName();
31 exists = true;
32 } catch (NoClassDefFoundError ncdf) {
34 appEngineInternalOnClasspath = exists;
37 private RuntimeHelper() { }
39 /**
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
47 * warning.
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) {
56 return;
58 if (classStr.startsWith("java.") || classStr.startsWith("javax.")) {
59 return;
61 try {
62 Class<?> cls = Class.forName(classStr);
63 AppEngineInternal anno = getAppEngineInternalAnnotation(cls);
64 if (anno != null) {
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);
73 } else {
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) {
82 /**
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);
94 return anno;
96 try {
97 namesExamined.add(name);
98 if (!firstPass) {
99 cls = Class.forName(name + ".package-info");
100 } else {
102 AppEngineInternal anno = cls.getAnnotation(AppEngineInternal.class);
103 if (anno != null) {
104 updateInternalAnnotationCache(namesExamined, anno);
105 return anno;
107 } catch (ClassNotFoundException cnfe) {
109 name = getOwningPackage(name);
110 firstPass = false;
112 updateInternalAnnotationCache(namesExamined, null);
113 return 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('.');
140 if (lastDot == -1) {
141 return null;
143 return resource.substring(0, lastDot);