package water.api; import hex.ModelBuilder; import water.Iced; import water.TypeMap; import water.api.schemas3.MetadataV3; import water.api.schemas3.RouteV3; import water.api.schemas3.SchemaMetadataV3; import water.api.schemas4.EndpointV4; import water.util.MarkdownBuilder; import water.api.schemas4.EndpointsListV4; import water.api.schemas4.ListRequestV4; import java.net.MalformedURLException; import java.util.Map; /** * Docs REST API handler, which provides endpoint handlers for the autogeneration of * Markdown (and in the future perhaps HTML and PDF) documentation for REST API endpoints * and payload entities (aka Schemas). */ public class MetadataHandler extends Handler { /** Return a list of all REST API Routes and a Markdown Table of Contents. */ @SuppressWarnings("unused") // called through reflection by RequestServer public MetadataV3 listRoutes(int version, MetadataV3 docs) { MarkdownBuilder builder = new MarkdownBuilder(); builder.comment("Preview with http://jbt.github.io/markdown-editor"); builder.heading1("REST API Routes Table of Contents"); builder.hline(); builder.tableHeader("HTTP method", "URI pattern", "Input schema", "Output schema", "Summary"); docs.routes = new RouteV3[RequestServer.numRoutes()]; int i = 0; for (Route route : RequestServer.routes()) { RouteV3 schema = new RouteV3(route); docs.routes[i] = schema; // ModelBuilder input / output schema hackery MetadataV3 look = new MetadataV3(); look.routes = new RouteV3[1]; look.routes[0] = schema; look.path = route._url; look.http_method = route._http_method; fetchRoute(version, look); schema.input_schema = look.routes[0].input_schema; schema.output_schema = look.routes[0].output_schema; builder.tableRow( route._http_method, route._url, Handler.getHandlerMethodInputSchema(route._handler_method).getSimpleName(), Handler.getHandlerMethodOutputSchema(route._handler_method).getSimpleName(), route._summary); i++; } docs.markdown = builder.toString(); return docs; } public EndpointsListV4 listRoutes4(int version, ListRequestV4 inp) { EndpointsListV4 res = new EndpointsListV4(); res.endpoints = new EndpointV4[RequestServer.numRoutes(4)]; int i = 0; for (Route route : RequestServer.routes()) { if (route.getVersion() != version) continue; EndpointV4 routeSchema = Schema.newInstance(EndpointV4.class).fillFromImpl(route); res.endpoints[i++] = routeSchema; } return res; } /** Return the metadata for a REST API Route, specified either by number or path. */ // Also called through reflection by RequestServer public MetadataV3 fetchRoute(int version, MetadataV3 docs) { Route route = null; if (docs.path != null && docs.http_method != null) { try { route = RequestServer.lookupRoute(new RequestUri(docs.http_method, docs.path)); } catch (MalformedURLException e) { route = null; } } else { // Linear scan for the route, plus each route is asked for in-order // during doc-gen leading to an O(n^2) execution cost. if (docs.path != null) try { docs.num = Integer.parseInt(docs.path); } catch (NumberFormatException e) { /* path is not a number, it's ok */ } if (docs.num >= 0 && docs.num < RequestServer.numRoutes()) route = RequestServer.routes().get(docs.num); // Crash-n-burn if route not found (old code thru an AIOOBE), so we // something similarly bad. docs.routes = new RouteV3[]{new RouteV3(route)}; } if (route == null) return null; Schema sinput, soutput; if( route._handler_class.equals(water.api.ModelBuilderHandler.class) || route._handler_class.equals(water.api.GridSearchHandler.class)) { // GridSearchHandler uses the same logic as ModelBuilderHandler because there are no separate // ${ALGO}GridSearchParametersV3 classes, instead each field in ${ALGO}ParametersV3 is marked as either gridable // or not. String ss[] = route._url.split("/"); String algoURLName = ss[3]; // {}/{3}/{ModelBuilders}/{gbm}/{parameters} String algoName = ModelBuilder.algoName(algoURLName); // gbm -> GBM; deeplearning -> DeepLearning String schemaDir = ModelBuilder.schemaDirectory(algoURLName); int version2 = Integer.valueOf(ss[1]); try { String inputSchemaName = schemaDir + algoName + "V" + version2; // hex.schemas.GBMV3 sinput = (Schema) TypeMap.getTheFreezableOrThrow(TypeMap.onIce(inputSchemaName)); } catch (java.lang.ClassNotFoundException e) { // Not very pretty, but for some routes such as /99/Grid/glm we want to map to GLMV3 (because GLMV99 does not // exist), yet for others such as /99/Grid/svd we map to SVDV99 (because SVDV3 does not exist). sinput = (Schema) TypeMap.theFreezable(TypeMap.onIce(schemaDir + algoName + "V3")); } sinput.init_meta(); soutput = sinput; } else { sinput = Schema.newInstance(Handler.getHandlerMethodInputSchema (route._handler_method)); soutput = Schema.newInstance(Handler.getHandlerMethodOutputSchema(route._handler_method)); } docs.routes[0].input_schema = sinput.getClass().getSimpleName(); docs.routes[0].output_schema = soutput.getClass().getSimpleName(); docs.routes[0].markdown = route.markdown(sinput,soutput).toString(); return docs; } /** Fetch the metadata for a Schema by its full internal classname, e.g. "hex.schemas.DeepLearningV2.DeepLearningParametersV2". TODO: Do we still need this? */ @Deprecated @SuppressWarnings("unused") // called through reflection by RequestServer public MetadataV3 fetchSchemaMetadataByClass(int version, MetadataV3 docs) { docs.schemas = new SchemaMetadataV3[1]; // NOTE: this will throw an exception if the classname isn't found: SchemaMetadataV3 meta = new SchemaMetadataV3(SchemaMetadata.createSchemaMetadata(docs.classname)); docs.schemas[0] = meta; return docs; } /** Fetch the metadata for a Schema by its simple Schema name (e.g., "DeepLearningParametersV2"). */ @SuppressWarnings("unused") // called through reflection by RequestServer public MetadataV3 fetchSchemaMetadata(int version, MetadataV3 docs) { if ("void".equals(docs.schemaname)) { docs.schemas = new SchemaMetadataV3[0]; return docs; } docs.schemas = new SchemaMetadataV3[1]; // NOTE: this will throw an exception if the classname isn't found: Schema schema = Schema.newInstance(docs.schemaname); // get defaults try { Iced impl = (Iced) schema.getImplClass().newInstance(); schema.fillFromImpl(impl); } catch (Exception e) { // ignore if create fails; this can happen for abstract classes } SchemaMetadataV3 meta = new SchemaMetadataV3(new SchemaMetadata(schema)); docs.schemas[0] = meta; return docs; } /** Fetch the metadata for all the Schemas. */ @SuppressWarnings("unused") // called through reflection by RequestServer public MetadataV3 listSchemas(int version, MetadataV3 docs) { Map<String, Class<? extends Schema>> ss = SchemaServer.schemas(); docs.schemas = new SchemaMetadataV3[ss.size()]; // NOTE: this will throw an exception if the classname isn't found: int i = 0; for (Class<? extends Schema> schema_class : ss.values()) { // No hardwired version! YAY! FINALLY! Schema schema = Schema.newInstance(schema_class); // get defaults try { Iced impl = (Iced) schema.getImplClass().newInstance(); schema.fillFromImpl(impl); } catch (Exception e) { // ignore if create fails; this can happen for abstract classes } docs.schemas[i++] = new SchemaMetadataV3(new SchemaMetadata(schema)); } return docs; } }