提交 21cbf231 编写于 作者: K kohsuke

integrated stapler 1.29. JSON/XML export support is moved to stapler.


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@3108 71c3de6d-444a-0410-be80-ed276b4c234a
上级 a521969f
package hudson.api;
/**
* Interface that an exposed bean can implement, to do the equivalent
* of <tt>writeReplace</tt> in Java serialization.
* @author Kohsuke Kawaguchi
*/
public interface CustomExposureBean {
/**
* The returned object will be introspected and written as JSON/XML.
*/
Object toExposedObject();
}
package hudson.api;
import java.io.IOException;
/**
* Receives the event callback on the model data to be exposed.
*
* <p>
* The call sequence is:
*
* <pre>
* EVENTS := startObject PROPERTY* endObject
* PROPERTY := name VALUE
* VALUE := valuePrimitive
* | value
* | valueNull
* | startArray VALUE* endArray
* | EVENTS
* </pre>
*
* @author Kohsuke Kawaguchi
*/
public interface DataWriter {
void name(String name) throws IOException;
void valuePrimitive(Object v) throws IOException;
void value(String v) throws IOException;
void valueNull() throws IOException;
void startArray() throws IOException;
void endArray() throws IOException;
void startObject() throws IOException;
void endObject() throws IOException;
}
package hudson.api;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Mark the field or the getter method whose value gets exposed
* to the remote API.
*
* @author Kohsuke Kawaguchi
* @see ExposedBean
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({FIELD, METHOD})
public @interface Exposed {
/**
* Controls how visible this property is.
*
* <p>
* If the value is 1, the property will be
* visible only when the current model object is exposed as the
* top-level object.
* <p>
* If the value is 2, in addition to above, the property will still
* be visible if the current model object is exposed as the 2nd-level
* object.
* <p>
* And the rest goes in the same way. If the value is N, the object
* is exposed as the Nth level object.
*
* <p>
* The default value of this property is determined by
* {@link ExposedBean#defaultVisibility()}.
*
* <p>
* So bigger the number, more important the property is.
*/
int visibility() default 0;
/**
* Name of the exposed property.
* <p>
* This token is used as the XML element name or the JSON property name.
* The default is to use the Java property name.
*/
String name() default "";
}
package hudson.api;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that the class has {@link Exposed} annotations
* on its properties to indicate which properties are written
* as values to the remote XML/JSON API.
*
* <p>
* This annotation inherits, so it only needs to be placed on the base class.
*
* @author Kohsuke Kawaguchi
* @see Exposed
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Target(ElementType.TYPE)
public @interface ExposedBean {
/**
* Controls the default visibility of all {@link Exposed} properties
* of this class (and its descendants.)
*
* <p>
* A big default visibility value usually indicates that the bean
* is always exposed as a descendant of another bean. In such case,
* unless the default visibility is set no property will be exposed.
*/
int defaultVisibility() default 1;
}
package hudson.api;
import java.lang.reflect.Field;
/**
* {@link Property} based on {@link Field}.
* @author Kohsuke Kawaguchi
*/
class FieldProperty extends Property {
private final Field field;
public FieldProperty(Parser owner, Field field, Exposed exposed) {
super(owner,field.getName(),exposed);
this.field = field;
}
protected Object getValue(Object object) throws IllegalAccessException {
return field.get(object);
}
}
package hudson.api;
import java.beans.Introspector;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* {@link Property} based on {@link Method}.
* @author Kohsuke Kawaguchi
*/
public class MethodProperty extends Property {
private final Method method;
MethodProperty(Parser owner, Method m, Exposed exposed) {
super(owner,buildName(m.getName()),exposed);
this.method = m;
}
private static String buildName(String name) {
if(name.startsWith("get"))
name = name.substring(3);
else
if(name.startsWith("is"))
name = name.substring(2);
return Introspector.decapitalize(name);
}
protected Object getValue(Object object) throws IllegalAccessException, InvocationTargetException {
return method.invoke(object);
}
}
package hudson.api;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.io.IOException;
/**
* Writes all the property of one {@link ExposedBean} to {@link DataWriter}.
*
* @author Kohsuke Kawaguchi
*/
public class Parser<T> {
private final Class<T> type;
/**
* {@link Parser} for the super class.
*/
private final Parser<? super T> superParser;
private final Property[] properties;
/*package*/ final ParserBuilder parent;
/*package*/ final int defaultVisibility;
/*package*/ Parser(ParserBuilder parent, Class<T> type) {
this.parent = parent;
this.type = type;
ExposedBean eb = type.getAnnotation(ExposedBean.class);
if(eb ==null)
throw new IllegalArgumentException(type+" doesn't have @ExposedBean");
this.defaultVisibility = eb.defaultVisibility();
parent.parsers.put(type,this);
Class<? super T> sc = type.getSuperclass();
if(sc!=null && sc.getAnnotation(ExposedBean.class)!=null)
superParser = parent.get(sc);
else
superParser = null;
List<Property> properties = new ArrayList<Property>();
// Use reflection to find out what properties are exposed.
for( Field f : type.getFields() ) {
if(f.getDeclaringClass()!=type) continue;
Exposed exposed = f.getAnnotation(Exposed.class);
if(exposed !=null)
properties.add(new FieldProperty(this,f,exposed));
}
for( Method m : type.getMethods() ) {
if(m.getDeclaringClass()!=type) continue;
Exposed exposed = m.getAnnotation(Exposed.class);
if(exposed !=null)
properties.add(new MethodProperty(this,m,exposed));
}
this.properties = properties.toArray(new Property[properties.size()]);
Arrays.sort(this.properties);
}
/**
* Writes the property values of the given object to the writer.
*/
public void writeTo(T object, DataWriter writer) throws IOException {
writer.startObject();
writeTo(object,1,writer);
writer.endObject();
}
void writeTo(T object, int depth, DataWriter writer) throws IOException {
if(superParser!=null)
superParser.writeTo(object,depth,writer);
for (Property p : properties)
p.writeTo(object,depth,writer);
}
}
package hudson.api;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Creates and maintains {@link Parser}s, that are used to write out
* the value representation of {@link ExposedBean exposed beans}.
* @author Kohsuke Kawaguchi
*/
public class ParserBuilder {
/**
* Instanciated {@link Parser}s.
* Registration happens in {@link Parser#Parser(ParserBuilder,Class)} so that cyclic references
* are handled correctly.
*/
/*package*/ final Map<Class,Parser> parsers = new ConcurrentHashMap<Class,Parser>();
public <T> Parser<T> get(Class<T> type) {
Parser p = parsers.get(type);
if(p==null) {
synchronized(this) {
p = parsers.get(type);
if(p==null)
p = new Parser<T>(this,type);
}
}
return p;
}
}
package hudson.api;
import hudson.util.IOException2;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Exposes one {@link Exposed exposed property} of {@link ExposedBean} to
* {@link DataWriter}.
*
* @author Kohsuke Kawaguchi
*/
abstract class Property implements Comparable<Property> {
final String name;
final ParserBuilder owner;
final int visibility;
Property(Parser parent, String name, Exposed exposed) {
this.owner = parent.parent;
this.name = exposed.name().length()>1 ? exposed.name() : name;
int v = exposed.visibility();
if(v==0)
v = parent.defaultVisibility;
this.visibility = v;
}
public int compareTo(Property that) {
return this.name.compareTo(that.name);
}
/**
* Writes one property of the given object to {@link DataWriter}.
*/
public void writeTo(Object object, int depth, DataWriter writer) throws IOException {
if(visibility<depth) return; // not visible
try {
writer.name(name);
writeValue(getValue(object),depth,writer);
} catch (IllegalAccessException e) {
throw new IOException2("Failed to write "+name,e);
} catch (InvocationTargetException e) {
throw new IOException2("Failed to write "+name,e);
}
}
/**
* Writes one value of the property to {@link DataWriter}.
*/
private void writeValue(Object value, int depth, DataWriter writer) throws IOException {
if(value==null) {
writer.valueNull();
return;
}
if(value instanceof CustomExposureBean) {
writeValue(((CustomExposureBean)value).toExposedObject(),depth,writer);
return;
}
Class c = value.getClass();
if(STRING_TYPES.contains(c)) {
writer.value(value.toString());
return;
}
if(PRIMITIVE_TYPES.contains(c)) {
writer.valuePrimitive(value);
return;
}
if(c.getComponentType()!=null) { // array
writer.startArray();
for (Object item : (Object[]) value)
writeValue(item,depth,writer);
writer.endArray();
return;
}
if(value instanceof Collection) {
writer.startArray();
for (Object item : (Collection) value)
writeValue(item,depth,writer);
writer.endArray();
return;
}
if(value instanceof Map) {
writer.startObject();
for (Map.Entry e : ((Map<?,?>) value).entrySet()) {
writer.name(e.getKey().toString());
writeValue(e.getValue(),depth,writer);
}
writer.endObject();
return;
}
if(value instanceof Calendar) {
writer.valuePrimitive(((Calendar) value).getTimeInMillis());
return;
}
if(value instanceof Enum) {
writer.value(value.toString());
return;
}
// otherwise handle it as a bean
writer.startObject();
owner.get(c).writeTo(value,depth+1,writer);
writer.endObject();
}
/**
* Gets the value of this property from the bean.
*/
protected abstract Object getValue(Object bean) throws IllegalAccessException, InvocationTargetException;
private static final Set<Class> STRING_TYPES = new HashSet<Class>(Arrays.asList(
String.class,
URL.class
));
private static final Set<Class> PRIMITIVE_TYPES = new HashSet<Class>(Arrays.asList(
Integer.class,
Long.class,
Boolean.class
));
}
/**
* Hudson remote XML/JSON API support.
*
* <h2>Design</h2>
* <p>
* Hudson provides the JSON/XML dump of its model objects
* (mostly {@link ModelObject}s but can be others.) See
* <a href="https://hudson.deva.java.net/remote-api.html"> for the user-level
* documentation of this feature.
*
* <p>
* This mechanism is implemented by marking model objects
* by using {@link ExposedBean} and {@link Exposed} annotations.
* Those annotation together designates what properties are exposed
* to those remote APIs.
*
* <p>
* So generally speaking the model classes only need to annotate themselves
* by those annotations to get their data exposed to the remote API.
*
* @since 1.101
*/
package hudson.api;
import hudson.model.ModelObject;
\ No newline at end of file
......@@ -3,7 +3,7 @@ package hudson.model;
import hudson.Launcher;
import hudson.Proc.LocalProc;
import hudson.Util;
import hudson.api.Exposed;
import org.kohsuke.stapler.export.Exposed;
import hudson.tasks.Fingerprinter.FingerprintAction;
import hudson.tasks.test.AbstractTestResultAction;
import hudson.maven.MavenBuild;
......
......@@ -2,8 +2,8 @@ package hudson.model;
import hudson.XmlFile;
import hudson.Util;
import hudson.api.Exposed;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import java.io.File;
import java.io.IOException;
......
package hudson.model;
import hudson.api.DataWriter;
import hudson.api.Exposed;
import hudson.api.Parser;
import hudson.api.ParserBuilder;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exposed;
import org.kohsuke.stapler.export.Flavor;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Stack;
import java.beans.Introspector;
/**
* Used to expose remote access API for ".../api/"
......@@ -36,143 +31,14 @@ public class Api extends AbstractModelObject {
/**
* Exposes the bean as XML.
*/
public void doXml(StaplerRequest req, final StaplerResponse rsp) throws IOException {
rsp.setContentType("application/xml;charset=UTF-8");
write(new DataWriter() {
private String name = Introspector.decapitalize(bean.getClass().getSimpleName());
private final Stack<String> objectNames = new Stack<String>();
private final Stack<Boolean> arrayState = new Stack<Boolean>();
private final Writer out = rsp.getWriter();
public boolean isArray;
public void name(String name) {
this.name = name;
}
public void valuePrimitive(Object v) throws IOException {
value(v.toString());
}
public void value(String v) throws IOException {
String n = adjustName();
out.write('<'+n+'>');
out.write(v);
out.write("</"+n+'>');
}
public void valueNull() {
// use absence to indicate null.
}
public void startArray() {
// use repeated element to display array
// this means nested arrays are not supported
isArray = true;
}
public void endArray() {
isArray = false;
}
public void startObject() throws IOException {
objectNames.push(name);
arrayState.push(isArray);
out.write('<'+adjustName()+'>');
}
public void endObject() throws IOException {
name = objectNames.pop();
isArray = arrayState.pop();
out.write("</"+adjustName()+'>');
}
/**
* Returns the name to be used as an element name
* by considering {@link #isArray}
*/
private String adjustName() {
if(isArray) {
if(name.endsWith("s"))
return name.substring(0,name.length()-1);
}
return name;
}
});
public void doXml(StaplerRequest req, final StaplerResponse rsp) throws IOException, ServletException {
rsp.serveExposedBean(req,bean, Flavor.XML);
}
/**
* Exposes the bean as JSON.
*/
public void doJson(StaplerRequest req, final StaplerResponse rsp) throws IOException {
rsp.setContentType("text/javascript;charset=UTF-8");
String pad = req.getParameter("jsonp");
PrintWriter w = rsp.getWriter();
if(pad!=null) w.print(pad+'(');
write(new DataWriter() {
private boolean needComma;
private final Writer out = rsp.getWriter();
public void name(String name) throws IOException {
comma();
out.write(name+':');
needComma = false;
}
private void data(String v) throws IOException {
comma();
out.write(v);
}
private void comma() throws IOException {
if(needComma) out.write(',');
needComma = true;
}
public void valuePrimitive(Object v) throws IOException {
data(v.toString());
}
public void value(String v) throws IOException {
data('\"'+v+'\"');
}
public void valueNull() throws IOException {
data("null");
}
public void startArray() throws IOException {
comma();
out.write('[');
needComma = false;
}
public void endArray() throws IOException {
out.write(']');
needComma = true;
}
public void startObject() throws IOException {
comma();
out.write('{');
needComma=false;
}
public void endObject() throws IOException {
out.write('}');
needComma=true;
}
});
if(pad!=null) w.print(')');
public void doJson(StaplerRequest req, final StaplerResponse rsp) throws IOException, ServletException {
rsp.serveExposedBean(req,bean, Flavor.JSON);
}
private void write(DataWriter writer) throws IOException {
Parser p = parserBuilder.get(bean.getClass());
p.writeTo(bean,writer);
}
private static final ParserBuilder parserBuilder = new ParserBuilder();
}
......@@ -13,7 +13,7 @@ import hudson.TcpSlaveAgentListener;
import hudson.Util;
import static hudson.Util.fixEmpty;
import hudson.XmlFile;
import hudson.api.Exposed;
import org.kohsuke.stapler.export.Exposed;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.JobListener;
......
......@@ -2,8 +2,7 @@ package hudson.model;
import hudson.ExtensionPoint;
import hudson.Util;
import hudson.api.Exposed;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import hudson.model.Descriptor.FormException;
import hudson.tasks.BuildTrigger;
import hudson.tasks.LogRotator;
......
package hudson.model;
import hudson.Util;
import hudson.api.ExposedBean;
import hudson.api.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import hudson.model.Node.Mode;
import hudson.util.OneShotEvent;
......
......@@ -5,7 +5,7 @@ import com.thoughtworks.xstream.converters.basic.AbstractBasicConverter;
import java.io.Serializable;
import hudson.api.CustomExposureBean;
import org.kohsuke.stapler.export.CustomExposureBean;
/**
* The build outcome.
......
......@@ -8,8 +8,8 @@ import hudson.FilePath;
import hudson.Util;
import static hudson.Util.combine;
import hudson.XmlFile;
import hudson.api.Exposed;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import hudson.tasks.BuildStep;
import hudson.tasks.LogRotator;
import hudson.tasks.test.AbstractTestResultAction;
......@@ -17,7 +17,6 @@ import hudson.util.IOException2;
import hudson.util.XStream2;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.Stapler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
......
......@@ -4,8 +4,8 @@ import com.thoughtworks.xstream.XStream;
import hudson.CopyOnWrite;
import hudson.FeedAdapter;
import hudson.XmlFile;
import hudson.api.ExposedBean;
import hudson.api.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import hudson.model.Descriptor.FormException;
import hudson.scm.ChangeLogSet;
import hudson.util.RunList;
......
package hudson.model;
import hudson.Util;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.ExposedBean;
import hudson.scm.ChangeLogSet.Entry;
import hudson.util.RunList;
import org.kohsuke.stapler.StaplerRequest;
......
......@@ -4,8 +4,8 @@ import hudson.model.AbstractBuild;
import hudson.model.User;
import hudson.scm.CVSChangeLogSet.CVSChangeLog;
import hudson.util.IOException2;
import hudson.api.Exposed;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import org.apache.commons.digester.Digester;
import org.xml.sax.SAXException;
......
......@@ -2,8 +2,8 @@ package hudson.scm;
import hudson.MarkupText;
import hudson.Util;
import hudson.api.Exposed;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import hudson.model.AbstractBuild;
import hudson.model.User;
......
package hudson.scm;
import hudson.api.CustomExposureBean;
import org.kohsuke.stapler.export.CustomExposureBean;
/**
* Designates the SCM operation.
......
......@@ -3,8 +3,8 @@ package hudson.scm;
import hudson.model.AbstractBuild;
import hudson.model.User;
import hudson.scm.SubversionChangeLogSet.LogEntry;
import hudson.api.Exposed;
import hudson.api.ExposedBean;
import org.kohsuke.stapler.export.Exposed;
import org.kohsuke.stapler.export.ExposedBean;
import java.io.IOException;
import java.util.ArrayList;
......
......@@ -11,6 +11,7 @@ import hudson.util.FormFieldValidator;
import hudson.util.ArgumentListBuilder;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.DataBoundConstructor;
import javax.servlet.ServletException;
import java.io.File;
......@@ -37,9 +38,7 @@ public class Ant extends Builder {
*/
private final String antOpts;
/**
* @stapler-constructor
*/
@DataBoundConstructor
public Ant(String targets,String antName, String antOpts) {
this.targets = targets;
this.antName = antName;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册