├── src ├── main │ ├── ui │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ └── .npmignore │ │ ├── app │ │ │ ├── shared │ │ │ │ └── index.ts │ │ │ ├── app.component.scss │ │ │ ├── header │ │ │ │ ├── header.component.scss │ │ │ │ ├── index.ts │ │ │ │ ├── header.component.ts │ │ │ │ └── header.component.html │ │ │ ├── grid │ │ │ │ ├── index.ts │ │ │ │ ├── grid.module.ts │ │ │ │ ├── grid.component.scss │ │ │ │ └── grid.component.ts │ │ │ ├── home │ │ │ │ ├── index.ts │ │ │ │ ├── home.component.scss │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.ts │ │ │ ├── app.component.html │ │ │ ├── error │ │ │ │ ├── index.ts │ │ │ │ ├── error.component.html │ │ │ │ ├── error.component.ts │ │ │ │ └── error.component.scss │ │ │ ├── help │ │ │ │ ├── index.ts │ │ │ │ ├── startup.component.ts │ │ │ │ ├── startup.component.scss │ │ │ │ └── startup.component.html │ │ │ ├── login │ │ │ │ ├── index.ts │ │ │ │ ├── login.component.scss │ │ │ │ ├── login.component.ts │ │ │ │ └── login.component.html │ │ │ ├── codemirror │ │ │ │ ├── index.ts │ │ │ │ └── codemirror.component.ts │ │ │ ├── file-browser │ │ │ │ ├── index.ts │ │ │ │ ├── file-browser.component.scss │ │ │ │ ├── file-browser.component.ts │ │ │ │ └── file-browser.component.html │ │ │ ├── subsection │ │ │ │ ├── index.ts │ │ │ │ ├── subsection.component.html │ │ │ │ ├── subsection.component.scss │ │ │ │ └── subsection.component.ts │ │ │ ├── index.ts │ │ │ ├── marklogic │ │ │ │ ├── index.ts │ │ │ │ ├── breakpoint.ts │ │ │ │ └── marklogic.service.ts │ │ │ ├── auth │ │ │ │ ├── auth.model.ts │ │ │ │ ├── index.ts │ │ │ │ ├── auth-guard.service.ts │ │ │ │ ├── http-interceptor.ts │ │ │ │ └── auth.service.ts │ │ │ ├── app.component.ts │ │ │ ├── app.routes.ts │ │ │ ├── app.component.spec.ts │ │ │ ├── app.module.ts │ │ │ └── material.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── styles │ │ │ ├── _flex.scss │ │ │ └── _colors.scss │ │ ├── typings.d.ts │ │ ├── main.ts │ │ ├── index.html │ │ ├── tsconfig.json │ │ ├── styles.scss │ │ ├── test.ts │ │ └── polyfills.ts │ ├── resources │ │ ├── modules │ │ │ ├── eval.xqy │ │ │ ├── pause.xqy │ │ │ ├── step-in.xqy │ │ │ ├── step-out.xqy │ │ │ ├── continue.xqy │ │ │ ├── disable-server.xqy │ │ │ ├── enable-server.xqy │ │ │ ├── step-over.xqy │ │ │ ├── is-server-enabled.xqy │ │ │ ├── clear-breakpoints.xqy │ │ │ ├── get-breakpoints.xqy │ │ │ ├── get-servers.xqy │ │ │ ├── set-breakpoints.xqy │ │ │ ├── value.xqy │ │ │ ├── set-breakpoint.xqy │ │ │ ├── invoke-module.xqy │ │ │ ├── get-attached.xqy │ │ │ ├── get-file.xqy │ │ │ ├── get-requests.xqy │ │ │ ├── get-request.xqy │ │ │ ├── get-marklogic-system-files.xqy │ │ │ ├── get-stacktrace.xqy │ │ │ └── get-files.xqy │ │ ├── logback.xml │ │ ├── banner.txt │ │ └── application.properties │ ├── java │ │ └── com │ │ │ └── marklogic │ │ │ └── debugger │ │ │ ├── errors │ │ │ └── InvalidRequestException.java │ │ │ ├── web │ │ │ ├── Breakpoint.java │ │ │ ├── AppController.java │ │ │ ├── RestResponseEntityExceptionHandler.java │ │ │ └── ApiController.java │ │ │ ├── LoginInfo.java │ │ │ ├── App.java │ │ │ ├── auth │ │ │ ├── RestAuthenticationEntryPoint.java │ │ │ ├── AuthSuccessHandler.java │ │ │ ├── ConnectionAuthenticationToken.java │ │ │ ├── MarkLogicAuthenticationManager.java │ │ │ └── ConnectionAuthenticationFilter.java │ │ │ └── Config.java │ └── e2e │ │ ├── app.po.ts │ │ ├── app.e2e-spec.ts │ │ └── tsconfig.json ├── favicon.ico ├── typings.d.ts ├── main.ts ├── index.html ├── tsconfig.json ├── polyfills.ts └── test.ts ├── gradle.properties ├── proxy.config.json ├── e2e ├── app.po.ts ├── app.e2e-spec.ts └── tsconfig.json ├── .editorconfig ├── .gitignore ├── .github └── ISSUE_TEMPLATE.md ├── protractor.conf.js ├── README.md ├── karma.conf.js ├── angular-cli.json ├── package.json ├── tslint.json ├── CHANGELOG.md ├── CONTRIBUTING.md └── LICENSE /src/main/ui/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/ui/app/shared/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/ui/assets/.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/ui/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.0.0-alpha.8 2 | 3 | -------------------------------------------------------------------------------- /src/main/ui/app/header/header.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/ui/app/grid/index.ts: -------------------------------------------------------------------------------- 1 | export * from './grid.module'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/ui/app/error/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './header.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/help/index.ts: -------------------------------------------------------------------------------- 1 | export * from './startup.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/login/index.ts: -------------------------------------------------------------------------------- 1 | export * from './login.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/codemirror/index.ts: -------------------------------------------------------------------------------- 1 | export * from './codemirror.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/file-browser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './file-browser.component'; 2 | -------------------------------------------------------------------------------- /src/main/ui/app/subsection/index.ts: -------------------------------------------------------------------------------- 1 | export * from './subsection.component'; 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paxtonhare/marklogic-debugger/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/main/ui/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/main/resources/modules/eval.xqy: -------------------------------------------------------------------------------- 1 | declare variable $xquery external; 2 | 3 | dbg:eval($xquery) 4 | -------------------------------------------------------------------------------- /src/main/ui/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/main/ui/app/marklogic/index.ts: -------------------------------------------------------------------------------- 1 | export * from './marklogic.service'; 2 | export * from './breakpoint'; 3 | -------------------------------------------------------------------------------- /src/main/ui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paxtonhare/marklogic-debugger/HEAD/src/main/ui/favicon.ico -------------------------------------------------------------------------------- /proxy.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "http://localhost:9999", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/modules/pause.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | dbg:attach(xs:unsignedLong($requestId)) 4 | -------------------------------------------------------------------------------- /src/main/resources/modules/step-in.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | dbg:step(xs:unsignedLong($requestId)) 4 | -------------------------------------------------------------------------------- /src/main/resources/modules/step-out.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | dbg:out(xs:unsignedLong($requestId)) 4 | -------------------------------------------------------------------------------- /src/main/resources/modules/continue.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | dbg:continue(xs:unsignedLong($requestId)) 4 | -------------------------------------------------------------------------------- /src/main/resources/modules/disable-server.xqy: -------------------------------------------------------------------------------- 1 | declare variable $serverId external; 2 | 3 | dbg:disconnect(xs:unsignedLong($serverId)) 4 | -------------------------------------------------------------------------------- /src/main/resources/modules/enable-server.xqy: -------------------------------------------------------------------------------- 1 | declare variable $serverId external; 2 | 3 | dbg:connect(xs:unsignedLong($serverId)) 4 | -------------------------------------------------------------------------------- /src/main/resources/modules/step-over.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | dbg:next(xs:unsignedLong($requestId)) 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/errors/InvalidRequestException.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.errors; 2 | 3 | public class InvalidRequestException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/web/Breakpoint.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.web; 2 | 3 | public class Breakpoint { 4 | public String uri; 5 | public String line; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/ui/styles/_flex.scss: -------------------------------------------------------------------------------- 1 | .flex-auto { 2 | flex: auto; 3 | } 4 | 5 | .vbox { 6 | display: flex; 7 | flex-direction: column !important; 8 | position: relative; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/main/ui/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | 4 | declare var System: any; 5 | -------------------------------------------------------------------------------- /src/main/ui/app/auth/auth.model.ts: -------------------------------------------------------------------------------- 1 | export class AuthModel { 2 | constructor( 3 | public hostname: string, 4 | public port: number, 5 | public username: string, 6 | public password: string 7 | ) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, see links for more information 2 | // https://github.com/typings/typings 3 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 4 | 5 | declare var System: any; 6 | -------------------------------------------------------------------------------- /src/main/resources/modules/is-server-enabled.xqy: -------------------------------------------------------------------------------- 1 | declare variable $serverId external; 2 | 3 | let $o := json:object() 4 | let $_ := map:put($o, "enabled", dbg:connected() = xs:unsignedLong($serverId)) 5 | return 6 | xdmp:to-json($o) 7 | -------------------------------------------------------------------------------- /src/main/resources/modules/clear-breakpoints.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | let $requestId := xs:unsignedLong($requestId) 4 | for $expression in dbg:breakpoints($requestId) 5 | return 6 | dbg:clear($requestId, $expression) 7 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class MarklogicDebuggerPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor/globals'; 2 | 3 | export class MarklogicDebuggerPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/ui/app/marklogic/breakpoint.ts: -------------------------------------------------------------------------------- 1 | export class Breakpoint { 2 | uri: string; 3 | line: number; 4 | enabled: boolean; 5 | 6 | constructor(uri: string, line: number, enabled: boolean) { 7 | this.uri = uri; 8 | this.line = line; 9 | this.enabled = enabled; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/ui/app/subsection/subsection.component.html: -------------------------------------------------------------------------------- 1 |
2 | arrow_drop_down {{title}} 3 | 4 |
5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /src/main/ui/app/grid/grid.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { GridManiaComponent, DividerComponent } from './grid.component'; 4 | 5 | @NgModule({ 6 | exports: [ 7 | GridManiaComponent, 8 | DividerComponent 9 | ], 10 | declarations: [ 11 | GridManiaComponent, 12 | DividerComponent 13 | ] 14 | }) 15 | export class GridManiaModule { } 16 | -------------------------------------------------------------------------------- /src/main/ui/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { environment } from './environments/environment'; 4 | import { AppModule } from './app/app.module'; 5 | 6 | if (environment.production) { 7 | enableProdMode(); 8 | } 9 | 10 | platformBrowserDynamic().bootstrapModule(AppModule); 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | [*.java] 17 | indent_style = tab 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarklogicDebugger 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Loading... 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/LoginInfo.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger; 2 | 3 | public class LoginInfo { 4 | public String username; 5 | public String password; 6 | public String hostname; 7 | public int port; 8 | 9 | public String toString() { 10 | return "{\"username\":\"" + username + "\"," + 11 | "\"hostname\": \"" + hostname + "\"," + 12 | "\"port\": \"" + port + "\"}"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarklogicDebugger 6 | 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { MarklogicDebuggerPage } from './app.po'; 2 | 3 | describe('marklogic-debugger App', function() { 4 | let page: MarklogicDebuggerPage; 5 | 6 | beforeEach(() => { 7 | page = new MarklogicDebuggerPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { MarklogicDebuggerPage } from './app.po'; 2 | 3 | describe('marklogic-debugger App', function() { 4 | let page: MarklogicDebuggerPage; 5 | 6 | beforeEach(() => { 7 | page = new MarklogicDebuggerPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/main/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/ui/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/main/ui/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AuthService } from './auth'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | constructor(private authService: AuthService) {} 11 | 12 | isAuthenticated() { 13 | return this.authService.isAuthenticated(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": ["es6", "dom"], 7 | "mapRoot": "./", 8 | "module": "es6", 9 | "moduleResolution": "node", 10 | "outDir": "../dist/out-tsc", 11 | "sourceMap": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "../node_modules/@types" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-breakpoints.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | let $request-id := xs:unsignedLong($requestId) 4 | let $expr := dbg:expr($request-id, dbg:breakpoints($request-id)) 5 | let $o := json:object() 6 | let $_ := ( 7 | map:put($o, "uri", $expr/dbg:uri/fn:string()), 8 | map:put($o, "line", $expr/dbg:line/fn:data()), 9 | map:put($o, "statement", $expr/dbg:expr-source/fn:string()) 10 | ) 11 | return 12 | xdmp:to-json($o) 13 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-servers.xqy: -------------------------------------------------------------------------------- 1 | xdmp:to-json( 2 | json:to-array( 3 | let $connected := dbg:connected() 4 | for $server in xdmp:servers() 5 | return 6 | let $o := json:object() 7 | let $_ := ( 8 | map:put($o, "id", fn:string($server)), 9 | map:put($o, "name", xdmp:server-name($server)), 10 | map:put($o, "connected", fn:exists($connected[. = $server])) 11 | ) 12 | return 13 | $o 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /src/main/ui/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { AuthGuard } from './auth/auth-guard.service'; 3 | 4 | import { HomeComponent } from './home'; 5 | import { LoginComponent } from './login'; 6 | 7 | 8 | export const ROUTES: Routes = [ 9 | { path: '', component: HomeComponent, canActivate: [AuthGuard] }, 10 | { path: 'server/:appserverName', component: HomeComponent, canActivate: [AuthGuard] }, 11 | { path: 'login', component: LoginComponent } 12 | ]; 13 | -------------------------------------------------------------------------------- /src/main/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": ["es6", "dom"], 8 | "mapRoot": "./", 9 | "module": "es6", 10 | "moduleResolution": "node", 11 | "outDir": "../../../dist/out-tsc", 12 | "sourceMap": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "../../../node_modules/@types" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/web/AppController.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.web; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | 7 | @Controller 8 | public class AppController { 9 | 10 | @RequestMapping(value = {"/", "/login", "/home", "/404"}, method = RequestMethod.GET) 11 | public String index() { 12 | return "index.html"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/ui/app/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Http, Request, RequestOptionsArgs, Response, 3 | RequestOptions, ConnectionBackend, Headers, XHRBackend 4 | } from '@angular/http'; 5 | import { AuthGuard } from './auth-guard.service'; 6 | import { AuthService } from './auth.service'; 7 | import { HTTP_PROVIDER } from './http-interceptor'; 8 | 9 | export const AUTH_PROVIDERS = [ 10 | AuthGuard, 11 | AuthService, 12 | HTTP_PROVIDER 13 | ]; 14 | 15 | export * from './auth.service'; 16 | export * from './auth.model'; 17 | -------------------------------------------------------------------------------- /src/main/resources/modules/set-breakpoints.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | declare variable $uri external; 3 | declare variable $line external; 4 | 5 | let $expr-id := 6 | let $requestId := xs:unsignedLong($requestId) 7 | let $line := xs:unsignedInt($line) + 1 8 | let $expressions := dbg:line($requestId, $uri, $line) ! dbg:expr($requestId, .) 9 | return 10 | (($expressions[dbg:line = $line])[1], $expressions[1])[1]/dbg:expr-id/fn:data() 11 | return 12 | dbg:break(xs:unsignedLong($requestId), $expr-id) 13 | -------------------------------------------------------------------------------- /src/main/ui/app/subsection/subsection.component.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | background-color: #f3f3f3; 3 | padding: 2px; 4 | border-bottom: 1px solid #ccc; 5 | cursor: default; 6 | 7 | mdl-icon { 8 | font-size: 16px; 9 | transform: rotate(0deg); 10 | -webkit-transform: rotate(0deg); 11 | transition: transform 0.125s ease-in-out; 12 | -webkit-transition: -webkit-transform 0.125s ease-in-out; 13 | 14 | } 15 | 16 | &.collapsed { 17 | mdl-icon { 18 | transform: rotate(-90deg); 19 | -webkit-transform: rotate(-90deg); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/ui/app/error/error.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Error
4 |
5 | 6 | 7 | 8 |
9 |
10 |
{{error}}
11 |
12 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /src/main/ui/app/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { AuthService } from '../auth'; 4 | 5 | @Component({ 6 | selector: 'app-header', 7 | templateUrl: './header.component.html', 8 | styleUrls: ['./header.component.scss'] 9 | }) 10 | export class HeaderComponent { 11 | constructor( 12 | private authService: AuthService, 13 | private router: Router) {} 14 | 15 | logout() { 16 | this.authService.logout().subscribe(() => { 17 | this.router.navigate(['login']); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/modules/value.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | declare variable $xquery external; 3 | 4 | let $o := json:object() 5 | let $_ := 6 | try { 7 | map:put($o, "resp", dbg:value(xs:unsignedLong($requestId), $xquery)), 8 | map:put($o, "error", fn:false()) 9 | } 10 | catch($e) { 11 | map:put($o, "resp", xdmp:quote($e, 12 | yes 13 | yes 14 | yes 15 | )), 16 | map:put($o, "error", fn:true()) 17 | } 18 | return 19 | xdmp:to-json($o) 20 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | __ __ _ _ _ 3 | | \/ | | | | | (_) 4 | | \ / | __ _ _ __| | _| | ___ __ _ _ ___ 5 | | |\/| |/ _` | '__| |/ / | / _ \ / _` | |/ __| 6 | | | | | (_| | | | <| |___| (_) | (_| | | (__ 7 | |_|__|_|\__,_|_| |_|\_\______\___/ \__, |_|\___| 8 | | __ \ | | __/ | 9 | | | | | ___| |__ _ _ __ _ __ _|___/ _ __ 10 | | | | |/ _ \ '_ \| | | |/ _` |/ _` |/ _ \ '__| 11 | | |__| | __/ |_) | |_| | (_| | (_| | __/ | 12 | |_____/ \___|_.__/ \__,_|\__, |\__, |\___|_| 13 | __/ | __/ | 14 | |___/ |___/ 15 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/App.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.boot.web.support.SpringBootServletInitializer; 7 | 8 | @SpringBootApplication 9 | public class App extends SpringBootServletInitializer { 10 | 11 | @Override 12 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 13 | return application.sources(App.class); 14 | } 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(App.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # IDEs and editors 11 | /.idea 12 | .project 13 | .classpath 14 | .c9/ 15 | *.launch 16 | .settings/ 17 | 18 | # IDE - VSCode 19 | .vscode/* 20 | !.vscode/settings.json 21 | !.vscode/tasks.json 22 | !.vscode/launch.json 23 | !.vscode/extensions.json 24 | 25 | # misc 26 | /.sass-cache 27 | /connect.lock 28 | /coverage/* 29 | /libpeerconnection.log 30 | npm-debug.log 31 | testem.log 32 | /typings 33 | 34 | # e2e 35 | /e2e/*.js 36 | /e2e/*.map 37 | 38 | #System Files 39 | .DS_Store 40 | Thumbs.db 41 | 42 | /.gradle 43 | /build 44 | /src/main/resources/static 45 | -------------------------------------------------------------------------------- /src/main/ui/app/subsection/subsection.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-subsection', 5 | templateUrl: './subsection.component.html', 6 | styleUrls: ['./subsection.component.scss'] 7 | }) 8 | export class SubsectionComponent { 9 | collapsed: boolean = false; 10 | @Input() title: string; 11 | @Output() clickHandler: EventEmitter = new EventEmitter(); 12 | 13 | constructor() {} 14 | 15 | isCollapsed() { 16 | return this.collapsed; 17 | } 18 | 19 | onClick($event) { 20 | this.clickHandler.emit($event); 21 | } 22 | 23 | toggleCollapsed() { 24 | this.collapsed = !this.collapsed; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/ui/app/error/error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, HostListener, Inject } from '@angular/core'; 2 | 3 | import { MdlDialogReference } from 'angular2-mdl'; 4 | 5 | @Component({ 6 | selector: 'app-error', 7 | templateUrl: './error.component.html', 8 | styleUrls: ['./error.component.scss'] 9 | }) 10 | export class ErrorComponent { 11 | error: string; 12 | 13 | constructor( 14 | private dialog: MdlDialogReference, 15 | @Inject('error') error: string 16 | ) { 17 | this.error = error; 18 | } 19 | 20 | hide() { 21 | this.dialog.hide(); 22 | } 23 | 24 | @HostListener('keydown.esc') 25 | public onEsc(): void { 26 | this.cancel(); 27 | } 28 | 29 | cancel() { 30 | this.hide(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/ui/app/file-browser/file-browser.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colors'; 2 | 3 | $bg-color: unquote("rgb(#{$palette-debugger-50})"); 4 | 5 | :host { 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | :not(.filetree) > .filetree { 11 | margin: 0px 0px 0px 5px; 12 | } 13 | 14 | .filetree { 15 | margin: 5px 0px 0px 5px; 16 | padding: 0px; 17 | list-style: none; 18 | line-height: initial; 19 | 20 | .filetree { 21 | padding: 0px 10px; 22 | } 23 | 24 | li { 25 | cursor: pointer; 26 | &.active { 27 | background-color: $bg-color; 28 | } 29 | } 30 | 31 | div.entry { 32 | padding: 0px 2px 33 | } 34 | 35 | p { 36 | display: inline-block; 37 | margin: 0px; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### So you need help Debugging the debugger, eh? 2 | 3 | Help me maximize this ticket's value. 4 | 5 | #### The issue 6 | 7 | Short description of the problem: 8 | 9 | What behavior are you expecting? 10 | 11 | #### Tech details 12 | 13 | Which Operating System are you using? 14 | 15 | Which version of MarkLogic are you using? 16 | 17 | Which version of the MarkLogic Debugger are you using? 18 | 19 | Which browser are you using? 20 | 21 | #### The devil is in the details... 22 | 23 | If possible, rerun the command with -d for debugging output and attach the output: 24 | 25 | **for quickstart:** 26 | `java -jar marklogic-debugger.war -d > myoutput.txt` 27 | 28 | **for gradle:** 29 | `gradle mlYourCommand -d > myoutput.txt` 30 | -------------------------------------------------------------------------------- /src/main/ui/app/error/error.component.scss: -------------------------------------------------------------------------------- 1 | /deep/ .mdl-dialog { 2 | width: 500px; 3 | } 4 | 5 | /deep/ mdl-dialog-host-component { 6 | padding: 0px; 7 | } 8 | 9 | .error-dialog { 10 | min-width: 500px; 11 | width: 500px; 12 | } 13 | 14 | pre { 15 | width: 100%; 16 | overflow: auto; 17 | } 18 | 19 | .mdl-dialog__content { 20 | max-height: 500px; 21 | overflow-y: scroll; 22 | } 23 | 24 | .mdl-dialog__title { 25 | 26 | font-size: 1.5rem; 27 | 28 | .mdl-button--fab.mdl-button--mini-fab { 29 | height: 30px; 30 | min-width: 30px; 31 | width: 30px; 32 | } 33 | 34 | display: flex; 35 | flex-direction: row; 36 | align-items: center; 37 | 38 | padding: 12px 24px 12px; 39 | 40 | background-color: #222; 41 | 42 | color: white; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/ui/app/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sign Out 5 | 6 | 7 | Debugger 8 | 9 | 10 | 11 | Sign Out 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/modules/set-breakpoint.xqy: -------------------------------------------------------------------------------- 1 | declare variable $serverId external; 2 | declare variable $uri external; 3 | declare variable $line external; 4 | 5 | let $modules-db := xdmp:server-modules-database(xs:unsignedLong($serverId)) 6 | return 7 | let $results := dbg:eval(' 8 | declare variable $uri external; 9 | declare variable $line external; 10 | let $request := xdmp:request() 11 | let $expressions := dbg:line($request, $uri, $line) ! dbg:expr($request, .) 12 | return 13 | (($expressions[dbg:line = $line])[1], $expressions[1])[1]/dbg:expr-id/fn:data() 14 | ', 15 | ( 16 | xs:QName("uri"), $uri, 17 | xs:QName("line"), xs:unsignedInt($line) 18 | ), 19 | map:new(( 20 | map:entry("modules", $modules-db) 21 | ))) 22 | let $request := $results[1] 23 | return 24 | $results 25 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/auth/RestAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.auth; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | @Component( "restAuthenticationEntryPoint" ) 12 | public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { 13 | 14 | @Override 15 | public void commence(HttpServletRequest request, HttpServletResponse response, 16 | AuthenticationException authException ) throws IOException { 17 | response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/ui/app/help/startup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostListener } from '@angular/core'; 2 | 3 | import { MdlDialogReference } from 'angular2-mdl'; 4 | 5 | @Component({ 6 | selector: 'app-startup', 7 | templateUrl: './startup.component.html', 8 | styleUrls: ['./startup.component.scss'] 9 | }) 10 | export class StartupComponent implements OnInit { 11 | showOnStartup: boolean; 12 | 13 | constructor(private dialog: MdlDialogReference) { } 14 | 15 | ngOnInit() { 16 | this.showOnStartup = (localStorage.getItem('_show_welcome_') !== 'false'); 17 | } 18 | 19 | hide() { 20 | this.dialog.hide(); 21 | } 22 | 23 | @HostListener('keydown.esc') 24 | public onEsc(): void { 25 | this.cancel(); 26 | } 27 | 28 | cancel() { 29 | localStorage.setItem('_show_welcome_', this.showOnStartup.toString()); 30 | this.hide(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/main/ui/app/auth/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, Router, ActivatedRouteSnapshot, 3 | RouterStateSnapshot } from '@angular/router'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { AuthService } from './auth.service'; 6 | 7 | @Injectable() 8 | export class AuthGuard implements CanActivate { 9 | constructor( 10 | private authService: AuthService, 11 | private router: Router) {} 12 | 13 | canActivate( 14 | route: ActivatedRouteSnapshot, 15 | state: RouterStateSnapshot): Observable | boolean { 16 | if (this.authService.isAuthenticated()) { 17 | return true; 18 | } 19 | 20 | // Store the attempted URL for redirecting 21 | this.authService.redirectUrl = state.url; 22 | 23 | // Navigate to the login page 24 | this.router.navigate(['login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/modules/invoke-module.xqy: -------------------------------------------------------------------------------- 1 | import module namespace admin = "http://marklogic.com/xdmp/admin" 2 | at "/MarkLogic/admin.xqy"; 3 | 4 | declare variable $uri external; 5 | 6 | declare variable $serverId external; 7 | 8 | declare variable $ml-dir := xdmp:filesystem-filepath('.') || '/Modules'; 9 | 10 | let $server-id := xs:unsignedLong($serverId) 11 | let $config := admin:get-configuration() 12 | let $modules-db := admin:appserver-get-modules-database($config, $server-id) 13 | let $content-db := admin:appserver-get-database($config, $server-id) 14 | let $server-root := admin:appserver-get-root($config, $server-id) 15 | let $options := 16 | 17 | {$content-db} 18 | {$modules-db} 19 | 20 | return 21 | if (fn:starts-with($uri, "/MarkLogic/")) then 22 | dbg:invoke($uri, (), $options) 23 | else 24 | dbg:invoke($uri, (), $options) 25 | -------------------------------------------------------------------------------- /src/main/ui/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "../../../node_modules/codemirror/lib/codemirror.css"; 3 | @import "~angular2-mdl/scss/color-definitions"; 4 | 5 | $fa-font-path: "../../../node_modules/font-awesome/fonts"; 6 | @import "../../../node_modules/font-awesome/scss/font-awesome"; 7 | @import "../../../node_modules/material-design-icons/iconfont/material-icons.css"; 8 | 9 | body { 10 | margin: 0px; 11 | padding: 0px; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | @import './styles/colors'; 19 | 20 | $color-primary: $palette-debugger-500; 21 | $color-primary-dark: $palette-blue-700; 22 | $color-accent: $palette-amber-A200; 23 | $color-primary-contrast: $color-dark-contrast; 24 | $color-accent-contrast: $color-dark-contrast; 25 | 26 | .bar { 27 | box-sizing: border-box; 28 | color: white; 29 | width: 100%; 30 | padding: 16px; 31 | height: 64px; 32 | } 33 | 34 | @import '~angular2-mdl/scss/material-design-lite'; 35 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # MarkLogic properties - these are loaded by Gradle as well to avoid duplication 2 | mlAppName=Debuggy 3 | mlHost=localhost 4 | # mlRestPort=8010 5 | 6 | # Credentials for deploying 7 | mlUsername=admin 8 | mlPassword=admin 9 | 10 | # Credentials for loading modules 11 | mlRestAdminUsername=admin 12 | mlRestAdminPassword=admin 13 | 14 | # Spring Boot property for the port that the app server runs on 15 | server.port=9999 16 | 17 | # By default, enable the dev profile 18 | spring.profiles.active=dev 19 | 20 | # Disable Thymeleaf caching 21 | spring.thymeleaf.cache=false 22 | 23 | # No need to restart when a web file is modified 24 | spring.devtools.restart.exclude=static/**,templates/** 25 | 26 | # The cookie name is application-specific so it doesn't conflict with other Spring Boot-based apps 27 | server.session.cookie.name=marklogicDebuggerSessionid 28 | 29 | # Default the timeout to 10 hours (Spring Boot docs say this is in seconds, but it seems to be minutes instead) 30 | server.session.timeout=600 31 | -------------------------------------------------------------------------------- /src/main/ui/styles/_colors.scss: -------------------------------------------------------------------------------- 1 | $palette-debugger: 2 | "233,233,243" 3 | "199,200,225" 4 | "162,164,206" 5 | "124,128,186" 6 | "96,100,171" 7 | "68,73,156" 8 | "62,66,148" 9 | "53,57,138" 10 | "45,49,128" 11 | "31,33,110" 12 | "173,175,255" 13 | "122,125,255" 14 | "71,75,255" 15 | "45,50,255" 16 | ; 17 | 18 | $palette-debugger-50: nth($palette-debugger, 1); 19 | $palette-debugger-100: nth($palette-debugger, 2); 20 | $palette-debugger-200: nth($palette-debugger, 3); 21 | $palette-debugger-300: nth($palette-debugger, 4); 22 | $palette-debugger-400: nth($palette-debugger, 5); 23 | $palette-debugger-500: nth($palette-debugger, 6); 24 | $palette-debugger-600: nth($palette-debugger, 7); 25 | $palette-debugger-700: nth($palette-debugger, 8); 26 | $palette-debugger-800: nth($palette-debugger, 9); 27 | $palette-debugger-900: nth($palette-debugger, 10); 28 | $palette-debugger-A100: nth($palette-debugger, 11); 29 | $palette-debugger-A200: nth($palette-debugger, 12); 30 | $palette-debugger-A400: nth($palette-debugger, 13); 31 | $palette-debugger-A700: nth($palette-debugger, 14); 32 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/web/RestResponseEntityExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.web; 2 | 3 | import com.marklogic.debugger.errors.InvalidRequestException; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.context.request.WebRequest; 10 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 11 | 12 | @ControllerAdvice 13 | public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { 14 | 15 | @ExceptionHandler(InvalidRequestException.class) 16 | protected ResponseEntity handleInvalidRequestId(InvalidRequestException ex, WebRequest request) { 17 | String bodyOfResponse = "Request ID not found"; 18 | return handleExceptionInternal(ex, bodyOfResponse, 19 | new HttpHeaders(), HttpStatus.NOT_FOUND, request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-attached.xqy: -------------------------------------------------------------------------------- 1 | import module namespace functx = "http://www.functx.com" 2 | at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy"; 3 | 4 | declare variable $serverId external; 5 | 6 | let $a := json:array() 7 | let $_ := 8 | for $attached in dbg:attached(xs:unsignedLong($serverId)) 9 | let $status := xdmp:request-status(xdmp:host(), xs:unsignedLong($serverId), $attached) 10 | let $o := map:new(( 11 | map:entry("server", xdmp:server-name($status/*:server-id)), 12 | map:entry("host", xdmp:host-name($status/*:host-id)), 13 | map:entry("modules", if ($status/*:modules = 0) then "FileSystem" else xdmp:database-name($status/*:modules)), 14 | map:entry("database", if ($status/*:database = 0) then "FileSystem" else xdmp:database-name($status/*:database)), 15 | for $item in $status/*[fn:not(self::*:server-id or self::*:host-id or self::*:modules or self::*:database)] 16 | return 17 | map:entry(functx:words-to-camel-case(fn:replace(fn:local-name($item), "-", " ")), $item/fn:data()) 18 | )) 19 | return 20 | json:array-push($a, $o) 21 | return 22 | xdmp:to-json($a) 23 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-file.xqy: -------------------------------------------------------------------------------- 1 | import module namespace admin = "http://marklogic.com/xdmp/admin" 2 | at "/MarkLogic/admin.xqy"; 3 | 4 | declare variable $serverId external; 5 | declare variable $uri external; 6 | 7 | declare variable $ml-dir := xdmp:filesystem-filepath('.') || '/Modules'; 8 | 9 | let $server-id := xs:unsignedLong($serverId) 10 | let $config := admin:get-configuration() 11 | let $modules-db := admin:appserver-get-modules-database($config, $server-id) 12 | let $server-root := admin:appserver-get-root($config, $server-id) 13 | return 14 | if ($modules-db = 0) then 15 | if (fn:starts-with($uri, "/MarkLogic/")) then 16 | xdmp:document-get($ml-dir || $uri) 17 | else 18 | xdmp:document-get($server-root || $uri) 19 | else 20 | xdmp:invoke-function(function() { 21 | if (fn:starts-with($uri, "/MarkLogic/")) then 22 | xdmp:document-get($ml-dir || $uri) 23 | else 24 | fn:doc($uri) 25 | }, 26 | 27 | different-transaction 28 | {$modules-db} 29 | update-auto-commit 30 | ) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marklogic Debugger 2 | 3 | A stand alone debugger for MarkLogic Server. Simply download the war and run it. Then point to your MarkLogic Server and start debugging. Nothing gets installed in MarkLogic. 4 | 5 | **_Should_** work with all versions of MarkLogic. Tested with 7, 8, and 9. 6 | 7 | ## Prerequisites 8 | 9 | - You need Java 8 to run the executable war file. 10 | 11 | ## Using It 12 | 13 | Download the war file from the [releases page](https://github.com/paxtonhare/marklogic-debugger/releases). 14 | 15 | Run it with: 16 | 17 | `java -jar marklogic-debugger.war` 18 | 19 | Open your browser to [http://localhost:9999](http://localhost:9999) 20 | 21 | ##### Changing the port 22 | 23 | Want to use a port other than 9999? Run it like this: 24 | 25 | `java -jar marklogic-debugger.war --server.port=8090` 26 | 27 | ### Browser Compatibility 28 | 29 | Use Chrome or FireFox or Safari. Not tested in IE. 30 | 31 | ## Building From Source 32 | 33 | Want to contribute? Perhaps you just want to poke the code? 34 | 35 | Look at our [CONTRIBUTING.md](https://github.com/paxtonhare/marklogic-debugger/blob/master/CONTRIBUTING.md#building-the-debugger-from-source) file for details on building from source. 36 | -------------------------------------------------------------------------------- /src/main/ui/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | TestBed.compileComponents(); 14 | }); 15 | 16 | it('should create the app', async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | 22 | it(`should have as title 'app works!'`, async(() => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.debugElement.componentInstance; 25 | expect(app.title).toEqual('app works!'); 26 | })); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | const fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | const compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /src/main/ui/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | 10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 11 | declare var __karma__: any; 12 | declare var require: any; 13 | 14 | // Prevent Karma from running prematurely. 15 | __karma__.loaded = function () {}; 16 | 17 | 18 | Promise.all([ 19 | System.import('@angular/core/testing'), 20 | System.import('@angular/platform-browser-dynamic/testing') 21 | ]) 22 | // First, initialize the Angular testing environment. 23 | .then(([testing, testingBrowser]) => { 24 | testing.getTestBed().initTestEnvironment( 25 | testingBrowser.BrowserDynamicTestingModule, 26 | testingBrowser.platformBrowserDynamicTesting() 27 | ); 28 | }) 29 | // Then we find all the tests. 30 | .then(() => require.context('./', true, /\.spec\.ts/)) 31 | // And load the modules. 32 | .then(context => context.keys().map(context)) 33 | // Finally, start Karma to run the tests. 34 | .then(__karma__.start, __karma__.error); 35 | -------------------------------------------------------------------------------- /src/main/ui/app/file-browser/file-browser.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-file-browser', 5 | templateUrl: './file-browser.component.html', 6 | styleUrls: ['./file-browser.component.scss'] 7 | }) 8 | export class FileBrowserComponent { 9 | @Input() selectedUri: string; 10 | @Input() currentChild: any; 11 | @Input() fileSets: Array; 12 | @Output() fileShown = new EventEmitter(); 13 | @Input() isRoot: boolean = true; 14 | 15 | constructor(protected el: ElementRef) { 16 | } 17 | 18 | getEntryIcon(child) { 19 | if (child.type === 'dir') { 20 | return child.collapsed ? 'fa-folder-o' : 'fa-folder-open-o'; 21 | } 22 | return 'fa-file-o'; 23 | } 24 | 25 | isSelected(child) { 26 | return this.selectedUri && (this.selectedUri === child.uri); 27 | } 28 | 29 | entryClicked(entry) { 30 | if (entry.type === 'dir') { 31 | entry.collapsed = !(!!entry.collapsed); 32 | } else { 33 | this.selectedUri = entry.uri; 34 | this.currentChild = entry; 35 | this.fileShown.next(entry); 36 | } 37 | } 38 | 39 | fileClicked($event) { 40 | this.fileShown.next($event); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/main/ui/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/main/ui/test.ts': ['angular-cli'] 19 | }, 20 | mime: { 21 | 'text/x-typescript': ['ts','tsx'] 22 | }, 23 | remapIstanbulReporter: { 24 | reports: { 25 | html: 'coverage', 26 | lcovonly: './coverage/coverage.lcov' 27 | } 28 | }, 29 | angularCli: { 30 | config: './angular-cli.json', 31 | environment: 'dev' 32 | }, 33 | reporters: config.angularCli && config.angularCli.codeCoverage 34 | ? ['progress', 'karma-remap-istanbul'] 35 | : ['progress'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-requests.xqy: -------------------------------------------------------------------------------- 1 | xquery version "1.0-ml"; 2 | 3 | declare namespace server = "http://marklogic.com/xdmp/status/server"; 4 | 5 | import module namespace functx = "http://www.functx.com" 6 | at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy"; 7 | 8 | declare variable $serverId external; 9 | 10 | let $current-request := xdmp:request() 11 | let $a := json:array() 12 | let $_ := 13 | for $status in xdmp:server-status(xdmp:host(), (xdmp:server("TaskServer"), xs:unsignedLong($serverId)))/server:request-statuses/server:request-status[fn:not(server:request-id = $current-request)] 14 | let $o := map:new(( 15 | map:entry("server", xdmp:server-name($status/*:server-id)), 16 | map:entry("host", xdmp:host-name($status/*:host-id)), 17 | map:entry("modules", if ($status/*:modules = 0) then "FileSystem" else xdmp:database-name($status/*:modules)), 18 | map:entry("database", if ($status/*:database = 0) then "FileSystem" else xdmp:database-name($status/*:database)), 19 | for $item in $status/*[fn:not(self::*:server-id or self::*:host-id or self::*:modules or self::*:database)] 20 | return 21 | map:entry(functx:words-to-camel-case(fn:replace(fn:local-name($item), "-", " ")), $item/fn:data()) 22 | )) 23 | return 24 | json:array-push($a, $o) 25 | return 26 | xdmp:to-json($a) 27 | 28 | -------------------------------------------------------------------------------- /src/main/ui/app/login/login.component.scss: -------------------------------------------------------------------------------- 1 | /deep/ body { 2 | background-color: #f3f3ff; 3 | } 4 | 5 | /deep/ .mdl-card__supporting-text { 6 | width: 100%; 7 | } 8 | 9 | h3 { 10 | text-align: center; 11 | width: 100%; 12 | } 13 | 14 | :host { 15 | margin-top: 4em; 16 | display: block; 17 | } 18 | 19 | .login-box { 20 | width: 500px; 21 | margin: 0pt auto; 22 | 23 | form { 24 | @-moz-document url-prefix() { 25 | .fa { 26 | position: relative; 27 | top: -30px; 28 | } 29 | 30 | // mdl-textfield.mdl-textfield { 31 | // margin-top: 20px; 32 | // padding: 20px 0px 0px 0px; 33 | // } 34 | } 35 | 36 | .fa { 37 | margin-left: -14px; 38 | } 39 | 40 | .fa-times { 41 | color: red; 42 | } 43 | 44 | .fa-check { 45 | color: green; 46 | } 47 | 48 | 49 | .error { 50 | background: rgba(255,0,0,0.5); 51 | padding: 10px; 52 | border-radius: 2px; 53 | color: #333; 54 | margin-bottom: 20px; 55 | } 56 | } 57 | } 58 | 59 | .current-server { 60 | text-transform: none; 61 | } 62 | 63 | .mdl-card, .mdl-card__supporting-text { 64 | overflow: initial; 65 | } 66 | 67 | .mdl-card__supporting-text { 68 | text-align: center; 69 | } 70 | 71 | #start-debugging { 72 | margin-top: 20px; 73 | } 74 | -------------------------------------------------------------------------------- /src/main/ui/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular and is loaded before the app. 2 | // You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | 21 | // If you need to support the browsers/features below, uncomment the import 22 | // and run `npm install import-name-here'; 23 | // Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 24 | 25 | // Needed for: IE9 26 | // import 'classlist.js'; 27 | 28 | // Animations 29 | // Needed for: All but Chrome and Firefox, Not supported in IE9 30 | // import 'web-animations-js'; 31 | 32 | // Date, currency, decimal and percent pipes 33 | // Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 34 | // import 'intl'; 35 | 36 | // NgClass on SVG elements 37 | // Needed for: IE10, IE11 38 | // import 'classlist.js'; 39 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-request.xqy: -------------------------------------------------------------------------------- 1 | xquery version "1.0-ml"; 2 | 3 | declare namespace server = "http://marklogic.com/xdmp/status/server"; 4 | 5 | import module namespace functx = "http://www.functx.com" 6 | at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy"; 7 | 8 | declare variable $serverId external; 9 | declare variable $requestId external; 10 | 11 | let $current-request := xs:unsignedLong($requestId) 12 | let $status := xdmp:server-status(xdmp:host(), (xdmp:server("TaskServer"), xs:unsignedLong($serverId)))/server:request-statuses/server:request-status[fn:not(server:request-id = $current-request)] 13 | let $o := 14 | if (fn:exists($status)) then 15 | map:new(( 16 | map:entry("server", xdmp:server-name($status/*:server-id)), 17 | map:entry("host", xdmp:host-name($status/*:host-id)), 18 | map:entry("modules", if ($status/*:modules = 0) then "FileSystem" else xdmp:database-name($status/*:modules)), 19 | map:entry("database", if ($status/*:database = 0) then "FileSystem" else xdmp:database-name($status/*:database)), 20 | for $item in $status/*[fn:not(self::*:server-id or self::*:host-id or self::*:modules or self::*:database)] 21 | return 22 | map:entry(functx:words-to-camel-case(fn:replace(fn:local-name($item), "-", " ")), $item/fn:data()) 23 | )) 24 | else 25 | map:new(()) 26 | return 27 | $o 28 | -------------------------------------------------------------------------------- /src/main/ui/app/help/startup.component.scss: -------------------------------------------------------------------------------- 1 | /deep/ .mdl-dialog { 2 | width: 500px; 3 | } 4 | 5 | /deep/ mdl-dialog-host-component { 6 | padding: 0px; 7 | } 8 | 9 | .error-dialog { 10 | min-width: 500px; 11 | width: 500px; 12 | } 13 | 14 | .mdl-dialog__content { 15 | max-height: 400px; 16 | overflow-y: scroll; 17 | } 18 | 19 | .mdl-dialog__actions { 20 | border-top: 1px solid #d9d9d9; 21 | } 22 | 23 | /deep/ mdl-switch.mdl-switch.debug-instructions { 24 | display: inline; 25 | top: -5px; 26 | } 27 | 28 | .mdl-button[disabled][disabled].help-button { 29 | background-color: rgba(158,158,158, 0.20); 30 | } 31 | 32 | pre { 33 | width: 100%; 34 | overflow: auto; 35 | } 36 | 37 | .mdl-dialog__title { 38 | 39 | font-size: 1.5rem; 40 | 41 | .mdl-button--fab.mdl-button--mini-fab { 42 | height: 30px; 43 | min-width: 30px; 44 | width: 30px; 45 | } 46 | 47 | display: flex; 48 | flex-direction: row; 49 | align-items: center; 50 | 51 | padding: 12px 24px 12px; 52 | 53 | background-color: #222; 54 | 55 | color: white; 56 | } 57 | 58 | .instructions { 59 | margin: 0px 20px; 60 | } 61 | 62 | .show-on-startup { 63 | // float: left; 64 | height: auto; 65 | span { 66 | color: rgba(0,0,0, 0.54); 67 | } 68 | } 69 | 70 | .scroll { 71 | position: fixed; 72 | right: 10px; 73 | bottom: 135px; 74 | text-align: center; 75 | } 76 | -------------------------------------------------------------------------------- /src/main/ui/app/file-browser/file-browser.component.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /src/main/ui/app/grid/grid.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | /deep/ [gm-col] + gm-divider, 8 | /deep/ [gm-col] + [gm-divider] { 9 | display: flex; 10 | width: 1px; 11 | cursor: col-resize; 12 | background-color: #ccc; 13 | &:after { 14 | content: ''; 15 | left: -8px; 16 | position: relative; 17 | padding: 0px 9px; 18 | } 19 | 20 | > * { 21 | cursor: initial; 22 | } 23 | } 24 | 25 | /deep/ [gm-row] + gm-divider, 26 | /deep/ [gm-row] + [gm-divider] { 27 | display: flex; 28 | height: 1px; 29 | cursor: row-resize; 30 | background-color: #ccc; 31 | &:after { 32 | content: ''; 33 | width: 100%; 34 | top: -8px; 35 | position: relative; 36 | padding: 9px 0px; 37 | } 38 | 39 | > * { 40 | cursor: initial; 41 | } 42 | 43 | } 44 | 45 | /deep/ [gm-row], [gm-col] { 46 | box-sizing: border-box; 47 | display: -webkit-box; 48 | display: -webkit-flex; 49 | display: -moz-box; 50 | display: -ms-flexbox; 51 | display: flex; 52 | } 53 | 54 | /deep/ [gm-col] { 55 | flex-direction: column; 56 | } 57 | 58 | /deep/ [gm-row] { 59 | flex-direction: row; 60 | } 61 | 62 | /deep/ .flex { 63 | display: flex; 64 | } 65 | 66 | @mixin flex-widths($name: null) { 67 | $flexName: 'flex'; 68 | @for $i from 0 through 20 { 69 | $value : #{($i * 5) / 100}; 70 | 71 | /deep/ .#{$flexName}-#{$i * 5} { 72 | flex: #{$value} 1 0% 73 | } 74 | } 75 | } 76 | 77 | @include flex-widths(); 78 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.28.3", 4 | "name": "marklogic-debugger" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src/main/ui", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.json", 19 | "prefix": "app", 20 | "styles": [ 21 | "styles.scss" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "e2e": { 32 | "protractor": { 33 | "config": "./protractor.conf.js" 34 | } 35 | }, 36 | "lint": [ 37 | { 38 | "files": "src/main/ui/**/*.ts", 39 | "project": "src/main/ui/tsconfig.json" 40 | }, 41 | { 42 | "files": "e2e/**/*.ts", 43 | "project": "e2e/tsconfig.json" 44 | } 45 | ], 46 | "test": { 47 | "karma": { 48 | "config": "./karma.conf.js" 49 | } 50 | }, 51 | "defaults": { 52 | "styleExt": "scss", 53 | "prefixInterfaces": false, 54 | "inline": { 55 | "style": false, 56 | "template": false 57 | }, 58 | "spec": { 59 | "class": false, 60 | "component": true, 61 | "directive": true, 62 | "module": false, 63 | "pipe": true, 64 | "service": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/ui/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | import { RouterModule } from '@angular/router'; 6 | import { MdlModule } from 'angular2-mdl'; 7 | import { GridManiaModule } from './grid'; 8 | 9 | import { CodemirrorComponent } from './codemirror'; 10 | import { AUTH_PROVIDERS } from './auth'; 11 | 12 | import { AppComponent } from './app.component'; 13 | import { FileBrowserComponent } from './file-browser'; 14 | import { HeaderComponent } from './header'; 15 | import { HomeComponent } from './home'; 16 | import { LoginComponent } from './login'; 17 | import { ErrorComponent } from './error'; 18 | import { SubsectionComponent } from './subsection'; 19 | import { MarkLogicService } from './marklogic'; 20 | import { ROUTES } from './app.routes'; 21 | import { StartupComponent } from './help'; 22 | 23 | @NgModule({ 24 | declarations: [ 25 | AppComponent, 26 | FileBrowserComponent, 27 | HeaderComponent, 28 | HomeComponent, 29 | LoginComponent, 30 | ErrorComponent, 31 | SubsectionComponent, 32 | CodemirrorComponent, 33 | StartupComponent 34 | ], 35 | imports: [ 36 | BrowserModule, 37 | FormsModule, 38 | HttpModule, 39 | RouterModule.forRoot(ROUTES, { useHash: true }), 40 | // MaterialModule.forRoot() 41 | MdlModule, 42 | GridManiaModule 43 | ], 44 | entryComponents: [ 45 | ErrorComponent, 46 | StartupComponent 47 | ], 48 | providers: [ 49 | AUTH_PROVIDERS, 50 | MarkLogicService 51 | ], 52 | bootstrap: [AppComponent] 53 | }) 54 | export class AppModule { } 55 | -------------------------------------------------------------------------------- /src/main/ui/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { AuthModel, AuthService } from '../auth'; 4 | import { MarkLogicService } from '../marklogic'; 5 | 6 | @Component({ 7 | selector: 'app-login-form', 8 | templateUrl: './login.component.html', 9 | styleUrls: ['./login.component.scss'] 10 | }) 11 | export class LoginComponent { 12 | authInfo: AuthModel = new AuthModel('localhost', 8000, 'admin', 'admin'); 13 | appServers: Array; 14 | currentServer: any; 15 | invalidLogin: boolean = false; 16 | serverOk: boolean = false; 17 | 18 | constructor( 19 | private authService: AuthService, 20 | private marklogicService: MarkLogicService, 21 | private router: Router) { 22 | this.checkServer(); 23 | } 24 | 25 | updateHostname(hostname: any) { 26 | this.authInfo.hostname = hostname; 27 | this.checkServer(); 28 | } 29 | 30 | updatePort(port: any) { 31 | this.authInfo.port = port; 32 | this.checkServer(); 33 | } 34 | 35 | checkServer(): void { 36 | this.authService.checkServer(this.authInfo).subscribe((res: any) => { 37 | this.serverOk = res.result; 38 | }, 39 | (error: any) => { 40 | this.serverOk = false; 41 | }); 42 | } 43 | 44 | login(): void { 45 | this.invalidLogin = false; 46 | this.authService.login(this.authInfo).subscribe(() => { 47 | this.marklogicService.getServers().subscribe((servers) => { 48 | this.appServers = servers; 49 | }); 50 | }, 51 | (error: any) => { 52 | if (error.status === 401) { 53 | this.invalidLogin = true; 54 | } 55 | }); 56 | } 57 | 58 | selectServer(server) { 59 | this.currentServer = server; 60 | 61 | } 62 | 63 | startDebugging() { 64 | this.router.navigate(['server', this.currentServer.name]); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/auth/AuthSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.auth; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 5 | import org.springframework.security.web.savedrequest.HttpSessionRequestCache; 6 | import org.springframework.security.web.savedrequest.RequestCache; 7 | import org.springframework.security.web.savedrequest.SavedRequest; 8 | import org.springframework.util.StringUtils; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | 15 | public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { 16 | 17 | private RequestCache requestCache = new HttpSessionRequestCache(); 18 | 19 | @Override 20 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 21 | Authentication authentication) throws ServletException, IOException { 22 | SavedRequest savedRequest = requestCache.getRequest(request, response); 23 | 24 | if (savedRequest == null) { 25 | clearAuthenticationAttributes(request); 26 | return; 27 | } 28 | 29 | String targetUrlParam = getTargetUrlParameter(); 30 | if (isAlwaysUseDefaultTargetUrl() || 31 | (targetUrlParam != null && 32 | StringUtils.hasText(request.getParameter(targetUrlParam)))) { 33 | requestCache.removeRequest(request, response); 34 | clearAuthenticationAttributes(request); 35 | return; 36 | } 37 | 38 | clearAuthenticationAttributes(request); 39 | } 40 | 41 | public void setRequestCache(RequestCache requestCache) { 42 | this.requestCache = requestCache; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-marklogic-system-files.xqy: -------------------------------------------------------------------------------- 1 | xquery version "1.0-ml"; 2 | 3 | declare option xdmp:mapping "false"; 4 | 5 | declare variable $ml-dir := xdmp:filesystem-filepath('.') || '/Modules'; 6 | declare variable $start-dir := $ml-dir || '/MarkLogic'; 7 | 8 | declare function local:get-system-files($dir as xs:string, $a as json:array) { 9 | for $entry in xdmp:filesystem-directory($dir)/dir:entry[dir:type = "file"][fn:not(dir:filename = '.DS_Store')] 10 | let $o := json:object() 11 | let $_ := map:put($o, "name", fn:string($entry/dir:filename)) 12 | let $_ := map:put($o, "type", "file") 13 | let $_ := map:put($o, "uri", fn:replace($entry/dir:pathname, $ml-dir, "")) 14 | return 15 | json:array-push($a, $o) 16 | }; 17 | 18 | declare function local:get-system-dirs($dir as xs:string, $a as json:array) { 19 | for $entry in xdmp:filesystem-directory($dir)/dir:entry[dir:type = "directory"] 20 | return 21 | let $o := json:object() 22 | let $children := json:array() 23 | let $_ := local:get-system-dirs($entry/dir:pathname, $children) 24 | let $_ := local:get-system-files($entry/dir:pathname, $children) 25 | let $_ := map:put($o, "name", fn:string($entry/dir:filename)) 26 | let $_ := map:put($o, "uri", fn:replace($entry/dir:pathname, $ml-dir, "")) 27 | let $_ := map:put($o, "type", "dir") 28 | let $_ := map:put($o, "collapsed", fn:true()) 29 | let $_ := map:put($o, "children", $children) 30 | return 31 | json:array-push($a, $o) 32 | }; 33 | 34 | let $obj := 35 | let $o := json:object() 36 | let $_ := map:put($o, "name", "/MarkLogic") 37 | let $_ := map:put($o, "uri", $ml-dir) 38 | let $_ := map:put($o, "type", "dir") 39 | let $_ := map:put($o, "collapsed", fn:true()) 40 | let $children := json:array() 41 | let $_ := local:get-system-dirs($start-dir, $children) 42 | let $_ := local:get-system-files($start-dir, $children) 43 | let $_ := map:put($o, "children", $children) 44 | return 45 | $o 46 | return 47 | xdmp:to-json($obj) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marklogic-debugger", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "rimraf": "rimraf", 8 | "ng": "ng", 9 | "start": "ng serve --host 0.0.0.0 -pc proxy.config.json", 10 | "lint": "tslint \"src/main/ui/**/*.ts\"", 11 | "test": "ng test", 12 | "pree2e": "webdriver-manager update --standalone false --gecko false", 13 | "e2e": "protractor", 14 | "clean.dist": "npm run rimraf -- dist", 15 | "build.prod": "npm run clean.dist && ng build -prod" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/common": "^2.3.1", 20 | "@angular/compiler": "^2.3.1", 21 | "@angular/core": "^2.3.1", 22 | "@angular/forms": "^2.3.1", 23 | "@angular/http": "^2.3.1", 24 | "@angular/platform-browser": "^2.3.1", 25 | "@angular/platform-browser-dynamic": "^2.3.1", 26 | "@angular/router": "^3.3.1", 27 | "angular2-mdl": "^2.13.1", 28 | "codemirror": "^5.23.0", 29 | "core-js": "^2.4.1", 30 | "font-awesome": "^4.7.0", 31 | "lodash": "^4.17.4", 32 | "material-design-icons": "^3.0.1", 33 | "mdi": "^1.8.36", 34 | "ng2-codemirror": "^1.1.1", 35 | "rxjs": "^5.0.1", 36 | "ts-helpers": "^1.1.1", 37 | "zone.js": "^0.7.2" 38 | }, 39 | "devDependencies": { 40 | "@types/codemirror": "0.0.38", 41 | "@types/jasmine": "^2.5.2", 42 | "@types/lodash": "^4.14.52", 43 | "@angular/compiler-cli": "^2.3.1", 44 | "@types/jasmine": "2.5.38", 45 | "@types/node": "^6.0.42", 46 | "angular-cli": "1.0.0-beta.28.3", 47 | "codelyzer": "~2.0.0-beta.1", 48 | "jasmine-core": "2.5.2", 49 | "jasmine-spec-reporter": "2.5.0", 50 | "karma": "1.2.0", 51 | "karma-chrome-launcher": "^2.0.0", 52 | "karma-cli": "^1.0.1", 53 | "karma-jasmine": "^1.0.2", 54 | "karma-remap-istanbul": "^0.2.1", 55 | "npm": "3.10.8", 56 | "protractor": "~4.0.13", 57 | "rimraf": "^2.5.4", 58 | "ts-node": "1.2.1", 59 | "tslint": "^4.3.0", 60 | "typescript": "~2.0.3" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-stacktrace.xqy: -------------------------------------------------------------------------------- 1 | declare variable $requestId external; 2 | 3 | declare function local:build-var-array($vars) { 4 | let $array := json:array() 5 | let $_ := 6 | for $var in $vars 7 | return 8 | json:array-push($array, map:new(( 9 | map:entry("name", $var/*:name/fn:data()), 10 | map:entry("prefix", $var/*:prefix/fn:data()), 11 | map:entry("value", $var/*:value/fn:data()) 12 | ))) 13 | return 14 | $array 15 | }; 16 | 17 | let $stack := dbg:stack(xs:unsignedLong($requestId)) 18 | let $e := json:array() 19 | let $_ := 20 | for $expr in $stack/*:expr 21 | let $expression := map:new(( 22 | map:entry("expressionId", $expr/*:expr-id/fn:data()), 23 | map:entry("expressionSource", $expr/*:expr-source/fn:data()), 24 | map:entry("uri", $expr/*:uri/fn:data()), 25 | map:entry("location", map:new(( 26 | map:entry("database", $expr/*:location/*:database/fn:data()), 27 | map:entry("uri", $expr/*:location/*:uri/fn:data()) 28 | ))), 29 | map:entry("line", $expr/*:line/fn:data()), 30 | map:entry("column", $expr/*:column/fn:data()), 31 | map:entry("globalVariables", local:build-var-array($expr/*:global-variables/*:global-variable)), 32 | map:entry("externalVariables", local:build-var-array($expr/*:external-variables/*:external-variable)) 33 | )) 34 | return 35 | json:array-push($e, $expression) 36 | let $f := json:array() 37 | let $_ := 38 | for $frame in $stack/*:frame 39 | return 40 | json:array-push($f, map:new(( 41 | map:entry("uri", $frame/*:uri/fn:data()), 42 | map:entry("location", map:new(( 43 | map:entry("database", $frame/*:location/*:database/fn:data()), 44 | map:entry("uri", $frame/*:location/*:uri/fn:data()) 45 | ))), 46 | map:entry("line", $frame/*:line/fn:data()), 47 | map:entry("column", $frame/*:line/fn:data()), 48 | map:entry("globalVariables", local:build-var-array($frame/*:global-variables/*:global-variable)), 49 | map:entry("externalVariables", local:build-var-array($frame/*:external-variables/*:external-variable)), 50 | map:entry("variables", local:build-var-array($frame/*:variables/*:variable)) 51 | ))) 52 | return 53 | xdmp:to-json(map:new(( 54 | map:entry("expressions", $e), 55 | map:entry("frames", $f) 56 | ))) 57 | -------------------------------------------------------------------------------- /src/main/ui/app/auth/http-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Http, Request, RequestOptionsArgs, Response, 3 | RequestOptions, ConnectionBackend, Headers, XHRBackend 4 | } from '@angular/http'; 5 | import { Router } from '@angular/router'; 6 | import { Observable } from 'rxjs/Rx'; 7 | 8 | import * as _ from 'lodash'; 9 | 10 | class HttpInterceptor extends Http { 11 | constructor( 12 | backend: ConnectionBackend, 13 | defaultOptions: RequestOptions, 14 | private _router: Router) { 15 | super(backend, defaultOptions); 16 | } 17 | 18 | request(url: string | Request, options?: RequestOptionsArgs): Observable { 19 | return this.intercept(super.request(url, options)); 20 | } 21 | 22 | get(url: string, options?: RequestOptionsArgs): Observable { 23 | return this.intercept(super.get(url, options)); 24 | } 25 | 26 | post(url: string, body: string, options?: RequestOptionsArgs): Observable { 27 | return this.intercept(super.post(url, body, this.getRequestOptionArgs(options))); 28 | } 29 | 30 | put(url: string, body: string, options?: RequestOptionsArgs): Observable { 31 | return this.intercept(super.put(url, body, this.getRequestOptionArgs(options))); 32 | } 33 | 34 | delete(url: string, options?: RequestOptionsArgs): Observable { 35 | return this.intercept(super.delete(url, options)); 36 | } 37 | 38 | getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs { 39 | if (options == null) { 40 | options = new RequestOptions(); 41 | } 42 | if (options.headers == null) { 43 | options.headers = new Headers(); 44 | } 45 | if (!options.headers.has('Content-Type')) { 46 | options.headers.append('Content-Type', 'application/json'); 47 | } 48 | return options; 49 | } 50 | 51 | intercept(observable: Observable): Observable { 52 | return observable.catch((err, source) => { 53 | if (err.status === 401 && !_.endsWith(err.url, '/login')) { 54 | this._router.navigate(['login']); 55 | return Observable.empty(null); 56 | } else { 57 | return Observable.throw(err); 58 | } 59 | }); 60 | 61 | } 62 | } 63 | 64 | export function interceptorFactory( 65 | xhrBackend: XHRBackend, 66 | requestOptions: RequestOptions, 67 | router: Router 68 | ) { 69 | return new HttpInterceptor(xhrBackend, requestOptions, router); 70 | } 71 | 72 | export const HTTP_PROVIDER = { 73 | provide: Http, 74 | useFactory: interceptorFactory, 75 | deps: [XHRBackend, RequestOptions, Router] 76 | }; 77 | -------------------------------------------------------------------------------- /src/main/ui/app/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, EventEmitter } from '@angular/core'; 2 | import { Headers, Http, RequestOptions, Response } from '@angular/http'; 3 | 4 | import { AuthModel } from './auth.model'; 5 | 6 | @Injectable() 7 | export class AuthService { 8 | authenticated: EventEmitter = new EventEmitter(); 9 | redirectUrl: string; 10 | 11 | AUTH_HOST: string = '_authed_host_'; 12 | AUTH_PORT: string = '_authed_port_'; 13 | 14 | constructor(private http: Http) {} 15 | 16 | isAuthenticated() { 17 | return localStorage.getItem('_isAuthenticated_') === 'true'; 18 | } 19 | 20 | get hostname() : string { 21 | return localStorage.getItem(this.AUTH_HOST); 22 | } 23 | 24 | get port() : number { 25 | return parseInt(localStorage.getItem(this.AUTH_PORT), 10); 26 | } 27 | 28 | setAuthenticated(authed: boolean, hostname: string, port: number) { 29 | localStorage.setItem('_isAuthenticated_', authed.toString()); 30 | if (hostname) { 31 | localStorage.setItem(this.AUTH_HOST, hostname); 32 | } else { 33 | localStorage.removeItem(this.AUTH_HOST); 34 | } 35 | 36 | if (port) { 37 | localStorage.setItem(this.AUTH_PORT, port.toString()); 38 | } else { 39 | localStorage.removeItem(this.AUTH_PORT); 40 | } 41 | this.authenticated.emit(authed); 42 | } 43 | 44 | checkServer(authInfo: AuthModel) { 45 | return this.http.get(`/api/server/status?host=${authInfo.hostname}&port=${authInfo.port}`).map((resp: Response) => { 46 | return resp.json(); 47 | }); 48 | } 49 | 50 | login(authInfo: AuthModel) { 51 | const body = this.formData({ 52 | username: authInfo.username, 53 | password: authInfo.password, 54 | hostname: authInfo.hostname, 55 | port: authInfo.port 56 | }); 57 | let headers = new Headers(); 58 | headers.set('Content-Type', 'application/x-www-form-urlencoded'); 59 | let options = new RequestOptions({ 60 | headers: headers, 61 | method: 'POST' 62 | }); 63 | let resp = this.http.post('/api/user/login', body, options).share(); 64 | resp.subscribe(() => { 65 | this.setAuthenticated(true, authInfo.hostname, authInfo.port); 66 | }, 67 | (error) => {}); 68 | return resp; 69 | } 70 | 71 | formData(data) { 72 | return Object.keys(data).map((key) => { 73 | return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]); 74 | }).join('&'); 75 | } 76 | 77 | logout() { 78 | let resp = this.http.delete('/api/user/logout').share(); 79 | resp.subscribe(() => { 80 | this.setAuthenticated(false, null, null); 81 | }); 82 | return resp; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/ui/app/help/startup.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Welcome to the MarkLogic Debugger!

4 |
5 |

There are 3 ways to debug your requests. (scroll to see them all)

6 | 7 |
1. Auto-pause requests
8 |
9 |

With this option you tell MarkLogic to automatically pause subsequent requests on 10 | the first instruction.

11 |

Enable this method by turning on Auto-pause in the requests pane

12 |

Once enabled you can refresh the request list by clicking on the refresh button: .

13 |

Then click on the request you wish to debug.

14 |
15 | 16 |
2. dbg:stop()
17 |
18 |

This method is when you put a call to db:stop() somewhere in your code.

19 |

This will force MarkLogic to stop execution of your code on the db:stop() statement.

20 |

Once you run your code you will need to refresh the request list by clicking on the refresh button: .

21 |

Then click on the request you wish to debug.

22 |
23 | 24 |
3. Pause a long running request
25 |
26 |

This method is when you tell MarkLogic to pause a long running request so you can debug it.

27 |

Refresh the request list by clicking on the refresh button: .

28 |

Then click on the pause button next to the request you wish to debug.

29 |
30 | 31 |
4. Invoke a main module on the server
32 |
33 |

This method is when you tell MarkLogic to run a specific main module in debug mode. MarkLogic will pause the module on the first instruction.

34 |

To do this, select a main module in the file browser.

35 |

Then click on the play icon above the source code view.

36 |

Refresh the request list by clicking on the refresh button: .

37 |

Then click on the newly launched request you wish to debug.

38 |
39 |
40 |
41 | 42 |
scroll
43 |
44 |
45 |
46 |
47 | 48 | Show this message on Startup 49 | 50 | This help is available from the top right menu. Simply click on the button. 51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /src/main/ui/app/material.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { MdButtonToggleModule } from '@angular2-material/button-toggle'; 3 | import { MdButtonModule } from '@angular2-material/button'; 4 | import { MdCheckboxModule } from '@angular2-material/checkbox'; 5 | import { MdRadioModule } from '@angular2-material/radio'; 6 | import { MdSlideToggleModule } from '@angular2-material/slide-toggle'; 7 | import { MdSliderModule } from '@angular2-material/slider'; 8 | import { MdSidenavModule } from '@angular2-material/sidenav'; 9 | import { MdListModule } from '@angular2-material/list'; 10 | import { MdGridListModule } from '@angular2-material/grid-list'; 11 | import { MdCardModule } from '@angular2-material/card'; 12 | import { MdIconModule } from '@angular2-material/icon'; 13 | import { MdProgressCircleModule } from '@angular2-material/progress-circle'; 14 | import { MdProgressBarModule } from '@angular2-material/progress-bar'; 15 | import { MdInputModule } from '@angular2-material/input'; 16 | import { MdTabsModule } from '@angular2-material/tabs'; 17 | import { MdToolbarModule } from '@angular2-material/toolbar'; 18 | import { MdTooltipModule } from '@angular2-material/tooltip'; 19 | import { 20 | MdLiveAnnouncer, 21 | MdRippleModule, 22 | RtlModule, 23 | PortalModule, 24 | OverlayModule 25 | } from '@angular2-material/core'; 26 | import { MdMenuModule} from '@angular2-material/menu'; 27 | 28 | const MATERIAL_MODULES = [ 29 | MdButtonModule, 30 | MdButtonToggleModule, 31 | MdCardModule, 32 | MdCheckboxModule, 33 | MdGridListModule, 34 | MdIconModule, 35 | MdInputModule, 36 | MdListModule, 37 | MdMenuModule, 38 | MdProgressBarModule, 39 | MdProgressCircleModule, 40 | MdRadioModule, 41 | MdRippleModule, 42 | MdSidenavModule, 43 | MdSliderModule, 44 | MdSlideToggleModule, 45 | MdTabsModule, 46 | MdToolbarModule, 47 | MdTooltipModule, 48 | OverlayModule, 49 | PortalModule, 50 | RtlModule, 51 | ]; 52 | 53 | @NgModule({ 54 | imports: [ 55 | MdButtonModule.forRoot(), 56 | MdCardModule.forRoot(), 57 | MdCheckboxModule.forRoot(), 58 | MdGridListModule.forRoot(), 59 | MdInputModule.forRoot(), 60 | MdListModule.forRoot(), 61 | MdProgressBarModule.forRoot(), 62 | MdProgressCircleModule.forRoot(), 63 | MdRippleModule.forRoot(), 64 | MdSidenavModule.forRoot(), 65 | MdTabsModule.forRoot(), 66 | MdToolbarModule.forRoot(), 67 | PortalModule.forRoot(), 68 | RtlModule.forRoot(), 69 | 70 | // These modules include providers. 71 | MdButtonToggleModule.forRoot(), 72 | MdIconModule.forRoot(), 73 | MdMenuModule.forRoot(), 74 | MdRadioModule.forRoot(), 75 | MdSliderModule.forRoot(), 76 | MdSlideToggleModule.forRoot(), 77 | MdTooltipModule.forRoot(), 78 | OverlayModule.forRoot(), 79 | ], 80 | exports: MATERIAL_MODULES, 81 | providers: [MdLiveAnnouncer] 82 | }) 83 | export class MaterialRootModule { } 84 | 85 | 86 | @NgModule({ 87 | imports: MATERIAL_MODULES, 88 | exports: MATERIAL_MODULES, 89 | }) 90 | export class MaterialModule { 91 | static forRoot(): ModuleWithProviders { 92 | return {ngModule: MaterialRootModule}; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/ui/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

MarkLogic Debugger

5 | 36 | 37 | 56 |
57 |
58 |
59 | 60 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs"], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": true, 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/auth/ConnectionAuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.auth; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.security.authentication.AbstractAuthenticationToken; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.SpringSecurityCoreVersion; 8 | 9 | /** 10 | * An {@link org.springframework.security.core.Authentication} implementation that is 11 | * designed for simple presentation of a username and password. 12 | *

13 | * The principal and credentials should be set with an 14 | * Object that provides the respective property via its 15 | * Object.toString() method. The simplest such Object to use is 16 | * String. 17 | * 18 | * @author Ben Alex 19 | */ 20 | public class ConnectionAuthenticationToken extends AbstractAuthenticationToken { 21 | 22 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; 23 | 24 | // ~ Instance fields 25 | // ================================================================================================ 26 | 27 | private final Object principal; 28 | private Object credentials; 29 | private Object hostname; 30 | private Object port; 31 | 32 | // ~ Constructors 33 | // =================================================================================================== 34 | 35 | /** 36 | * This constructor can be safely used by any code that wishes to create a 37 | * ConnectionAuthenticationToken, as the {@link #isAuthenticated()} 38 | * will return false. 39 | * 40 | */ 41 | public ConnectionAuthenticationToken(Object principal, Object credentials, Object hostname, Object port) { 42 | super(null); 43 | this.principal = principal; 44 | this.credentials = credentials; 45 | this.hostname = hostname; 46 | this.port = port; 47 | setAuthenticated(false); 48 | } 49 | 50 | /** 51 | * This constructor should only be used by AuthenticationManager or 52 | * AuthenticationProvider implementations that are satisfied with 53 | * producing a trusted (i.e. {@link #isAuthenticated()} = true) 54 | * authentication token. 55 | * 56 | * @param principal 57 | * @param credentials 58 | * @param authorities 59 | */ 60 | public ConnectionAuthenticationToken(Object principal, Object credentials, Object hostname, Object port, 61 | Collection authorities) { 62 | super(authorities); 63 | this.principal = principal; 64 | this.credentials = credentials; 65 | this.hostname = hostname; 66 | this.port = port; 67 | super.setAuthenticated(true); // must use super, as we override 68 | } 69 | 70 | // ~ Methods 71 | // ======================================================================================================== 72 | 73 | public Object getCredentials() { 74 | return this.credentials; 75 | } 76 | 77 | public Object getPrincipal() { 78 | return this.principal; 79 | } 80 | 81 | public Object getHostname() { 82 | return this.hostname; 83 | } 84 | 85 | public Object getPort() { return this.port; } 86 | 87 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 88 | if (isAuthenticated) { 89 | throw new IllegalArgumentException( 90 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); 91 | } 92 | 93 | super.setAuthenticated(false); 94 | } 95 | 96 | @Override 97 | public void eraseCredentials() { 98 | super.eraseCredentials(); 99 | credentials = null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/ui/app/grid/grid.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OnInit, 3 | Component, 4 | ElementRef, 5 | HostListener, 6 | OnDestroy, 7 | Renderer, 8 | } from '@angular/core'; 9 | 10 | @Component({ 11 | selector: '[gm-grid]', 12 | template: '', 13 | styleUrls: ['./grid.component.scss'] 14 | }) 15 | export class GridManiaComponent {} 16 | 17 | @Component({ 18 | selector: 'gm-divider,[gm-divider]', 19 | template: '' 20 | }) 21 | export class DividerComponent implements OnDestroy, OnInit { 22 | protected resizing: boolean = false; 23 | protected documentColumnResizeListener: any; 24 | protected documentColumnResizeEndListener: any; 25 | protected draggerPosX: number; 26 | protected draggerPosY: number; 27 | protected draggerWidth: number; 28 | protected draggerHeight: number; 29 | protected previousCursor: any; 30 | protected horizontal: boolean = false; 31 | 32 | protected container: any; 33 | protected prev: any; 34 | protected next: any; 35 | 36 | @HostListener('mousedown', ['$event']) onMousedown(event: MouseEvent) { 37 | if (event.target === this.container && event.button === 0 && !event.ctrlKey) { 38 | if (this.resizing) { 39 | this.onMouseUp(event); 40 | return; 41 | } 42 | 43 | this.resizing = true; 44 | 45 | this.draggerWidth = this.container.offsetWidth; 46 | this.draggerHeight = this.container.offsetHeight; 47 | this.draggerPosX = this.container.getBoundingClientRect().left + document.body.scrollLeft + this.draggerWidth - event.pageX; 48 | this.draggerPosY = this.container.getBoundingClientRect().top + document.body.scrollTop + this.draggerHeight - event.pageY; 49 | this.previousCursor = document.body.style['cursor']; 50 | document.body.style['cursor'] = this.horizontal ? 'col-resize' : 'row-resize'; 51 | } 52 | } 53 | 54 | constructor(protected el: ElementRef, protected renderer: Renderer) {} 55 | 56 | ngOnInit() { 57 | this.container = this.el.nativeElement; 58 | this.prev = this.container.previousElementSibling; 59 | this.next = this.container.nextElementSibling; 60 | this.horizontal = this.prev.hasAttribute('gm-col'); 61 | 62 | this.documentColumnResizeListener = this.renderer.listenGlobal('body', 'mousemove', (event) => { 63 | if (this.resizing) { 64 | if (this.horizontal) { 65 | this.onHorizontalResize(event); 66 | } else { 67 | this.onVerticalResize(event); 68 | } 69 | } 70 | }); 71 | 72 | this.documentColumnResizeEndListener = this.renderer.listenGlobal('body', 'mouseup', this.onMouseUp); 73 | } 74 | 75 | onMouseUp = (event) => { 76 | if (this.resizing) { 77 | this.resizing = false; 78 | document.body.style['cursor'] = this.previousCursor; 79 | } 80 | } 81 | 82 | onHorizontalResize(event) { 83 | const totalWidth = this.prev.offsetWidth + this.next.offsetWidth; 84 | 85 | let leftPercentage = ( 86 | ( 87 | (event.pageX - this.prev.getBoundingClientRect().left + document.body.scrollLeft) + 88 | (this.draggerPosX - this.draggerWidth / 2) 89 | ) / totalWidth 90 | ); 91 | let rightPercentage = 1 - leftPercentage; 92 | 93 | this.prev.style['flex'] = leftPercentage.toString(); 94 | this.next.style['flex'] = rightPercentage.toString(); 95 | } 96 | 97 | onVerticalResize(event) { 98 | const totalHeight = this.prev.offsetHeight + this.next.offsetHeight; 99 | 100 | let topPercentage = ( 101 | ( 102 | (event.pageY - this.prev.getBoundingClientRect().top + document.body.scrollTop) + 103 | (this.draggerPosY - this.draggerHeight / 2) 104 | ) / totalHeight 105 | ); 106 | let bottomPercentage = 1 - topPercentage; 107 | 108 | this.prev.style['flex'] = topPercentage.toString(); 109 | this.next.style['flex'] = bottomPercentage.toString(); 110 | } 111 | 112 | ngOnDestroy() { 113 | if (this.documentColumnResizeListener) { 114 | this.documentColumnResizeListener(); 115 | } 116 | if (this.documentColumnResizeEndListener) { 117 | this.documentColumnResizeEndListener(); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v1.0.0-alpha.8](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.8) 4 | 5 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.7...v1.0.0-alpha.8) 6 | 7 | **Closed issues:** 8 | 9 | - Clear button collapses console window [\#26](https://github.com/paxtonhare/marklogic-debugger/issues/26) 10 | - Stack browser doesn't work correctly [\#25](https://github.com/paxtonhare/marklogic-debugger/issues/25) 11 | - Error dialog is too big [\#24](https://github.com/paxtonhare/marklogic-debugger/issues/24) 12 | 13 | ## [v1.0.0-alpha.7](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.7) (2017-02-27) 14 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.6...v1.0.0-alpha.7) 15 | 16 | **Closed issues:** 17 | 18 | - Codemirror window isn't full width [\#22](https://github.com/paxtonhare/marklogic-debugger/issues/22) 19 | - Duplicate URIs in list of modules [\#21](https://github.com/paxtonhare/marklogic-debugger/issues/21) 20 | - Document Prereqs [\#20](https://github.com/paxtonhare/marklogic-debugger/issues/20) 21 | 22 | ## [v1.0.0-alpha.6](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.6) (2017-02-14) 23 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.5...v1.0.0-alpha.6) 24 | 25 | **Closed issues:** 26 | 27 | - invoke a main module from the debugger [\#19](https://github.com/paxtonhare/marklogic-debugger/issues/19) 28 | - determine if breakpoint is valid when set [\#18](https://github.com/paxtonhare/marklogic-debugger/issues/18) 29 | - update node dependencies [\#17](https://github.com/paxtonhare/marklogic-debugger/issues/17) 30 | - Rename the "Debugging is ON|OFF" [\#16](https://github.com/paxtonhare/marklogic-debugger/issues/16) 31 | - Can we pause a long running request? [\#10](https://github.com/paxtonhare/marklogic-debugger/issues/10) 32 | - Indicate that something happened [\#3](https://github.com/paxtonhare/marklogic-debugger/issues/3) 33 | 34 | ## [v1.0.0-alpha.5](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.5) (2017-02-10) 35 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.4...v1.0.0-alpha.5) 36 | 37 | **Closed issues:** 38 | 39 | - Add scroll bar to the Console area [\#15](https://github.com/paxtonhare/marklogic-debugger/issues/15) 40 | - debug area changes height when stepping [\#14](https://github.com/paxtonhare/marklogic-debugger/issues/14) 41 | - Welcome dialog is showing many times [\#13](https://github.com/paxtonhare/marklogic-debugger/issues/13) 42 | 43 | ## [v1.0.0-alpha.4](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.4) (2017-02-10) 44 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) 45 | 46 | **Closed issues:** 47 | 48 | - Doesn't work in safari. [\#12](https://github.com/paxtonhare/marklogic-debugger/issues/12) 49 | - bug in getting files from Filesystem [\#11](https://github.com/paxtonhare/marklogic-debugger/issues/11) 50 | 51 | ## [v1.0.0-alpha.3](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.3) (2017-02-09) 52 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) 53 | 54 | **Closed issues:** 55 | 56 | - Create a custom session name to avoid stomping on other appservers [\#9](https://github.com/paxtonhare/marklogic-debugger/issues/9) 57 | - Change to non-8080 port [\#8](https://github.com/paxtonhare/marklogic-debugger/issues/8) 58 | 59 | ## [v1.0.0-alpha.2](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.2) (2017-02-09) 60 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/1.0.0-alpha.1...v1.0.0-alpha.2) 61 | 62 | **Closed issues:** 63 | 64 | - add ability to toggle or remove breakpoints [\#7](https://github.com/paxtonhare/marklogic-debugger/issues/7) 65 | - README points to a different project [\#6](https://github.com/paxtonhare/marklogic-debugger/issues/6) 66 | - ability to read files off filesystem [\#5](https://github.com/paxtonhare/marklogic-debugger/issues/5) 67 | - Need a "didn't authenticate" message [\#4](https://github.com/paxtonhare/marklogic-debugger/issues/4) 68 | - fix breakpoints [\#2](https://github.com/paxtonhare/marklogic-debugger/issues/2) 69 | - Consistent highlighting [\#1](https://github.com/paxtonhare/marklogic-debugger/issues/1) 70 | 71 | ## [1.0.0-alpha.1](https://github.com/paxtonhare/marklogic-debugger/tree/1.0.0-alpha.1) (2017-02-02) 72 | 73 | 74 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 75 | -------------------------------------------------------------------------------- /src/main/resources/modules/get-files.xqy: -------------------------------------------------------------------------------- 1 | xquery version "1.0-ml"; 2 | 3 | import module namespace admin = "http://marklogic.com/xdmp/admin" 4 | at "/MarkLogic/admin.xqy"; 5 | 6 | declare option xdmp:mapping "false"; 7 | 8 | declare variable $serverId external; 9 | 10 | declare function local:build-files($uris as xs:string*, $parent as xs:string, $a as json:array) 11 | { 12 | let $parent := 13 | if (fn:ends-with($parent, "/")) then $parent 14 | else 15 | $parent || "/" 16 | let $files := 17 | fn:distinct-values( 18 | for $uri in $uris[fn:matches(., "^" || $parent || "[^/]+$")] 19 | let $file := fn:replace($uri, "^" || $parent || "([^/]+)$", "$1") 20 | return 21 | $file 22 | ) 23 | for $file in $files 24 | let $o := json:object() 25 | let $_ := map:put($o, "name", $file) 26 | let $_ := map:put($o, "type", "file") 27 | let $_ := map:put($o, "collapsed", fn:true()) 28 | let $_ := map:put($o, "uri", $parent || $file) 29 | return 30 | json:array-push($a, $o) 31 | }; 32 | 33 | declare function local:build-dirs($uris as xs:string*, $parent as xs:string) 34 | { 35 | let $parent := 36 | if (fn:ends-with($parent, "/")) then $parent 37 | else 38 | $parent || "/" 39 | let $dirs := 40 | fn:distinct-values( 41 | for $uri in $uris[fn:matches(., "^" || $parent || "[^/]+/.*$")] 42 | let $dir := fn:replace($uri, "^" || $parent || "([^/]+)/.*$", "$1") 43 | return 44 | $dir 45 | ) 46 | let $a := json:array() 47 | let $_ := 48 | if ($parent eq '/') then 49 | local:build-files($uris, $parent, $a) 50 | else () 51 | let $_ := 52 | for $dir in $dirs 53 | let $o := json:object() 54 | let $oo := local:build-dirs($uris, $parent || $dir) 55 | let $_ := local:build-files($uris, $parent || $dir, $oo) 56 | let $_ := map:put($o, "name", $dir) 57 | let $_ := map:put($o, "uri", $parent || $dir) 58 | let $_ := map:put($o, "type", "dir") 59 | let $_ := map:put($o, "children", $oo) 60 | let $_ := map:put($o, "collapsed", fn:true()) 61 | return 62 | json:array-push($a, $o) 63 | return $a 64 | }; 65 | 66 | declare function local:get-system-files($root-dir, $dirs, $a as json:array) { 67 | for $entry in $dirs/dir:entry[dir:type = "file"] 68 | let $o := json:object() 69 | let $_ := map:put($o, "name", fn:string($entry/dir:filename)) 70 | let $_ := map:put($o, "type", "file") 71 | let $_ := map:put($o, "collapsed", fn:true()) 72 | let $_ := map:put($o, "uri", fn:replace($entry/dir:pathname, $root-dir, "")) 73 | return 74 | json:array-push($a, $o) 75 | }; 76 | 77 | declare function local:get-system-dirs($root-dir, $dirs, $a as json:array) { 78 | for $entry in $dirs/dir:entry[dir:type = "directory"] 79 | return 80 | let $o := json:object() 81 | let $children := json:array() 82 | let $child-dirs := xdmp:filesystem-directory($entry/dir:pathname) 83 | let $_ := local:get-system-dirs($root-dir, $child-dirs, $children) 84 | let $_ := local:get-system-files($root-dir, $child-dirs, $children) 85 | let $_ := map:put($o, "name", fn:string($entry/dir:filename)) 86 | let $_ := map:put($o, "uri", fn:replace($entry/dir:pathname, $root-dir, "")) 87 | let $_ := map:put($o, "type", "dir") 88 | let $_ := map:put($o, "collapsed", fn:true()) 89 | let $_ := map:put($o, "children", $children) 90 | return 91 | json:array-push($a, $o) 92 | }; 93 | 94 | let $server-id := xs:unsignedLong($serverId) 95 | let $config := admin:get-configuration() 96 | let $modules-db := admin:appserver-get-modules-database($config, $server-id) 97 | let $server-root := admin:appserver-get-root($config, $server-id) 98 | let $obj := 99 | if ($modules-db = 0) then 100 | let $o := json:object() 101 | let $_ := map:put($o, "name", "/") 102 | let $_ := map:put($o, "type", "dir") 103 | let $_ := map:put($o, "collapsed", fn:false()) 104 | let $children := json:array() 105 | let $dirs := xdmp:filesystem-directory($server-root) 106 | let $_ := local:get-system-dirs($server-root, $dirs, $children) 107 | let $_ := map:put($o, "children", $children) 108 | return 109 | $o 110 | else 111 | let $uris := 112 | xdmp:invoke-function(function() { 113 | for $x in cts:search(fn:doc(), cts:and-query(()), "unfiltered") 114 | let $uri := xdmp:node-uri($x) 115 | where fn:not(fn:ends-with($uri, "/")) 116 | order by $uri ascending 117 | return 118 | $uri 119 | }, 120 | map:new(( 121 | map:entry("isolation", "different-transaction"), 122 | map:entry("database", $modules-db), 123 | map:entry("transactionMode", "update-auto-commit") 124 | ))) 125 | let $o := json:object() 126 | let $_ := map:put($o, "name", "/") 127 | let $_ := map:put($o, "type", "dir") 128 | let $_ := map:put($o, "collapsed", fn:false()) 129 | let $children := local:build-dirs($uris, "/") 130 | let $_ := map:put($o, "children", $children) 131 | return 132 | $o 133 | return 134 | xdmp:to-json($obj) 135 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/auth/MarkLogicAuthenticationManager.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.auth; 2 | 3 | import com.marklogic.spring.http.RestClient; 4 | import com.marklogic.spring.http.RestConfig; 5 | import com.marklogic.spring.http.SimpleRestConfig; 6 | import org.apache.http.auth.AuthScope; 7 | import org.apache.http.auth.Credentials; 8 | import org.apache.http.auth.UsernamePasswordCredentials; 9 | import org.apache.http.client.CredentialsProvider; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.AuthenticationProvider; 13 | import org.springframework.security.authentication.BadCredentialsException; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.core.AuthenticationException; 16 | import org.springframework.web.client.HttpClientErrorException; 17 | 18 | import java.net.URI; 19 | 20 | /** 21 | * Implements Spring Security's AuthenticationManager interface so that it can authenticate users by making a simple 22 | * request to MarkLogic and checking for a 401. Also implements AuthenticationProvider so that it can be used with 23 | * Spring Security's ProviderManager. 24 | */ 25 | public class MarkLogicAuthenticationManager implements AuthenticationProvider, AuthenticationManager { 26 | 27 | private SimpleRestConfig restConfig; 28 | 29 | private String pathToAuthenticateAgainst = "/"; 30 | 31 | /** 32 | * A RestConfig instance is needed so a request can be made to MarkLogic to see if the user can successfully 33 | * authenticate. 34 | * 35 | * @param restConfig 36 | */ 37 | public MarkLogicAuthenticationManager(RestConfig restConfig) { 38 | this.restConfig = (SimpleRestConfig)restConfig; 39 | } 40 | 41 | @Override 42 | public boolean supports(Class authentication) { 43 | return ConnectionAuthenticationToken.class.isAssignableFrom(authentication); 44 | } 45 | 46 | @Override 47 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 48 | if (!(authentication instanceof ConnectionAuthenticationToken)) { 49 | throw new IllegalArgumentException( 50 | getClass().getName() + " only supports " + ConnectionAuthenticationToken.class.getName()); 51 | } 52 | 53 | ConnectionAuthenticationToken token = (ConnectionAuthenticationToken) authentication; 54 | String username = token.getPrincipal().toString(); 55 | String password = token.getCredentials().toString(); 56 | String hostname = token.getHostname().toString(); 57 | int port = Integer.parseInt(token.getPort().toString()); 58 | 59 | if (username == "" || password == "" || hostname == "" || port == 0) { 60 | throw new BadCredentialsException("Invalid credentials"); 61 | } 62 | /** 63 | * For now, building a new RestTemplate each time. This should in general be okay, because we're typically not 64 | * authenticating users over and over. 65 | */ 66 | restConfig.setHost(hostname); 67 | restConfig.setRestPort(port); 68 | RestClient client = new RestClient(restConfig, new SimpleCredentialsProvider(username, password)); 69 | URI uri = client.buildUri(pathToAuthenticateAgainst, null); 70 | try { 71 | client.getRestOperations().getForEntity(uri, String.class); 72 | } catch (HttpClientErrorException ex) { 73 | if (HttpStatus.NOT_FOUND.equals(ex.getStatusCode())) { 74 | // Authenticated, but the path wasn't found - that's okay, we just needed to verify authentication 75 | } else if (HttpStatus.UNAUTHORIZED.equals(ex.getStatusCode())) { 76 | throw new BadCredentialsException("Invalid credentials"); 77 | } else { 78 | throw ex; 79 | } 80 | } 81 | 82 | return new ConnectionAuthenticationToken(token.getPrincipal(), token.getCredentials(), 83 | token.getHostname(), token.getPort(), token.getAuthorities()); 84 | } 85 | 86 | public void setPathToAuthenticateAgainst(String pathToAuthenticateAgainst) { 87 | this.pathToAuthenticateAgainst = pathToAuthenticateAgainst; 88 | } 89 | } 90 | 91 | /** 92 | * Simple implementation that is good for one-time requests. 93 | */ 94 | class SimpleCredentialsProvider implements CredentialsProvider { 95 | 96 | private String username; 97 | private String password; 98 | 99 | public SimpleCredentialsProvider(String username, String password) { 100 | this.username = username; 101 | this.password = password; 102 | } 103 | 104 | @Override 105 | public void setCredentials(AuthScope authscope, Credentials credentials) { 106 | } 107 | 108 | @Override 109 | public Credentials getCredentials(AuthScope authscope) { 110 | return new UsernamePasswordCredentials(username, password); 111 | } 112 | 113 | @Override 114 | public void clear() { 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/Config.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger; 2 | 3 | import com.marklogic.debugger.auth.AuthSuccessHandler; 4 | import com.marklogic.debugger.auth.ConnectionAuthenticationFilter; 5 | import com.marklogic.debugger.auth.RestAuthenticationEntryPoint; 6 | import org.apache.http.client.CredentialsProvider; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | 15 | import com.marklogic.spring.http.RestConfig; 16 | import com.marklogic.spring.http.SimpleRestConfig; 17 | import com.marklogic.spring.http.proxy.HttpProxy; 18 | import com.marklogic.debugger.auth.MarkLogicAuthenticationManager; 19 | import com.marklogic.spring.security.context.SpringSecurityCredentialsProvider; 20 | import com.marklogic.spring.security.web.util.matcher.CorsRequestMatcher; 21 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; 22 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 23 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 24 | 25 | /** 26 | * Extends Spring Boot's default web security configuration class and hooks in MarkLogic-specific classes from 27 | * marklogic-spring-web. Feel free to customize as needed. 28 | */ 29 | @Configuration 30 | @EnableWebSecurity 31 | public class Config extends WebSecurityConfigurerAdapter { 32 | 33 | /** 34 | * @return a config class with ML connection properties 35 | */ 36 | @Bean 37 | public RestConfig restConfig() { 38 | return new SimpleRestConfig(); 39 | } 40 | 41 | @Bean 42 | public CredentialsProvider credentialsProvider() { 43 | return new SpringSecurityCredentialsProvider(); 44 | } 45 | 46 | @Autowired 47 | private RestAuthenticationEntryPoint restAuthenticationEntryPoint; 48 | 49 | @Autowired 50 | private AuthSuccessHandler authenticationSuccessHandler; 51 | 52 | // /** 53 | // * @return an HttpProxy that a Spring MVC controller can use for proxying requests to MarkLogic. By default, uses 54 | // * Spring Security for credentials - this relies on Spring Security not erasing the user's credentials so 55 | // * that the username/password can be passed to MarkLogic on every request for authentication. 56 | // */ 57 | // @Bean 58 | // public HttpProxy httpProxy() { 59 | // return new HttpProxy(restConfig(), credentialsProvider()); 60 | // } 61 | 62 | /** 63 | * We seem to need this defined as a bean; otherwise, aspects of the default Spring Boot security will still remain. 64 | * 65 | * @return 66 | */ 67 | @Bean 68 | public MarkLogicAuthenticationManager markLogicAuthenticationManager() { 69 | return new MarkLogicAuthenticationManager(restConfig()); 70 | } 71 | 72 | /** 73 | * Sets MarkLogicAuthenticationProvider as the authentication manager, which overrides the in-memory authentication 74 | * manager that Spring Boot uses by default. We also have to set eraseCredentials to false so that the password is 75 | * kept in the Authentication object, which allows HttpProxy to use it when authenticating against MarkLogic. 76 | */ 77 | @Override 78 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 79 | super.configure(auth); 80 | auth.parentAuthenticationManager(markLogicAuthenticationManager()); 81 | auth.eraseCredentials(false); 82 | } 83 | 84 | /** 85 | * Configures what requests require authentication and which ones are always permitted. Uses CorsRequestMatcher to 86 | * allow for certain requests - e.g. put/post/delete requests - to be proxied successfully back to MarkLogic. 87 | * 88 | * This uses a form login by default, as for many MarkLogic apps (particularly demos), it's convenient to be able to 89 | * easily logout and login as a different user to show off security features. Spring Security has a very plain form 90 | * login page - you can customize this, just google for examples. 91 | */ 92 | @Override 93 | protected void configure(HttpSecurity http) throws Exception { 94 | 95 | ConnectionAuthenticationFilter authFilter = new ConnectionAuthenticationFilter(); 96 | authFilter.setAuthenticationManager(markLogicAuthenticationManager()); 97 | authFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler); 98 | authFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()); 99 | http 100 | .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class) 101 | .csrf().disable() 102 | .exceptionHandling() 103 | .authenticationEntryPoint(restAuthenticationEntryPoint) 104 | .and() 105 | .authorizeRequests() 106 | .antMatchers(getAlwaysPermittedPatterns()).permitAll().anyRequest().authenticated(); 107 | } 108 | 109 | @Bean 110 | public AuthSuccessHandler mySuccessHandler(){ 111 | return new AuthSuccessHandler(); 112 | } 113 | 114 | @Bean 115 | public SimpleUrlAuthenticationFailureHandler myFailureHandler(){ 116 | return new SimpleUrlAuthenticationFailureHandler(); 117 | } 118 | 119 | /** 120 | * Defines a set of URLs that are always permitted - these are based on the presumed contents of the 121 | * src/main/resources/static directory. 122 | * 123 | * @return 124 | */ 125 | protected String[] getAlwaysPermittedPatterns() { 126 | return new String[] { 127 | "/bower_components/**", 128 | "/fonts/**", 129 | "/images/**", 130 | "/styles/**", 131 | "/*.js", 132 | "/*.ttf", 133 | "/*.woff", 134 | "/*.svg", 135 | "/*.woff2", 136 | "/*.eot", 137 | "/*.css", 138 | "/", 139 | "/index.html", 140 | "/api/server/status" 141 | }; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/ui/app/marklogic/marklogic.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Headers, Http, RequestOptionsArgs, Response } from '@angular/http'; 3 | import { Breakpoint } from './breakpoint'; 4 | 5 | import * as _ from 'lodash'; 6 | 7 | @Injectable() 8 | export class MarkLogicService { 9 | 10 | constructor(private http: Http) {} 11 | 12 | getServers() { 13 | return this.get('/api/servers'); 14 | } 15 | 16 | enableServer(serverId) { 17 | return this.http.get('/api/servers/' + serverId + '/enable'); 18 | } 19 | 20 | disableServer(serverId) { 21 | return this.http.get('/api/servers/' + serverId + '/disable'); 22 | } 23 | 24 | getFiles(serverId) { 25 | return this.get('/api/servers/' + serverId + '/files'); 26 | } 27 | 28 | getSystemFiles() { 29 | return this.get('/api/marklogic/files'); 30 | } 31 | 32 | getFile(serverId, uri) { 33 | let options: RequestOptionsArgs = { 34 | headers: new Headers({'Accept': 'text/plain'}) 35 | }; 36 | const url = '/api/servers/' + serverId + '/file?uri=' + uri; 37 | return this.http.get(url, options).map((resp: Response) => { 38 | return resp.text(); 39 | }); 40 | } 41 | 42 | getServerEnabled(serverId) { 43 | return this.get(`/api/servers/${serverId}`); 44 | } 45 | 46 | getRequests(serverId) { 47 | return this.get(`/api/servers/${serverId}/requests`); 48 | } 49 | 50 | getRequest(serverId, requestId) { 51 | return this.get(`/api/servers/${serverId}/requests/${requestId}`); 52 | } 53 | 54 | getStack(requestId) { 55 | return this.get(`/api/requests/${requestId}/stack`); 56 | } 57 | 58 | stepOver(requestId: any) { 59 | return this.http.get(`/api/requests/${requestId}/step-over`); 60 | } 61 | 62 | stepIn(requestId: any) { 63 | return this.http.get(`/api/requests/${requestId}/step-in`); 64 | } 65 | 66 | stepOut(requestId: any) { 67 | return this.http.get(`/api/requests/${requestId}/step-out`); 68 | } 69 | 70 | continue(requestId: any) { 71 | return this.http.get(`/api/requests/${requestId}/continue`); 72 | } 73 | 74 | pause(requestId: any) { 75 | return this.http.get(`/api/requests/${requestId}/pause`); 76 | } 77 | 78 | get(url: string) { 79 | return this.http.get(url).map((resp: Response) => { 80 | return resp.json(); 81 | }); 82 | } 83 | 84 | getTrace(id: string) { 85 | return this.http.get('/hub/traces/' + id); 86 | } 87 | 88 | getIds(query: string) { 89 | return this.http.get('/hub/traces/ids?q=' + query); 90 | } 91 | 92 | getAllBreakpoints(server: string): Map> { 93 | let map = new Map>(); 94 | let parsed = JSON.parse(localStorage.getItem(`breakpoints-${server}`)); 95 | if (parsed) { 96 | Object.keys(parsed).forEach((key) => { 97 | let breakpoints = new Array(); 98 | for (let bp of parsed[key]) { 99 | let breakpoint = new Breakpoint(bp.uri, bp.line, bp.enabled); 100 | breakpoints.push(breakpoint); 101 | } 102 | map.set(key, breakpoints); 103 | }); 104 | } 105 | return map; 106 | } 107 | 108 | getBreakpoints(server: string, uri: string): Array { 109 | let breakpoints = this.getAllBreakpoints(server); 110 | return breakpoints.get(uri) || new Array(); 111 | } 112 | 113 | enableBreakpoint(server: string, uri: string, line: number) { 114 | let breakpoints = this.getBreakpoints(server, uri); 115 | breakpoints.push(new Breakpoint(uri, line, true)); 116 | this.setBreakpoints(server, uri, breakpoints); 117 | } 118 | 119 | toggleBreakpoint(server: string, uri: string, line: number) { 120 | let breakpoints = this.getBreakpoints(server, uri); 121 | let breakpoint = _.find(breakpoints, (breakpoint: Breakpoint) => { 122 | return breakpoint.uri === uri && breakpoint.line === line; 123 | }); 124 | if (breakpoint) { 125 | breakpoint.enabled = !breakpoint.enabled; 126 | this.setBreakpoints(server, uri, breakpoints); 127 | } 128 | } 129 | 130 | disableBreakpoint(server: string, uri: string, line: number) { 131 | let breakpoints: Array = this.getBreakpoints(server, uri); 132 | _.remove(breakpoints, (bp) => { return bp.line === line; }); 133 | if (breakpoints.length > 0) { 134 | this.setBreakpoints(server, uri, breakpoints); 135 | } else { 136 | this.removeBreakpoints(server, uri); 137 | } 138 | } 139 | 140 | sendBreakpoints(requestId: any, breakpoints: Array) { 141 | let onOnly = _.filter(breakpoints, { enabled: true }); 142 | return this.http.post(`/api/requests/${requestId}/breakpoints`, onOnly); 143 | } 144 | 145 | evalExpression(requestId: any, expression: string) { 146 | return this.http.post(`/api/requests/${requestId}/eval`, expression).map((resp: Response) => { 147 | return resp.text(); 148 | }); 149 | } 150 | 151 | valueExpression(requestId: any, expression: string) { 152 | return this.http.post(`/api/requests/${requestId}/value`, expression).map((resp: Response) => { 153 | return resp.json(); 154 | }); 155 | } 156 | 157 | invokeModule(serverId: string, uri: string) { 158 | return this.http.post(`/api/servers/${serverId}/invoke?uri=${uri}`, null); 159 | } 160 | 161 | private setBreakpoints(server: string, uri, breakpoints: Array) { 162 | let allBreakpoints = this.getAllBreakpoints(server); 163 | allBreakpoints.set(uri, breakpoints); 164 | this.saveBreakpoints(server, allBreakpoints); 165 | } 166 | 167 | private removeBreakpoints(server: string, uri) { 168 | let allBreakpoints = this.getAllBreakpoints(server); 169 | allBreakpoints.delete(uri); 170 | this.saveBreakpoints(server, allBreakpoints); 171 | } 172 | 173 | 174 | private saveBreakpoints(server: string, breakpoints: Map>) { 175 | let serializeme = {}; 176 | breakpoints.forEach((value, key) => { 177 | serializeme[key] = value; 178 | }); 179 | localStorage.setItem(`breakpoints-${server}`, JSON.stringify(serializeme)); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/ui/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colors'; 2 | 3 | $hover-color: unquote("rgb(#{$palette-debugger-50})"); 4 | 5 | /deep/ .CodeMirror { 6 | height: 100%; 7 | width: 100%; 8 | 9 | .current-statement { 10 | background-color: $hover-color; 11 | } 12 | } 13 | 14 | .clickable { 15 | color: #666; 16 | width: 20px; 17 | height: 20px; 18 | margin: auto; 19 | display: inline-block; 20 | border-radius: 2px; 21 | cursor: default; 22 | 23 | /deep/ mdl-icon { 24 | font-size: 20px; 25 | } 26 | &:hover { 27 | background-color: rgba(0,0,0,0.05); 28 | color: black; 29 | } 30 | } 31 | 32 | .invoke-module { 33 | top: 6px; 34 | position: relative; 35 | } 36 | 37 | .fill-height { 38 | display: flex; 39 | flex: 1; 40 | flex-direction: column; 41 | } 42 | 43 | /deep/ .breakpoints { 44 | width: 1.5em; 45 | 46 | .section-body { 47 | padding: 10px; 48 | overflow-y: scroll; 49 | } 50 | } 51 | 52 | /deep/ .currentlines { 53 | width: 1.5em; 54 | } 55 | 56 | /deep/ .breakpoint-enabled { 57 | color: #822; 58 | font-size: 14pt; 59 | font-weight: bold; 60 | position: relative; 61 | } 62 | 63 | /deep/ .breakpoint-disabled { 64 | color: #666; 65 | font-size: 14pt; 66 | font-weight: bold; 67 | position: relative; 68 | } 69 | 70 | /deep/ .mdl-layout__content { 71 | height: 100%; 72 | } 73 | 74 | .breakpoints { 75 | ul { 76 | margin: 0; 77 | padding: 0px; 78 | list-style: none; 79 | cursor: default; 80 | 81 | .breakpoint-symbol { 82 | cursor: pointer; 83 | color: #666; 84 | 85 | &.enabled { 86 | color: #822; 87 | } 88 | } 89 | 90 | .fa-times { 91 | float: right; 92 | 93 | &:hover { 94 | color: red; 95 | } 96 | } 97 | 98 | li { 99 | padding: 5px; 100 | &:hover { 101 | background-color: $hover-color; 102 | } 103 | } 104 | } 105 | } 106 | 107 | .requests { 108 | .debug-status { 109 | display: inline; 110 | float: right; 111 | 112 | mdl-switch.mdl-switch.is-upgraded { 113 | padding-left: 28px; 114 | display: inline; 115 | margin-right: 20px; 116 | top: -5px; 117 | } 118 | } 119 | ul { 120 | margin: 0; 121 | padding: 0px; 122 | list-style: none; 123 | 124 | li { 125 | &:hover { 126 | background-color: $hover-color; 127 | } 128 | padding: 5px; 129 | 130 | cursor: pointer; 131 | 132 | .request-id { 133 | font-size: 60%; 134 | } 135 | 136 | .clickable { 137 | top: 6px; 138 | position: relative; 139 | } 140 | } 141 | } 142 | 143 | .section-body { 144 | padding: 10px; 145 | overflow-y: scroll; 146 | } 147 | } 148 | 149 | [gm-grid] { 150 | border-top: 1px solid #ccc; 151 | height: 100%; 152 | overflow: hidden; 153 | } 154 | 155 | .section-title { 156 | background-color: #f3f3f3; 157 | color: #333; 158 | padding: 10px; 159 | border-bottom: 1px solid #ccc; 160 | } 161 | 162 | /deep/ app-file-browser { 163 | flex: 1 auto; 164 | } 165 | 166 | .files { 167 | min-width: 250px; 168 | 169 | .section-body { 170 | display: flex; 171 | flex: 1; 172 | flex-direction: column; 173 | overflow-y: scroll; 174 | } 175 | } 176 | 177 | 178 | [gm-divider].debug-divider { 179 | display: block; 180 | height: initial; 181 | border-top: 1px solid #ccc; 182 | border-bottom: 1px solid #ccc; 183 | background-color: #f3f3f3; 184 | color: rgb(51,51,51); 185 | } 186 | 187 | .debug-area { 188 | min-height: 150px; 189 | 190 | .debugger { 191 | position: relative; 192 | 193 | .section-body { 194 | display: flex; 195 | flex: 1; 196 | flex-direction: column; 197 | overflow-y: scroll; 198 | } 199 | 200 | .disabled { 201 | position: absolute; 202 | top: 0px; 203 | bottom: 0px; 204 | left: 0px; 205 | right: 0px; 206 | background: rgba(255,255,255, 0.90); 207 | z-index: 10000; 208 | 209 | span { 210 | display: inline-block; 211 | width: 100%; 212 | font-weight: bold; 213 | margin: auto; 214 | text-align: center; 215 | position: absolute; 216 | top: 50%; 217 | left: 50%; 218 | transform: translate(-50%, -50%); 219 | } 220 | } 221 | } 222 | 223 | /deep/ mdl-icon { 224 | font-size: 16px; 225 | } 226 | 227 | .debugger { 228 | .section-title { 229 | padding: 5px; 230 | line-height: 8px; 231 | 232 | span { 233 | cursor: default; 234 | padding: 0px 2px; 235 | 236 | } 237 | } 238 | } 239 | } 240 | 241 | ul.frames { 242 | list-style: none; 243 | padding: 0px; 244 | margin: 0px; 245 | 246 | li { 247 | padding: 5px; 248 | cursor: default; 249 | &:hover { 250 | background-color: $hover-color; 251 | } 252 | 253 | .fa-long-arrow-right { 254 | visibility: hidden; 255 | &.visible { 256 | visibility: visible; 257 | } 258 | } 259 | } 260 | } 261 | 262 | .mdl-layout__header { 263 | min-height: 48px; 264 | } 265 | 266 | .mdl-layout__header-row { 267 | height: 48px; 268 | padding: 0 40px 0 10px; 269 | 270 | .mdl-switch { 271 | width: initial; 272 | } 273 | 274 | .selected-server { 275 | margin-right: 20px; 276 | } 277 | } 278 | 279 | .flex-70 { 280 | .section-body { 281 | display: flex; 282 | flex: 1; 283 | flex-direction: column; 284 | overflow-y: scroll; 285 | } 286 | 287 | app-codemirror { 288 | display: flex; 289 | flex: 1; 290 | } 291 | } 292 | 293 | .prompt { 294 | color: blue; 295 | margin-left: 5px; 296 | } 297 | 298 | .no-request { 299 | padding: 20px; 300 | color: #666; 301 | } 302 | 303 | .console-input { 304 | border: none; 305 | background: transparent; 306 | width: 60%; 307 | 308 | &:focus { 309 | outline: 0; 310 | } 311 | 312 | .prompt { 313 | color: #333; 314 | } 315 | } 316 | 317 | .console-output { 318 | color: #666; 319 | 320 | .prompt { 321 | color: #666; 322 | } 323 | 324 | .error { 325 | background-color: rgba(255, 0, 0, 0.5); 326 | padding: 5px; 327 | border-radius: 5px; 328 | color: black; 329 | width: 100%; 330 | display: inline-block; 331 | 332 | a { 333 | cursor: pointer; 334 | } 335 | 336 | pre { 337 | width: 100%; 338 | white-space: pre-wrap; 339 | } 340 | } 341 | } 342 | 343 | .console-buttons { 344 | float: right; 345 | } 346 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to MarkLogic Debugger 2 | 3 | MarkLogic Debuggers welcomes new contributors. This document will guide you 4 | through the process. 5 | 6 | - [Issues and Bugs](#found-an-issue) 7 | - [Feature Requests](#want-a-feature) 8 | - [Building from Source](#building-the-framework-from-source) 9 | - [Submission Guidelines](#submission-guidelines) 10 | 11 | ## Found an Issue? 12 | If you find a bug in the source code or a mistake in the documentation, you can help us by submitting an issue to our [GitHub Issue Tracker][issue tracker]. Even better you can submit a Pull Request 13 | with a fix for the issue you filed. 14 | 15 | ## Want a Feature? 16 | You can request a new feature by submitting an issue to our [GitHub Issue Tracker][issue tracker]. If you 17 | would like to implement a new feature then first create a new issue and discuss it with one of our 18 | project maintainers. 19 | 20 | ## Building the Debugger from Source 21 | Looking to build the code from source? Look no further. 22 | 23 | #### Prerequisites 24 | You need these to get started 25 | 26 | - Java 8 JDK 27 | - Gradle 3.1 or newer 28 | - Node JS 6.5 or newer 29 | - Typings `npm -g install typings` 30 | - A decent IDE. IntelliJ is nice. 31 | 32 | #### Building from the command line 33 | To build the war file simply run this command: 34 | 35 | ```bash 36 | cd /path/to/marklogic-debugger/ 37 | gradle build 38 | ``` 39 | 40 | #### Running the Debugger UI from source 41 | Make sure you have the prerequisites installed. 42 | 43 | You will need to open two terminal windows. 44 | 45 | **Terminal window 1** - This runs the webapp. 46 | ```bash 47 | cd /path/to/marklogic-debugger 48 | gradle bootrun 49 | ``` 50 | 51 | **Terminal window 2** - This runs the UI 52 | ``` 53 | cd /path/to/marklogic-debugger 54 | npm install 55 | npm start 56 | ``` 57 | 58 | Now open your browser to [http://localhost:4200](http://localhost:4200) to use the debug version of the UI. 59 | 60 | ## Submission Guidelines 61 | 62 | ### Submitting an Issue 63 | Before you submit your issue search the archive, maybe your question was already answered. 64 | 65 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 66 | Help us to maximize the effort we can spend fixing issues and adding new 67 | features, by not reporting duplicate issues. Please fill out the issue template so that your issue can be dealt with quickly. 68 | 69 | ### Submitting a Pull Request 70 | 71 | #### Fork marklogic-debugger 72 | 73 | Fork the project [on GitHub](https://github.com/paxtonhare/marklogic-debugger/fork) and clone 74 | your copy. 75 | 76 | ```sh 77 | $ git clone git@github.com:username/marklogic-debugger.git 78 | $ cd marklogic-debugger 79 | $ git remote add upstream git://github.com/paxtonhare/marklogic-debugger.git 80 | ``` 81 | 82 | We ask that you open an issue in the [issue tracker][] and get agreement from 83 | at least one of the project maintainers before you start coding. 84 | 85 | Nothing is more frustrating than seeing your hard work go to waste because 86 | your vision does not align with that of a project maintainer. 87 | 88 | #### Create a branch for your changes 89 | 90 | Okay, so you have decided to fix something. Create a feature branch 91 | and start hacking. **Note** that we use git flow and thus our most recent changes live on the develop branch. 92 | 93 | ```sh 94 | $ git checkout -b my-feature-branch -t origin/develop 95 | ``` 96 | 97 | #### Formatting code 98 | 99 | We use [.editorconfig][] to configure our editors for proper code formatting. If you don't 100 | use a tool that supports editorconfig be sure to configure your editor to use the settings 101 | equivalent to our .editorconfig file. 102 | 103 | #### Commit your changes 104 | 105 | Make sure git knows your name and email address: 106 | 107 | ```sh 108 | $ git config --global user.name "J. Random User" 109 | $ git config --global user.email "j.random.user@example.com" 110 | ``` 111 | 112 | Writing good commit logs is important. A commit log should describe what 113 | changed and why. Follow these guidelines when writing one: 114 | 115 | 1. The first line should be 50 characters or less and contain a short 116 | description of the change including the Issue number prefixed by a hash (#). 117 | 2. Keep the second line blank. 118 | 3. Wrap all other lines at 72 columns. 119 | 120 | A good commit log looks like this: 121 | 122 | ``` 123 | Fixing Issue #123: make the whatchamajigger work in MarkLogic 8 124 | 125 | Body of commit message is a few lines of text, explaining things 126 | in more detail, possibly giving some background about the issue 127 | being fixed, etc etc. 128 | 129 | The body of the commit message can be several paragraphs, and 130 | please do proper word-wrap and keep columns shorter than about 131 | 72 characters or so. That way `git log` will show things 132 | nicely even when it is indented. 133 | ``` 134 | 135 | The header line should be meaningful; it is what other people see when they 136 | run `git shortlog` or `git log --oneline`. 137 | 138 | #### Rebase your repo 139 | 140 | Use `git rebase` (not `git merge`) to sync your work from time to time. 141 | 142 | ```sh 143 | $ git fetch upstream 144 | $ git rebase upstream/develop 145 | ``` 146 | 147 | 148 | #### Test your code 149 | 150 | Make sure the JUnit tests pass. 151 | 152 | ```sh 153 | $ gradle test 154 | ``` 155 | 156 | Make sure that all tests pass. Please, do not submit patches that fail. 157 | 158 | #### Push your changes 159 | 160 | ```sh 161 | $ git push origin my-feature-branch 162 | ``` 163 | 164 | #### Agree to the contributor License 165 | 166 | Before we can merge your changes, you need to sign a [Contributor License Agreement](http://developer.marklogic.com/products/cla). You only need to do this once. 167 | 168 | #### Submit the pull request 169 | 170 | Go to https://github.com/username/marklogic-debugger and select your feature branch. Click 171 | the 'Pull Request' button and fill out the form. 172 | 173 | Pull requests are usually reviewed within a few days. If you get comments 174 | that need to be to addressed, apply your changes in a separate commit and push that to your 175 | feature branch. Post a comment in the pull request afterwards; GitHub does 176 | not send out notifications when you add commits to existing pull requests. 177 | 178 | That's it! Thank you for your contribution! 179 | 180 | 181 | #### After your pull request is merged 182 | 183 | After your pull request is merged, you can safely delete your branch and pull the changes 184 | from the main (upstream) repository: 185 | 186 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: 187 | 188 | ```shell 189 | git push origin --delete my-feature-branch 190 | ``` 191 | 192 | * Check out the develop branch: 193 | 194 | ```shell 195 | git checkout develop -f 196 | ``` 197 | 198 | * Delete the local branch: 199 | 200 | ```shell 201 | git branch -D my-feature-branch 202 | ``` 203 | 204 | * Update your develop with the latest upstream version: 205 | 206 | ```shell 207 | git pull --ff upstream develop 208 | ``` 209 | 210 | [issue tracker]: https://github.com/paxtonhare/marklogic-debugger/issues 211 | [.editorconfig]: http://editorconfig.org/ 212 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/auth/ConnectionAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.auth; 2 | 3 | import com.marklogic.debugger.LoginInfo; 4 | import org.springframework.security.authentication.AuthenticationServiceException; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.AuthenticationException; 7 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 8 | import org.springframework.util.Assert; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 13 | 14 | /** 15 | * Processes an authentication form submission. Called 16 | * {@code AuthenticationProcessingFilter} prior to Spring Security 3.0. 17 | *

18 | * Login forms must present two parameters to this filter: a username and password. The 19 | * default parameter names to use are contained in the static fields 20 | * {@link #SPRING_SECURITY_FORM_USERNAME_KEY} and 21 | * {@link #SPRING_SECURITY_FORM_PASSWORD_KEY}. The parameter names can also be changed by 22 | * setting the {@code usernameParameter} and {@code passwordParameter} properties. 23 | *

24 | * This filter by default responds to the URL {@code /login}. 25 | * 26 | * @author Ben Alex 27 | * @author Colin Sampaleanu 28 | * @author Luke Taylor 29 | * @since 3.0 30 | */ 31 | 32 | public class ConnectionAuthenticationFilter extends 33 | AbstractAuthenticationProcessingFilter { 34 | // ~ Static fields/initializers 35 | // ===================================================================================== 36 | 37 | public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; 38 | public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; 39 | public static final String SPRING_SECURITY_FORM_HOST_KEY = "hostname"; 40 | public static final String SPRING_SECURITY_FORM_PORT_KEY = "port"; 41 | 42 | private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; 43 | private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; 44 | private String hostnameParameter = SPRING_SECURITY_FORM_HOST_KEY; 45 | private String hostportParameter = SPRING_SECURITY_FORM_PORT_KEY; 46 | private boolean postOnly = true; 47 | 48 | // ~ Constructors 49 | // =================================================================================================== 50 | 51 | public ConnectionAuthenticationFilter() { 52 | super(new AntPathRequestMatcher("/api/user/login", "POST")); 53 | } 54 | 55 | // ~ Methods 56 | // ======================================================================================================== 57 | 58 | public Authentication attemptAuthentication(HttpServletRequest request, 59 | HttpServletResponse response) throws AuthenticationException { 60 | if (postOnly && !request.getMethod().equals("POST")) { 61 | throw new AuthenticationServiceException( 62 | "Authentication method not supported: " + request.getMethod()); 63 | } 64 | 65 | String username = obtainUsername(request); 66 | String password = obtainPassword(request); 67 | String hostname = obtainHostname(request); 68 | Integer port = obtainPort(request); 69 | LoginInfo loginInfo = new LoginInfo(); 70 | loginInfo.hostname = hostname; 71 | loginInfo.port = port; 72 | loginInfo.username = username; 73 | loginInfo.password = password; 74 | request.getSession().setAttribute("loginInfo", loginInfo); 75 | 76 | if (username == null) { 77 | username = ""; 78 | } 79 | 80 | if (password == null) { 81 | password = ""; 82 | } 83 | 84 | if (hostname == null) { 85 | hostname = ""; 86 | } 87 | 88 | if (port == null) { 89 | port = 8000; 90 | } 91 | 92 | username = username.trim(); 93 | 94 | ConnectionAuthenticationToken authRequest = new ConnectionAuthenticationToken( 95 | username, password, hostname, port); 96 | 97 | // Allow subclasses to set the "details" property 98 | setDetails(request, authRequest); 99 | 100 | return this.getAuthenticationManager().authenticate(authRequest); 101 | } 102 | 103 | /** 104 | * Enables subclasses to override the composition of the password, such as by 105 | * including additional values and a separator. 106 | *

107 | * This might be used for example if a postcode/zipcode was required in addition to 108 | * the password. A delimiter such as a pipe (|) should be used to separate the 109 | * password and extended value(s). The AuthenticationDao will need to 110 | * generate the expected password in a corresponding manner. 111 | *

112 | * 113 | * @param request so that request attributes can be retrieved 114 | * 115 | * @return the password that will be presented in the Authentication 116 | * request token to the AuthenticationManager 117 | */ 118 | protected String obtainPassword(HttpServletRequest request) { 119 | return request.getParameter(passwordParameter); 120 | } 121 | 122 | /** 123 | * Enables subclasses to override the composition of the username, such as by 124 | * including additional values and a separator. 125 | * 126 | * @param request so that request attributes can be retrieved 127 | * 128 | * @return the username that will be presented in the Authentication 129 | * request token to the AuthenticationManager 130 | */ 131 | protected String obtainUsername(HttpServletRequest request) { 132 | return request.getParameter(usernameParameter); 133 | } 134 | 135 | protected String obtainHostname(HttpServletRequest request) { 136 | return request.getParameter(hostnameParameter); 137 | } 138 | 139 | protected Integer obtainPort(HttpServletRequest request) { 140 | return Integer.parseInt(request.getParameter(hostportParameter)); 141 | } 142 | 143 | /** 144 | * Provided so that subclasses may configure what is put into the authentication 145 | * request's details property. 146 | * 147 | * @param request that an authentication request is being created for 148 | * @param authRequest the authentication request object that should have its details 149 | * set 150 | */ 151 | protected void setDetails(HttpServletRequest request, 152 | ConnectionAuthenticationToken authRequest) { 153 | authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); 154 | } 155 | 156 | /** 157 | * Sets the parameter name which will be used to obtain the username from the login 158 | * request. 159 | * 160 | * @param usernameParameter the parameter name. Defaults to "username". 161 | */ 162 | public void setUsernameParameter(String usernameParameter) { 163 | Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); 164 | this.usernameParameter = usernameParameter; 165 | } 166 | 167 | /** 168 | * Sets the parameter name which will be used to obtain the password from the login 169 | * request.. 170 | * 171 | * @param passwordParameter the parameter name. Defaults to "password". 172 | */ 173 | public void setPasswordParameter(String passwordParameter) { 174 | Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); 175 | this.passwordParameter = passwordParameter; 176 | } 177 | 178 | public void setHostnameParameter(String hostnameParameter) { 179 | Assert.hasText(hostnameParameter, "Hostname parameter must not be empty or null"); 180 | this.hostnameParameter = hostnameParameter; 181 | } 182 | 183 | /** 184 | * Defines whether only HTTP POST requests will be allowed by this filter. If set to 185 | * true, and an authentication request is received which is not a POST request, an 186 | * exception will be raised immediately and authentication will not be attempted. The 187 | * unsuccessfulAuthentication() method will be called as if handling a failed 188 | * authentication. 189 | *

190 | * Defaults to true but may be overridden by subclasses. 191 | */ 192 | public void setPostOnly(boolean postOnly) { 193 | this.postOnly = postOnly; 194 | } 195 | 196 | public final String getUsernameParameter() { 197 | return usernameParameter; 198 | } 199 | 200 | public final String getPasswordParameter() { 201 | return passwordParameter; 202 | } 203 | 204 | public final String getHostnameParameter() { 205 | return hostnameParameter; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/ui/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://{{hostname}}:{{port}} ● 6 | {{selectedServer.name}} 7 | 8 | 9 | 10 | 11 | Show Welcome Message 12 | Report an Issue 13 | Sign Out 14 | 15 | 16 | 17 | 18 |

19 |
20 |
21 | 32 |
33 | 34 |
35 |
36 |
37 | File View: {{currentUri}} 38 | play_arrow 39 |
40 |
41 | 48 |
49 |
50 |
51 |
52 |
53 | Debug Area 54 |
55 |
56 |
57 |
58 | DEBUG A REQUEST TO ENABLE 59 |
60 |
61 | 62 | play_arrow 63 | redo 64 | vertical_align_bottom 65 | vertical_align_top 66 |
67 |
68 | 69 |
    70 |
  • 71 | 72 | {{frame.uri}}:{{frame.line}} 73 |
  • 74 |
75 |
    76 |
  •  
  • 77 |
78 |
79 | 80 |
    81 | 84 | 87 | 90 |
91 |

 

92 |
93 | 94 |
95 | 96 |
97 |
98 | Server Error (show) 99 | 100 | {{output.type === 'i' ? '>' : '<-' }} {{output.txt}} 101 | 102 |
103 | > 104 |
105 |
106 |
107 |
108 |
109 |
BreakPoints
110 |
111 |

No breakpoints

112 |
113 |
    114 |
  • 115 | 116 | {{breakpoint.uri}}:{{breakpoint.line + 1}} 117 | 118 |
  • 119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | Requests 127 |
128 | 129 | Auto-pause requests is {{selectedServer.connected ? 'ON' : 'OFF'}} 130 |
131 |
132 |
133 |

No Requests stopped

134 |
    135 |
  • 136 | 137 | {{getRequestName(request)}} ({{request.requestId}}) 138 | play_arrow 139 | pause 140 |
  • 141 |
142 |
143 |
144 |
145 |
146 | 147 | 148 | -------------------------------------------------------------------------------- /src/main/ui/app/codemirror/codemirror.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | OnInit, 5 | OnChanges, 6 | Output, 7 | ViewChild, 8 | EventEmitter, 9 | forwardRef 10 | } from '@angular/core'; 11 | import { NG_VALUE_ACCESSOR } from '@angular/forms'; 12 | import { Breakpoint } from '../marklogic'; 13 | 14 | import * as CodeMirror from 'codemirror'; 15 | require('codemirror/mode/xquery/xquery'); 16 | require('codemirror/mode/javascript/javascript'); 17 | require('codemirror/addon/selection/mark-selection'); 18 | 19 | /** 20 | * CodeMirror component 21 | * Usage : 22 | * 23 | */ 24 | @Component({ 25 | selector: 'app-codemirror', 26 | providers: [ 27 | { 28 | provide: NG_VALUE_ACCESSOR, 29 | useExisting: forwardRef(() => CodemirrorComponent), 30 | multi: true 31 | } 32 | ], 33 | template: ``, 34 | }) 35 | export class CodemirrorComponent implements OnInit, OnChanges { 36 | 37 | @Input() config; 38 | 39 | @Input() breakpoints: Array; 40 | 41 | @Output() change = new EventEmitter(); 42 | @ViewChild('host') host; 43 | 44 | private _value = ''; 45 | private _line: number = null; 46 | private _showLine: number = null; 47 | private _expression: string; 48 | 49 | private currentStatement: CodeMirror.TextMarker; 50 | 51 | @Output() instance: CodeMirror.EditorFromTextArea = null; 52 | 53 | /** 54 | * Constructor 55 | */ 56 | constructor() {} 57 | 58 | get value(): any { return this._value; }; 59 | 60 | get line(): number { return this._line + 1; }; 61 | @Input() set line(l: number) { 62 | if (l === null) { 63 | this._line = null; 64 | return; 65 | } 66 | this._line = l - 1; 67 | if (this.instance) { 68 | this.instance.clearGutter('currentlines'); 69 | this.instance.setGutterMarker(this._line, 'currentlines', this.makeLineMarker()); 70 | this.jumpToLine(this._line); 71 | this.highlightExpression(); 72 | } 73 | } 74 | 75 | get showLine(): number { return this._showLine + 1; }; 76 | @Input() set showLine(l: number) { 77 | if (l === null) { 78 | this._showLine = null; 79 | return; 80 | } 81 | 82 | this._showLine = l - 1; 83 | if (this.instance) { 84 | this.jumpToLine(this._showLine); 85 | } 86 | } 87 | 88 | get expression(): string { return this._expression; } 89 | @Input() set expression(e: string) { 90 | this._expression = e; 91 | this.jumpToLine(this._line); 92 | this.highlightExpression(); 93 | } 94 | 95 | ngOnInit() { 96 | this.config = this.config || {}; 97 | this.codemirrorInit(this.config); 98 | } 99 | 100 | ngOnChanges(changes: any) { 101 | if (changes.breakpoints && changes.breakpoints.currentValue) { 102 | this.updateBreakpoints(); 103 | } 104 | } 105 | 106 | /** 107 | * Initialize codemirror 108 | */ 109 | codemirrorInit(config) { 110 | this.instance = CodeMirror.fromTextArea(this.host.nativeElement, config); 111 | this.instance.on('change', () => { 112 | this.updateValue(this.instance.getValue()); 113 | if (this._line !== null) { 114 | this.line = this._line + 1; 115 | } 116 | }); 117 | setTimeout(() => { 118 | this.instance.refresh(); 119 | if (this.line !== null) { 120 | if (this._showLine) { 121 | this.jumpToLine(this._showLine); 122 | } else { 123 | this.jumpToLine(this._line); 124 | } 125 | this.highlightExpression(); 126 | } 127 | }, 250); 128 | 129 | Object.keys(config.events).map((key) => { 130 | this.instance.on(key, config.events[key]); 131 | }); 132 | 133 | } 134 | 135 | makeBreakpoint(enabled: boolean) { 136 | const marker = document.createElement('div'); 137 | marker.className = 'breakpoint' + (enabled ? '-enabled' : '-disabled'); 138 | marker.innerHTML = '◉'; 139 | return marker; 140 | } 141 | 142 | makeLineMarker() { 143 | const marker = document.createElement('div'); 144 | marker.className = 'current-line'; 145 | marker.innerHTML = '➡'; 146 | return marker; 147 | } 148 | 149 | /** 150 | * Value update process 151 | */ 152 | updateValue(value) { 153 | this.updateBreakpoints(); 154 | 155 | this.onChange(value); 156 | this.onTouched(); 157 | this.change.emit(value); 158 | } 159 | 160 | updateBreakpoints() { 161 | if (this.instance) { 162 | this.instance.clearGutter('breakpoints'); 163 | if (this.breakpoints) { 164 | for (let breakpoint of this.breakpoints) { 165 | this.instance.setGutterMarker(breakpoint.line, 'breakpoints', this.makeBreakpoint(breakpoint.enabled)); 166 | } 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * Implements ControlValueAccessor 173 | */ 174 | writeValue(value) { 175 | this._value = value || ''; 176 | if (this.instance) { 177 | this.instance.setValue(this._value); 178 | this.jumpToLine(this._line); 179 | this.onChange(value); 180 | if (this.instance) { 181 | this.highlightExpression(); 182 | } 183 | } 184 | } 185 | 186 | 187 | jumpToLine(line: number) { 188 | if (this.instance && line && this._value !== '') { 189 | this.instance.scrollIntoView({line: line, ch: 0}, 40); 190 | } 191 | } 192 | 193 | highlightExpression() { 194 | if (this.currentStatement) { 195 | this.currentStatement.clear(); 196 | this.currentStatement = null; 197 | } 198 | 199 | if (this._value === '' || !this._expression || (this._line === null)) { 200 | return; 201 | } 202 | const lines = this._value.split(/[\r\n]/); 203 | 204 | let startLine = -1; 205 | let startChar = -1; 206 | let endLine = -1; 207 | let endChar = -1; 208 | let pos = 0; 209 | let i = this._line; 210 | let j = 0; 211 | 212 | let state = 'scanning'; 213 | 214 | let reset = () => { 215 | startLine = -1; 216 | startChar = -1; 217 | endLine = -1; 218 | endChar = -1; 219 | pos = 0; 220 | state = 'scanning'; 221 | } 222 | 223 | let peak = () => { 224 | if (i < lines.length && j < lines[i].length) { 225 | return lines[i][j]; 226 | } 227 | return null; 228 | }; 229 | 230 | let eat = () => { 231 | do { 232 | j++; 233 | while (i < lines.length && j > (lines[i].length - 1)) { 234 | j = 0; 235 | i++; 236 | } 237 | } while (i < lines.length && lines[i][j] === ' '); 238 | }; 239 | 240 | let eatExpr = () => { 241 | do { 242 | pos++; 243 | } while(this._expression[pos] === ' ') 244 | } 245 | 246 | while (state !== 'done' && peak() !== null) { 247 | switch(state) { 248 | case 'scanning': 249 | if (peak() === this._expression[pos]) { 250 | state = 'start'; 251 | startLine = i; 252 | startChar = j; 253 | eatExpr(); 254 | eat(); 255 | } else if (this._expression.substring(pos).startsWith('fn:') && peak() === this._expression[pos + 'fn:'.length]) { 256 | state = 'start'; 257 | startLine = i; 258 | startChar = j; 259 | pos += 'fn:'.length; 260 | } else if (this._expression.substring(pos).startsWith('fn:unordered(') && peak() === this._expression[pos + 'fn:unordered('.length]) { 261 | state = 'start'; 262 | startLine = i; 263 | startChar = j; 264 | pos += 'fn:unordered('.length; 265 | } else if (this._expression.substring(pos).startsWith('descendant::') && peak() === this._expression[pos + 'descendant::'.length]) { 266 | state = 'start'; 267 | startLine = i; 268 | startChar = j; 269 | pos += 'descendant::'.length; 270 | } 271 | else { 272 | eat(); 273 | } 274 | break; 275 | case 'comment': 276 | if (peak() === ':') { 277 | eat(); 278 | if (peak() === ')') { 279 | state = 'start'; 280 | eat(); 281 | } 282 | continue; 283 | } 284 | eat(); 285 | break; 286 | case 'start': 287 | if (peak() === this._expression[pos] || 288 | ( 289 | (peak() === '"' || peak() === '\'') && 290 | (this._expression[pos] === '"' || this._expression[pos] === '\'') 291 | )) { 292 | eatExpr(); 293 | if (pos > (this._expression.length - 1)) { 294 | state = 'done'; 295 | endLine = i; 296 | endChar = j; 297 | } 298 | } else if (peak() === '(' || peak() === ')') { 299 | eat(); 300 | if (peak() === ':') { 301 | state = 'comment'; 302 | eat(); 303 | } 304 | continue; 305 | } else if (peak() === '/' && this._expression[pos] === 'd') { 306 | if (this._expression.substring(pos).startsWith('descendant::')) { 307 | pos += 'descendant::'.length; 308 | } else { 309 | reset(); 310 | } 311 | } else if (this._expression[pos] === 'f') { 312 | if (this._expression.substring(pos).startsWith('fn:unordered(')) { 313 | pos += 'fn:unordered('.length; 314 | continue; 315 | } else if (this._expression.substring(pos).startsWith('fn:')) { 316 | pos += 'fn:'.length; 317 | continue; 318 | } else { 319 | reset(); 320 | } 321 | } else if (this._expression[pos] === ')') { 322 | eatExpr(); 323 | continue; 324 | } else if (this._expression.substring(pos).startsWith('..."')) { 325 | if (peak() === '"' || peak() === '\'') { 326 | pos += '..."'.length; 327 | } 328 | } else { 329 | reset(); 330 | continue; 331 | } 332 | eat(); 333 | break; 334 | } 335 | } 336 | 337 | if (state === 'done') { 338 | this.currentStatement = this.instance.getDoc().markText({line: startLine, ch: startChar}, {line: endLine, ch: endChar + 1}, {className: 'current-statement'}); 339 | } 340 | } 341 | 342 | onChange(_) {} 343 | onTouched() {} 344 | registerOnChange(fn) { this.onChange = fn; } 345 | registerOnTouched(fn) { this.onTouched = fn; } 346 | } 347 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2017 Paxton Hare. 2 | This project and its code and functionality is not representative of MarkLogic Server and is not supported by MarkLogic. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 9 | 10 | Apache License 11 | Version 2.0, January 2004 12 | http://www.apache.org/licenses/ 13 | 14 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 15 | 16 | 1. Definitions. 17 | 18 | "License" shall mean the terms and conditions for use, reproduction, 19 | and distribution as defined by Sections 1 through 9 of this document. 20 | 21 | "Licensor" shall mean the copyright owner or entity authorized by 22 | the copyright owner that is granting the License. 23 | 24 | "Legal Entity" shall mean the union of the acting entity and all 25 | other entities that control, are controlled by, or are under common 26 | control with that entity. For the purposes of this definition, 27 | "control" means (i) the power, direct or indirect, to cause the 28 | direction or management of such entity, whether by contract or 29 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 30 | outstanding shares, or (iii) beneficial ownership of such entity. 31 | 32 | "You" (or "Your") shall mean an individual or Legal Entity 33 | exercising permissions granted by this License. 34 | 35 | "Source" form shall mean the preferred form for making modifications, 36 | including but not limited to software source code, documentation 37 | source, and configuration files. 38 | 39 | "Object" form shall mean any form resulting from mechanical 40 | transformation or translation of a Source form, including but 41 | not limited to compiled object code, generated documentation, 42 | and conversions to other media types. 43 | 44 | "Work" shall mean the work of authorship, whether in Source or 45 | Object form, made available under the License, as indicated by a 46 | copyright notice that is included in or attached to the work 47 | (an example is provided in the Appendix below). 48 | 49 | "Derivative Works" shall mean any work, whether in Source or Object 50 | form, that is based on (or derived from) the Work and for which the 51 | editorial revisions, annotations, elaborations, or other modifications 52 | represent, as a whole, an original work of authorship. For the purposes 53 | of this License, Derivative Works shall not include works that remain 54 | separable from, or merely link (or bind by name) to the interfaces of, 55 | the Work and Derivative Works thereof. 56 | 57 | "Contribution" shall mean any work of authorship, including 58 | the original version of the Work and any modifications or additions 59 | to that Work or Derivative Works thereof, that is intentionally 60 | submitted to Licensor for inclusion in the Work by the copyright owner 61 | or by an individual or Legal Entity authorized to submit on behalf of 62 | the copyright owner. For the purposes of this definition, "submitted" 63 | means any form of electronic, verbal, or written communication sent 64 | to the Licensor or its representatives, including but not limited to 65 | communication on electronic mailing lists, source code control systems, 66 | and issue tracking systems that are managed by, or on behalf of, the 67 | Licensor for the purpose of discussing and improving the Work, but 68 | excluding communication that is conspicuously marked or otherwise 69 | designated in writing by the copyright owner as "Not a Contribution." 70 | 71 | "Contributor" shall mean Licensor and any individual or Legal Entity 72 | on behalf of whom a Contribution has been received by Licensor and 73 | subsequently incorporated within the Work. 74 | 75 | 2. Grant of Copyright License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | copyright license to reproduce, prepare Derivative Works of, 79 | publicly display, publicly perform, sublicense, and distribute the 80 | Work and such Derivative Works in Source or Object form. 81 | 82 | 3. Grant of Patent License. Subject to the terms and conditions of 83 | this License, each Contributor hereby grants to You a perpetual, 84 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 85 | (except as stated in this section) patent license to make, have made, 86 | use, offer to sell, sell, import, and otherwise transfer the Work, 87 | where such license applies only to those patent claims licensable 88 | by such Contributor that are necessarily infringed by their 89 | Contribution(s) alone or by combination of their Contribution(s) 90 | with the Work to which such Contribution(s) was submitted. If You 91 | institute patent litigation against any entity (including a 92 | cross-claim or counterclaim in a lawsuit) alleging that the Work 93 | or a Contribution incorporated within the Work constitutes direct 94 | or contributory patent infringement, then any patent licenses 95 | granted to You under this License for that Work shall terminate 96 | as of the date such litigation is filed. 97 | 98 | 4. Redistribution. You may reproduce and distribute copies of the 99 | Work or Derivative Works thereof in any medium, with or without 100 | modifications, and in Source or Object form, provided that You 101 | meet the following conditions: 102 | 103 | (a) You must give any other recipients of the Work or 104 | Derivative Works a copy of this License; and 105 | 106 | (b) You must cause any modified files to carry prominent notices 107 | stating that You changed the files; and 108 | 109 | (c) You must retain, in the Source form of any Derivative Works 110 | that You distribute, all copyright, patent, trademark, and 111 | attribution notices from the Source form of the Work, 112 | excluding those notices that do not pertain to any part of 113 | the Derivative Works; and 114 | 115 | (d) If the Work includes a "NOTICE" text file as part of its 116 | distribution, then any Derivative Works that You distribute must 117 | include a readable copy of the attribution notices contained 118 | within such NOTICE file, excluding those notices that do not 119 | pertain to any part of the Derivative Works, in at least one 120 | of the following places: within a NOTICE text file distributed 121 | as part of the Derivative Works; within the Source form or 122 | documentation, if provided along with the Derivative Works; or, 123 | within a display generated by the Derivative Works, if and 124 | wherever such third-party notices normally appear. The contents 125 | of the NOTICE file are for informational purposes only and 126 | do not modify the License. You may add Your own attribution 127 | notices within Derivative Works that You distribute, alongside 128 | or as an addendum to the NOTICE text from the Work, provided 129 | that such additional attribution notices cannot be construed 130 | as modifying the License. 131 | 132 | You may add Your own copyright statement to Your modifications and 133 | may provide additional or different license terms and conditions 134 | for use, reproduction, or distribution of Your modifications, or 135 | for any such Derivative Works as a whole, provided Your use, 136 | reproduction, and distribution of the Work otherwise complies with 137 | the conditions stated in this License. 138 | 139 | 5. Submission of Contributions. Unless You explicitly state otherwise, 140 | any Contribution intentionally submitted for inclusion in the Work 141 | by You to the Licensor shall be under the terms and conditions of 142 | this License, without any additional terms or conditions. 143 | Notwithstanding the above, nothing herein shall supersede or modify 144 | the terms of any separate license agreement you may have executed 145 | with Licensor regarding such Contributions. 146 | 147 | 6. Trademarks. This License does not grant permission to use the trade 148 | names, trademarks, service marks, or product names of the Licensor, 149 | except as required for reasonable and customary use in describing the 150 | origin of the Work and reproducing the content of the NOTICE file. 151 | 152 | 7. Disclaimer of Warranty. Unless required by applicable law or 153 | agreed to in writing, Licensor provides the Work (and each 154 | Contributor provides its Contributions) on an "AS IS" BASIS, 155 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 156 | implied, including, without limitation, any warranties or conditions 157 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 158 | PARTICULAR PURPOSE. You are solely responsible for determining the 159 | appropriateness of using or redistributing the Work and assume any 160 | risks associated with Your exercise of permissions under this License. 161 | 162 | 8. Limitation of Liability. In no event and under no legal theory, 163 | whether in tort (including negligence), contract, or otherwise, 164 | unless required by applicable law (such as deliberate and grossly 165 | negligent acts) or agreed to in writing, shall any Contributor be 166 | liable to You for damages, including any direct, indirect, special, 167 | incidental, or consequential damages of any character arising as a 168 | result of this License or out of the use or inability to use the 169 | Work (including but not limited to damages for loss of goodwill, 170 | work stoppage, computer failure or malfunction, or any and all 171 | other commercial damages or losses), even if such Contributor 172 | has been advised of the possibility of such damages. 173 | 174 | 9. Accepting Warranty or Additional Liability. While redistributing 175 | the Work or Derivative Works thereof, You may choose to offer, 176 | and charge a fee for, acceptance of support, warranty, indemnity, 177 | or other liability obligations and/or rights consistent with this 178 | License. However, in accepting such obligations, You may act only 179 | on Your own behalf and on Your sole responsibility, not on behalf 180 | of any other Contributor, and only if You agree to indemnify, 181 | defend, and hold each Contributor harmless for any liability 182 | incurred by, or claims asserted against, such Contributor by reason 183 | of your accepting any such warranty or additional liability. 184 | 185 | END OF TERMS AND CONDITIONS 186 | 187 | APPENDIX: How to apply the Apache License to your work. 188 | 189 | To apply the Apache License to your work, attach the following 190 | boilerplate notice, with the fields enclosed by brackets "{}" 191 | replaced with your own identifying information. (Don't include 192 | the brackets!) The text should be enclosed in the appropriate 193 | comment syntax for the file format. We also recommend that a 194 | file or class name and description of purpose be included on the 195 | same "printed page" as the copyright notice for easier 196 | identification within third-party archives. 197 | 198 | Copyright {yyyy} {name of copyright owner} 199 | 200 | Licensed under the Apache License, Version 2.0 (the "License"); 201 | you may not use this file except in compliance with the License. 202 | You may obtain a copy of the License at 203 | 204 | http://www.apache.org/licenses/LICENSE-2.0 205 | 206 | Unless required by applicable law or agreed to in writing, software 207 | distributed under the License is distributed on an "AS IS" BASIS, 208 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 209 | See the License for the specific language governing permissions and 210 | limitations under the License. 211 | -------------------------------------------------------------------------------- /src/main/java/com/marklogic/debugger/web/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.marklogic.debugger.web; 2 | 3 | import com.marklogic.client.DatabaseClient; 4 | import com.marklogic.client.DatabaseClientFactory; 5 | import com.marklogic.client.DatabaseClientFactory.Authentication; 6 | import com.marklogic.client.FailedRequestException; 7 | import com.marklogic.client.ResourceNotFoundException; 8 | import com.marklogic.client.eval.EvalResult; 9 | import com.marklogic.client.eval.EvalResultIterator; 10 | import com.marklogic.client.eval.ServerEvaluationCall; 11 | import com.marklogic.debugger.auth.ConnectionAuthenticationToken; 12 | import com.marklogic.debugger.errors.InvalidRequestException; 13 | import com.marklogic.xcc.*; 14 | import com.marklogic.xcc.exceptions.RequestException; 15 | import com.marklogic.xcc.types.ValueType; 16 | import org.apache.commons.io.IOUtils; 17 | import org.springframework.http.HttpMethod; 18 | import org.springframework.http.HttpStatus; 19 | import org.springframework.http.MediaType; 20 | import org.springframework.http.ResponseEntity; 21 | import org.springframework.http.client.ClientHttpRequest; 22 | import org.springframework.http.client.ClientHttpResponse; 23 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 24 | import org.springframework.security.core.context.SecurityContextHolder; 25 | import org.springframework.security.core.userdetails.User; 26 | import org.springframework.stereotype.Controller; 27 | import org.springframework.web.bind.annotation.*; 28 | 29 | import javax.servlet.http.HttpServletResponse; 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.net.URI; 33 | import java.net.URISyntaxException; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | 37 | @Controller 38 | @RequestMapping("/api") 39 | public class ApiController { 40 | 41 | /** 42 | * The UI checks the user's login status via this endpoint. 43 | */ 44 | @RequestMapping(value = "/user/status", method = RequestMethod.GET) 45 | @ResponseBody 46 | public String userStatus(HttpServletResponse response) { 47 | org.springframework.security.core.Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 48 | response.setContentType("application/json"); 49 | if (auth != null && auth.isAuthenticated()) { 50 | String username = null; 51 | if (auth.getPrincipal() instanceof User) { 52 | username = ((User) auth.getPrincipal()).getUsername(); 53 | } else { 54 | username = auth.getPrincipal().toString(); 55 | } 56 | return String.format("{\"authenticated\":true, \"username\":\"%s\"}", username); 57 | } 58 | return "{\"authenticated\":false}"; 59 | } 60 | 61 | /** 62 | * The UI logs the user out via this endpoint. 63 | */ 64 | @RequestMapping(value = "/user/logout", method = RequestMethod.DELETE) 65 | @ResponseBody 66 | public String logout() { 67 | SecurityContextHolder.clearContext(); 68 | return "{\"authenticated\":false}"; 69 | } 70 | 71 | @RequestMapping(value = "/server/status", method = RequestMethod.GET) 72 | @ResponseBody 73 | public String serverStatus(@RequestParam String host, @RequestParam int port) { 74 | boolean result = false; 75 | try { 76 | SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); 77 | requestFactory.setConnectTimeout(500); 78 | ClientHttpRequest request = requestFactory.createRequest(new URI("http://" + host + ":" + port), HttpMethod.HEAD); 79 | ClientHttpResponse response = request.execute(); 80 | result = true; 81 | } catch (IOException e) { 82 | e.printStackTrace(); 83 | } catch (URISyntaxException e) { 84 | e.printStackTrace(); 85 | } 86 | 87 | return "{\"result\":" + result + "}"; 88 | } 89 | 90 | @RequestMapping(value = "/servers", method = RequestMethod.GET) 91 | @ResponseBody 92 | public String getServers() throws InvalidRequestException { 93 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 94 | return evalQuery(auth, "get-servers.xqy"); 95 | } 96 | 97 | @RequestMapping(value = "/servers/{serverId}/enable", method = RequestMethod.GET) 98 | @ResponseBody 99 | public String enableServer(@PathVariable String serverId) throws InvalidRequestException { 100 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 101 | HashMap hm = new HashMap<>(); 102 | hm.put("serverId", serverId); 103 | return evalQuery(auth, "enable-server.xqy", hm); 104 | } 105 | 106 | @RequestMapping(value = "/servers/{serverId}", method = RequestMethod.GET) 107 | @ResponseBody 108 | public String isServerEnabled(@PathVariable String serverId) throws InvalidRequestException { 109 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 110 | HashMap hm = new HashMap<>(); 111 | hm.put("serverId", serverId); 112 | return evalQuery(auth, "is-server-enabled.xqy", hm); 113 | } 114 | 115 | @RequestMapping(value = "/servers/{serverId}/disable", method = RequestMethod.GET) 116 | @ResponseBody 117 | public String disableServer(@PathVariable String serverId) throws InvalidRequestException { 118 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 119 | HashMap hm = new HashMap<>(); 120 | hm.put("serverId", serverId); 121 | return evalQuery(auth, "disable-server.xqy", hm); 122 | } 123 | 124 | @RequestMapping(value = "/servers/{serverId}/files", method = RequestMethod.GET) 125 | @ResponseBody 126 | public String getServerFiles(@PathVariable String serverId) throws InvalidRequestException { 127 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 128 | HashMap hm = new HashMap<>(); 129 | hm.put("serverId", serverId); 130 | return evalQuery(auth, "get-files.xqy", hm); 131 | } 132 | 133 | 134 | @RequestMapping(value = "/marklogic/files", method = RequestMethod.GET) 135 | @ResponseBody 136 | public String getMarkLogicSystemFiles() throws InvalidRequestException { 137 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 138 | HashMap hm = new HashMap<>(); 139 | return evalQuery(auth, "get-marklogic-system-files.xqy", hm); 140 | } 141 | 142 | 143 | @RequestMapping(value = "/servers/{serverId}/file", method = RequestMethod.GET) 144 | @ResponseBody 145 | public String getServerFile(@PathVariable String serverId, @RequestParam String uri) throws InvalidRequestException { 146 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 147 | HashMap hm = new HashMap<>(); 148 | hm.put("serverId", serverId); 149 | hm.put("uri", uri); 150 | return evalQuery(auth, "get-file.xqy", hm); 151 | } 152 | 153 | @RequestMapping(value = "/servers/{serverId}/requests", method = RequestMethod.GET) 154 | @ResponseBody 155 | public String getRequests(@PathVariable String serverId) throws InvalidRequestException { 156 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 157 | HashMap hm = new HashMap<>(); 158 | hm.put("serverId", serverId); 159 | return evalQuery(auth, "get-requests.xqy", hm); 160 | } 161 | 162 | @RequestMapping(value = "/servers/{serverId}/requests/{requestId}", method = RequestMethod.GET) 163 | @ResponseBody 164 | public String getRequest(@PathVariable String serverId, @PathVariable String requestId) throws InvalidRequestException { 165 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 166 | HashMap hm = new HashMap<>(); 167 | hm.put("serverId", serverId); 168 | hm.put("requestId", requestId); 169 | return evalQuery(auth, "get-request.xqy", hm); 170 | } 171 | 172 | @RequestMapping(value = "/servers/{serverId}/invoke", method = RequestMethod.POST) 173 | public ResponseEntity invokeModule(@PathVariable String serverId, @RequestParam String uri) throws InvalidRequestException { 174 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 175 | HashMap hm = new HashMap<>(); 176 | hm.put("serverId", serverId); 177 | hm.put("uri", uri); 178 | evalQuery(auth, "invoke-module.xqy", hm); 179 | return new ResponseEntity<>(HttpStatus.NO_CONTENT); 180 | } 181 | 182 | @RequestMapping(value = "/requests/{requestId}/stack", method = RequestMethod.GET) 183 | @ResponseBody 184 | public String getStack(@PathVariable String requestId) throws InvalidRequestException { 185 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 186 | HashMap hm = new HashMap<>(); 187 | hm.put("requestId", requestId); 188 | return evalQuery(auth, "get-stacktrace.xqy", hm); 189 | } 190 | 191 | @RequestMapping(value = "/requests/{requestId}/step-over", method = RequestMethod.GET) 192 | @ResponseBody 193 | public String stepOver(@PathVariable String requestId) throws InvalidRequestException { 194 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 195 | HashMap hm = new HashMap<>(); 196 | hm.put("requestId", requestId); 197 | return evalQuery(auth, "step-over.xqy", hm); 198 | } 199 | 200 | @RequestMapping(value = "/requests/{requestId}/step-in", method = RequestMethod.GET) 201 | @ResponseBody 202 | public String stepIn(@PathVariable String requestId) throws InvalidRequestException { 203 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 204 | HashMap hm = new HashMap<>(); 205 | hm.put("requestId", requestId); 206 | return evalQuery(auth, "step-in.xqy", hm); 207 | } 208 | 209 | @RequestMapping(value = "/requests/{requestId}/step-out", method = RequestMethod.GET) 210 | @ResponseBody 211 | public String stepOut(@PathVariable String requestId) throws InvalidRequestException { 212 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 213 | HashMap hm = new HashMap<>(); 214 | hm.put("requestId", requestId); 215 | return evalQuery(auth, "step-out.xqy", hm); 216 | } 217 | 218 | @RequestMapping(value = "/requests/{requestId}/continue", method = RequestMethod.GET) 219 | @ResponseBody 220 | public String continueExecution(@PathVariable String requestId) throws InvalidRequestException { 221 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 222 | HashMap hm = new HashMap<>(); 223 | hm.put("requestId", requestId); 224 | return evalQuery(auth, "continue.xqy", hm); 225 | } 226 | 227 | @RequestMapping(value = "/requests/{requestId}/pause", method = RequestMethod.GET) 228 | @ResponseBody 229 | public String pauseRequest(@PathVariable String requestId) throws InvalidRequestException { 230 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 231 | HashMap hm = new HashMap<>(); 232 | hm.put("requestId", requestId); 233 | return evalQuery(auth, "pause.xqy", hm); 234 | } 235 | 236 | @RequestMapping(value = "/requests/{requestId}/breakpoints", method = RequestMethod.POST) 237 | @ResponseBody 238 | public String setBreakpoints(@PathVariable String requestId, @RequestBody List breakpoints) throws InvalidRequestException { 239 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 240 | HashMap hm = new HashMap<>(); 241 | hm.put("requestId", requestId); 242 | evalQuery(auth, "clear-breakpoints.xqy", hm); 243 | for (Breakpoint bp : breakpoints) { 244 | hm.put("uri", bp.uri); 245 | hm.put("line", bp.line); 246 | evalQuery(auth, "set-breakpoints.xqy", hm); 247 | } 248 | return ""; 249 | } 250 | 251 | @RequestMapping(value = "/requests/{requestId}/breakpoints", method = RequestMethod.GET) 252 | @ResponseBody 253 | public String getBreakpoints(@PathVariable String requestId) throws InvalidRequestException { 254 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 255 | HashMap hm = new HashMap<>(); 256 | hm.put("requestId", requestId); 257 | return evalQuery(auth, "get-breakpoints.xqy", hm); 258 | } 259 | 260 | @RequestMapping(value = "/requests/{requestId}/eval", method = RequestMethod.POST) 261 | @ResponseBody 262 | public String evalExpression(@PathVariable String requestId, @RequestBody String xquery) throws InvalidRequestException { 263 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 264 | HashMap hm = new HashMap<>(); 265 | hm.put("xquery", xquery); 266 | return evalQuery(auth, "eval.xqy", hm); 267 | } 268 | 269 | @RequestMapping(value = "/requests/{requestId}/value", method = RequestMethod.POST, produces = {MediaType.TEXT_PLAIN_VALUE}) 270 | @ResponseBody 271 | public String valueExpression(@PathVariable String requestId, @RequestBody String xquery) throws InvalidRequestException { 272 | ConnectionAuthenticationToken auth = (ConnectionAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); 273 | HashMap hm = new HashMap<>(); 274 | hm.put("requestId", requestId); 275 | hm.put("xquery", xquery); 276 | return evalQuery(auth, "value.xqy", hm); 277 | } 278 | 279 | private String getQuery(String resourceName) { 280 | try { 281 | InputStream inputStream = AppController.class.getClassLoader().getResourceAsStream("modules/" + resourceName); 282 | return IOUtils.toString(inputStream); 283 | } 284 | catch(IOException e) { 285 | e.printStackTrace(); 286 | throw new RuntimeException(e); 287 | } 288 | } 289 | 290 | private String evalQuery(ConnectionAuthenticationToken auth, String xquery) throws InvalidRequestException { 291 | return evalQuery(auth, xquery, new HashMap<>()); 292 | } 293 | 294 | private String evalQuery(ConnectionAuthenticationToken auth, String xquery, HashMap params) throws InvalidRequestException { 295 | String result = ""; 296 | if (auth != null) { 297 | try { 298 | DatabaseClient client = DatabaseClientFactory.newClient((String)auth.getHostname(), (Integer)auth.getPort(), (String)auth.getPrincipal(), (String)auth.getCredentials(), Authentication.DIGEST); 299 | String q = getQuery(xquery); 300 | ServerEvaluationCall sec = client.newServerEval().xquery(q); 301 | for (String key : params.keySet()) { 302 | sec.addVariable(key, params.get(key)); 303 | } 304 | EvalResultIterator it = sec.eval(); 305 | if (it != null && it.hasNext()) { 306 | EvalResult res = it.next(); 307 | result += res.getString(); 308 | } 309 | } 310 | catch(ResourceNotFoundException e) { 311 | try { 312 | ContentSource contentSource = ContentSourceFactory.newContentSource((String)auth.getHostname(), (Integer)auth.getPort(), (String)auth.getPrincipal(), (String)auth.getCredentials()); 313 | Session session = contentSource.newSession(); 314 | AdhocQuery adhocQuery = session.newAdhocQuery(getQuery(xquery)); 315 | for (String key : params.keySet()) { 316 | adhocQuery.setNewVariable(key, ValueType.XS_STRING, params.get(key)); 317 | } 318 | ResultSequence res = session.submitRequest(adhocQuery); 319 | result += res.asString(); 320 | } catch (RequestException e1) { 321 | e1.printStackTrace(); 322 | } 323 | } 324 | catch(FailedRequestException e) { 325 | if (e.getFailedRequest().getMessageCode().equals("DBG-REQUESTRECORD")) { 326 | throw new InvalidRequestException(); 327 | } 328 | throw new RuntimeException(e); 329 | } 330 | catch(Exception e) { 331 | e.printStackTrace(); 332 | } 333 | } 334 | return result; 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/main/ui/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, OnInit, OnDestroy, ViewChild } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Response } from '@angular/http'; 4 | import { MarkLogicService } from '../marklogic'; 5 | import { Breakpoint } from '../marklogic'; 6 | import { Router, ActivatedRoute, NavigationExtras } from '@angular/router'; 7 | import { AuthService } from '../auth'; 8 | import { ErrorComponent } from '../error'; 9 | import { StartupComponent } from '../help'; 10 | import { MdlDialogService, MdlDialogReference } from 'angular2-mdl'; 11 | import * as _ from 'lodash'; 12 | 13 | @Component({ 14 | selector: 'app-home', 15 | templateUrl: './home.component.html', 16 | styleUrls: ['./home.component.scss'] 17 | }) 18 | export class HomeComponent implements OnInit, OnDestroy { 19 | 20 | appservers: Array; 21 | selectedServer: any; 22 | hostname: string; 23 | port: number; 24 | serverFiles: any; 25 | systemFiles: any; 26 | requests: any; 27 | currentUri: string; 28 | currentLine: number; 29 | currentRequest: any; 30 | currentStackPosition: number; 31 | showLine: number; 32 | currentExpression: string; 33 | fileText: string; 34 | breakpoints: Map>; 35 | breakpointUris: Array; 36 | fileBreakpoints: Array; 37 | requestId: string; 38 | appserverName: any; 39 | stack: any; 40 | consoleInput: string; 41 | consoleOutput: Array = []; 42 | commandHistory: Array = []; 43 | commandHistoryIndex: number = -1; 44 | welcomeShown: boolean = false; 45 | 46 | breakpointsSet: boolean = false; 47 | 48 | @ViewChild('consoleInputCtrl') consoleInputCtrl; 49 | 50 | codeMirrorConfig = { 51 | lineNumbers: true, 52 | indentWithTabs: true, 53 | lineWrapping: true, 54 | readOnly: true, 55 | cursorBlinkRate: 0, 56 | gutters: ['CodeMirror-linenumbers', 'breakpoints', 'currentlines'], 57 | events: { 58 | gutterClick: this.gutterClick.bind(this), 59 | gutterContextMenu: this.gutterContextMenu.bind(this) 60 | } 61 | }; 62 | 63 | private sub: any; 64 | private sub2: any; 65 | 66 | constructor( 67 | private authService: AuthService, 68 | private router: Router, 69 | private route: ActivatedRoute, 70 | private dialogService: MdlDialogService, 71 | private marklogic: MarkLogicService) { 72 | this.hostname = authService.hostname; 73 | this.port = authService.port; 74 | } 75 | 76 | ngOnInit() { 77 | this.sub = this.route.params.subscribe(params => { 78 | this.appserverName = params['appserverName']; 79 | }); 80 | 81 | this.sub2 = this.route.queryParams.subscribe(params => { 82 | this.breakpointsSet = false; 83 | this.requestId = params['requestId']; 84 | 85 | if (!this.appserverName) { 86 | this.router.navigate(['login']); 87 | } 88 | 89 | if (!this.appservers || !this.selectedServer) { 90 | this.marklogic.getServers().subscribe((servers: any) => { 91 | this.appservers = servers; 92 | if (this.appserverName) { 93 | let server = _.find(this.appservers, (appserver) => { return appserver.name === this.appserverName; }); 94 | if (server) { 95 | this.selectedServer = server; 96 | this.showFiles(); 97 | this.getRequests(); 98 | this.getBreakpoints(); 99 | } 100 | } 101 | 102 | this.updateStack(); 103 | }, 104 | () => { 105 | this.router.navigate(['login']); 106 | }); 107 | } 108 | else { 109 | this.getRequests(); 110 | this.updateStack(); 111 | } 112 | }); 113 | 114 | if (!this.welcomeShown && localStorage.getItem('_show_welcome_') !== 'false') { 115 | this.showWelcome(); 116 | this.welcomeShown = true; 117 | } 118 | 119 | } 120 | 121 | updateStack() { 122 | if (this.requestId) { 123 | this.marklogic.getRequest(this.selectedServer.id, this.requestId).subscribe((request) => { 124 | this.currentRequest = request; 125 | this.getStack(this.requestId); 126 | }); 127 | } else { 128 | this.currentRequest = null; 129 | this.fileBreakpoints = null; 130 | this.currentLine = null; 131 | this.showLine = null; 132 | this.currentUri = null; 133 | } 134 | } 135 | 136 | showWelcome() { 137 | this.dialogService.showCustomDialog({ 138 | component: StartupComponent, 139 | isModal: true 140 | }); 141 | } 142 | 143 | handleDebugError(error: Response) { 144 | if (error.status === 404 && error.text() === 'Request ID not found') { 145 | let res = this.dialogService.alert(`The current Request: ${this.requestId} is no longer available.`); 146 | res.subscribe(() => { 147 | this.router.navigate(['server', this.appserverName]); 148 | }); 149 | } 150 | } 151 | 152 | openNewIssue() { 153 | window.open('https://github.com/paxtonhare/marklogic-debugger/issues/new', '_blank'); 154 | } 155 | 156 | ngOnDestroy() { 157 | this.sub.unsubscribe(); 158 | this.sub2.unsubscribe(); 159 | } 160 | 161 | logout() { 162 | this.authService.logout().subscribe(() => { 163 | this.router.navigate(['login']); 164 | }); 165 | } 166 | 167 | getRequests() { 168 | this.marklogic.getRequests(this.selectedServer.id).subscribe((requests: any) => { 169 | this.requests = requests; 170 | if (this.requests === null || this.requests.length === 0) { 171 | this.router.navigate(['server', this.appserverName]); 172 | } 173 | },() => { 174 | this.requests = null; 175 | }); 176 | } 177 | 178 | getStack(requestId) { 179 | this.marklogic.getStack(requestId).subscribe((stack: any) => { 180 | this.stack = stack; 181 | if (this.stack && this.stack.frames && this.stack.frames.length > 0) { 182 | this.showFile(this.stack.frames[0], 0); 183 | } 184 | }, () => { 185 | this.router.navigate(['server', this.appserverName]); 186 | }); 187 | } 188 | 189 | hasFrames() { 190 | return this.stack && 191 | this.stack.frames && 192 | this.stack.frames.length > 0; 193 | } 194 | 195 | hasVariables() { 196 | return this.stack && 197 | this.stack.frames && 198 | this.stack.frames[this.currentStackPosition] && 199 | ( 200 | ( 201 | this.stack.frames[this.currentStackPosition].variables && 202 | this.stack.frames[this.currentStackPosition].variables.length > 0 203 | ) || 204 | ( 205 | this.stack.frames[this.currentStackPosition].externalVariables && 206 | this.stack.frames[this.currentStackPosition].externalVariables.length > 0 207 | ) || 208 | ( 209 | this.stack.frames[this.currentStackPosition].globalVariables && 210 | this.stack.frames[this.currentStackPosition].globalVariables.length > 0 211 | ) 212 | ); 213 | } 214 | 215 | debugRequest(requestId) { 216 | let navigationExtras: NavigationExtras = { 217 | queryParams: { 'requestId': requestId } 218 | }; 219 | 220 | this.router.navigate(['server', this.appserverName], navigationExtras); 221 | } 222 | 223 | stepOver() { 224 | this.setBreakpoints().subscribe(() => { 225 | this.marklogic.stepOver(this.requestId).subscribe(() => { 226 | this.getStack(this.requestId); 227 | }); 228 | }, 229 | (error) => { 230 | this.handleDebugError(error) 231 | }); 232 | } 233 | 234 | stepIn() { 235 | this.setBreakpoints().subscribe(() => { 236 | this.marklogic.stepIn(this.requestId).subscribe(() => { 237 | this.getStack(this.requestId); 238 | }); 239 | }, 240 | (error) => { 241 | this.handleDebugError(error) 242 | }); 243 | } 244 | 245 | stepOut() { 246 | this.setBreakpoints().subscribe(() => { 247 | this.marklogic.stepOut(this.requestId).subscribe(() => { 248 | this.getStack(this.requestId); 249 | }); 250 | }, 251 | (error) => { 252 | this.handleDebugError(error) 253 | }); 254 | } 255 | 256 | continue() { 257 | this.setBreakpoints().subscribe((x: any) => { 258 | this.marklogic.continue(this.requestId).subscribe(() => { 259 | this.getStack(this.requestId); 260 | }); 261 | }, 262 | (error) => { 263 | this.handleDebugError(error) 264 | }); 265 | } 266 | 267 | continueRequest(requestId) { 268 | this.marklogic.continue(requestId).subscribe(() => { 269 | this.getRequests(); 270 | }); 271 | } 272 | 273 | pauseRequest(requestId) { 274 | this.marklogic.pause(requestId).subscribe(() => { 275 | this.debugRequest(requestId); 276 | }); 277 | } 278 | 279 | setBreakpoints() { 280 | let keys: Array; 281 | if (this.breakpoints) { 282 | keys = Array.from(this.breakpoints.keys()); 283 | } 284 | 285 | if (!this.breakpointsSet && keys && keys.length > 0) { 286 | this.breakpointsSet = true; 287 | let breakpoints = new Array(); 288 | for (let key of keys) { 289 | for (let bps of this.breakpoints.get(key)) { 290 | breakpoints.push(bps); 291 | } 292 | } 293 | return this.marklogic.sendBreakpoints(this.requestId, breakpoints); 294 | } 295 | 296 | return Observable.of({}); 297 | } 298 | 299 | gutterContextMenu(cm: any, line: number, gutter: string, clickEvent: MouseEvent) { 300 | clickEvent.preventDefault(); 301 | clickEvent.stopPropagation(); 302 | } 303 | 304 | gutterClick(cm: any, line: number, gutter: string, clickEvent: MouseEvent) { 305 | const info = cm.lineInfo(line); 306 | this.breakpointsSet = false; 307 | if (info.gutterMarkers && info.gutterMarkers.breakpoints && clickEvent.which === 3) { 308 | this.marklogic.disableBreakpoint(this.selectedServer.name, this.currentUri, line); 309 | } else if (info.gutterMarkers && info.gutterMarkers.breakpoints) { 310 | this.marklogic.toggleBreakpoint(this.selectedServer.name, this.currentUri, line); 311 | } else { 312 | this.marklogic.enableBreakpoint(this.selectedServer.name, this.currentUri, line); 313 | } 314 | this.getBreakpoints(); 315 | } 316 | 317 | getBreakpoints() { 318 | this.breakpoints = this.marklogic.getAllBreakpoints(this.selectedServer.name); 319 | this.breakpointUris = Array.from(this.breakpoints.keys()); 320 | if (this.currentUri) { 321 | this.fileBreakpoints = this.marklogic.getBreakpoints(this.selectedServer.name, this.currentUri); 322 | } else { 323 | this.fileBreakpoints = null; 324 | } 325 | } 326 | 327 | toggleBreakpoint(breakpoint: Breakpoint) { 328 | this.breakpointsSet = false; 329 | this.marklogic.toggleBreakpoint(this.selectedServer.name, breakpoint.uri, breakpoint.line); 330 | this.getBreakpoints(); 331 | } 332 | 333 | disableBreakpoint(breakpoint: Breakpoint) { 334 | this.breakpointsSet = false; 335 | this.marklogic.disableBreakpoint(this.selectedServer.name, breakpoint.uri, breakpoint.line); 336 | this.getBreakpoints(); 337 | } 338 | 339 | toggleConnected(server) { 340 | return server.connected ? this.enableServer(server) : this.disableServer(server); 341 | } 342 | 343 | enableServer(server) { 344 | this.marklogic.enableServer(server.id).subscribe(() => { 345 | server.connected = true; 346 | }); 347 | } 348 | 349 | disableServer(server) { 350 | this.marklogic.disableServer(server.id).subscribe(() => { 351 | server.connected = false; 352 | }); 353 | } 354 | 355 | showFiles() { 356 | this.marklogic.getFiles(this.selectedServer.id).subscribe((files: any) => { 357 | this.serverFiles = files; 358 | }); 359 | 360 | this.marklogic.getSystemFiles().subscribe((files: any) => { 361 | this.systemFiles = files; 362 | }); 363 | } 364 | 365 | fileClicked(uri) { 366 | this.fileBreakpoints = null; 367 | this.currentLine = null; 368 | this.showLine = null; 369 | this.marklogic.getFile(this.selectedServer.id, uri).subscribe((txt: any) => { 370 | this.currentUri = uri; 371 | this.fileText = txt; 372 | this.getBreakpoints(); 373 | }); 374 | } 375 | 376 | gotoBreakpoint(uri: string, line: number) { 377 | this.fileBreakpoints = null; 378 | this.currentLine = null; 379 | this.showLine = null; 380 | this.marklogic.getFile(this.selectedServer.id, uri).subscribe((txt: any) => { 381 | this.currentUri = uri; 382 | this.showLine = line; 383 | this.fileText = txt; 384 | this.getBreakpoints(); 385 | }); 386 | } 387 | 388 | showEval(frame, index: number) { 389 | this.currentLine = null; 390 | this.showLine = null; 391 | if (this.currentRequest) { 392 | this.currentUri = '/eval'; 393 | this.fileText = this.currentRequest.requestText; 394 | this.currentLine = frame.line; 395 | this.currentStackPosition = index; 396 | this.getBreakpoints(); 397 | if (this.stack.expressions && this.stack.expressions.length > 0) { 398 | if (index === 0) { 399 | this.currentExpression = this.stack.expressions[index].expressionSource; 400 | } 401 | else { 402 | this.currentExpression = null; 403 | } 404 | } 405 | } 406 | } 407 | 408 | showFile(frame: any, index: number) { 409 | if (frame.uri === '/eval') { 410 | this.showEval(frame, index); 411 | } else { 412 | this.currentLine = null; 413 | this.showLine = null; 414 | this.marklogic.getFile(this.selectedServer.id, frame.uri).subscribe((txt: any) => { 415 | this.currentUri = frame.uri; 416 | this.fileText = txt; 417 | this.currentLine = frame.line; 418 | this.currentStackPosition = index; 419 | this.getBreakpoints(); 420 | if (this.stack.expressions && this.stack.expressions.length > 0) { 421 | if (index === 0) { 422 | this.currentExpression = this.stack.expressions[index].expressionSource; 423 | } 424 | else { 425 | this.currentExpression = null; 426 | } 427 | } 428 | }); 429 | } 430 | } 431 | 432 | consoleKeyPressed($event: KeyboardEvent) { 433 | if (!this.requestId) { 434 | return; 435 | } 436 | if ($event.keyCode === 13) { 437 | this.consoleOutput.push({ 438 | txt: this.consoleInput, 439 | type: 'i' 440 | }); 441 | this.commandHistory.push(this.consoleInput); 442 | this.marklogic.valueExpression(this.requestId, this.consoleInput).subscribe((output: any) => { 443 | if (!output.error) { 444 | this.consoleOutput.push({ 445 | txt: output.resp, 446 | type: 'o' 447 | }); 448 | } else { 449 | this.consoleOutput.push({ 450 | txt: output.resp, 451 | type: 'e' 452 | }); 453 | } 454 | }); 455 | this.consoleInput = null; 456 | this.commandHistoryIndex = -1; 457 | } else if ($event.keyCode === 38) { 458 | if (this.commandHistoryIndex < (this.commandHistory.length - 1)) { 459 | this.commandHistoryIndex++; 460 | this.consoleInput = this.commandHistory[this.commandHistory.length - 1 - this.commandHistoryIndex]; 461 | } 462 | } else if ($event.keyCode === 40) { 463 | if (this.commandHistoryIndex > 0) { 464 | this.commandHistoryIndex--; 465 | this.consoleInput = this.commandHistory[this.commandHistory.length - 1 - this.commandHistoryIndex]; 466 | } 467 | } 468 | } 469 | 470 | invokeModule(uri: string) { 471 | this.marklogic.invokeModule(this.selectedServer.id, uri).subscribe(() => { 472 | setTimeout(() => { 473 | this.getRequests(); 474 | }, 1000); 475 | }); 476 | } 477 | 478 | showError(errorText: string) { 479 | this.dialogService.showCustomDialog({ 480 | component: ErrorComponent, 481 | providers: [ 482 | { provide: 'error', useValue: errorText } 483 | ], 484 | isModal: true 485 | }); 486 | } 487 | 488 | clearConsole($event: MouseEvent) { 489 | this.consoleOutput = []; 490 | $event.preventDefault(); 491 | $event.stopPropagation(); 492 | } 493 | 494 | focusConsole($event) { 495 | this.consoleInputCtrl.nativeElement.focus(); 496 | } 497 | 498 | getRequestName(request) { 499 | let name; 500 | if (request.requestKind === 'eval') { 501 | name = 'eval'; 502 | } else { 503 | name = (request.requestRewrittenText || request.requestText); 504 | } 505 | return name; 506 | } 507 | } 508 | --------------------------------------------------------------------------------