Some checks failed
build.yaml / feat: Enhance background job settings UI and functionality (push) Failing after 0s
- Updated BackgroundJob.vue to improve the display of background job statuses, including missing cross-references and current job mode. - Added auto-refresh functionality for background job status. - Introduced success toast notifications for successful status refreshes. - Modified the XML serialization process in DatasetXmlSerializer for better caching and performance. - Implemented a new RuleProvider for managing custom validation rules. - Improved error handling in routes for loading background job settings. - Enhanced ClamScan configuration with socket support for virus scanning. - Refactored dayjs utility to streamline locale management.
231 lines
7 KiB
TypeScript
231 lines
7 KiB
TypeScript
import DocumentXmlCache from '#models/DocumentXmlCache';
|
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
|
import Dataset from '#models/dataset';
|
|
import Strategy from './Strategy.js';
|
|
import { builder } from 'xmlbuilder2';
|
|
import logger from '@adonisjs/core/services/logger';
|
|
|
|
/**
|
|
* Configuration for XML serialization
|
|
*
|
|
* @interface XmlSerializationConfig
|
|
*/
|
|
export interface XmlSerializationConfig {
|
|
/** The dataset model to serialize */
|
|
model: Dataset;
|
|
/** DOM representation (if available) */
|
|
dom?: XMLBuilder;
|
|
/** Fields to exclude from serialization */
|
|
excludeFields: Array<string>;
|
|
/** Whether to exclude empty fields */
|
|
excludeEmpty: boolean;
|
|
/** Base URI for xlink:ref elements */
|
|
baseUri: string;
|
|
}
|
|
|
|
/**
|
|
* Options for controlling serialization behavior
|
|
*/
|
|
export interface SerializationOptions {
|
|
/** Enable XML caching */
|
|
enableCaching?: boolean;
|
|
/** Exclude empty fields from output */
|
|
excludeEmptyFields?: boolean;
|
|
/** Custom base URI */
|
|
baseUri?: string;
|
|
/** Fields to exclude */
|
|
excludeFields?: string[];
|
|
}
|
|
|
|
/**
|
|
* DatasetXmlSerializer
|
|
*
|
|
* Handles XML serialization of Dataset models with intelligent caching.
|
|
* Generates XML representations and manages cache lifecycle to optimize performance.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const serializer = new DatasetXmlSerializer(dataset);
|
|
* serializer.enableCaching();
|
|
* serializer.excludeEmptyFields();
|
|
*
|
|
* const xmlDocument = await serializer.toXmlDocument();
|
|
* ```
|
|
*/
|
|
export default class DatasetXmlSerializer {
|
|
private readonly config: XmlSerializationConfig;
|
|
private readonly strategy: Strategy;
|
|
private cache: DocumentXmlCache | null = null;
|
|
private cachingEnabled = false;
|
|
|
|
constructor(dataset: Dataset, options: SerializationOptions = {}) {
|
|
this.config = {
|
|
model: dataset,
|
|
excludeEmpty: options.excludeEmptyFields ?? false,
|
|
baseUri: options.baseUri ?? '',
|
|
excludeFields: options.excludeFields ?? [],
|
|
};
|
|
|
|
this.strategy = new Strategy({
|
|
excludeEmpty: options.excludeEmptyFields ?? false,
|
|
baseUri: options.baseUri ?? '',
|
|
excludeFields: options.excludeFields ?? [],
|
|
model: dataset,
|
|
});
|
|
|
|
if (options.enableCaching) {
|
|
this.cachingEnabled = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable caching for XML generation
|
|
* When enabled, generated XML is stored in database for faster retrieval
|
|
*/
|
|
public enableCaching(): this {
|
|
this.cachingEnabled = true;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Disable caching for XML generation
|
|
*/
|
|
public disableCaching(): this {
|
|
this.cachingEnabled = false;
|
|
return this;
|
|
}
|
|
|
|
set model(model: Dataset) {
|
|
this.config.model = model;
|
|
}
|
|
|
|
/**
|
|
* Configure to exclude empty fields from XML output
|
|
*/
|
|
public excludeEmptyFields(): this {
|
|
this.config.excludeEmpty = true;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the cache instance directly (useful when preloading)
|
|
* @param cache - The DocumentXmlCache instance
|
|
*/
|
|
public setCache(cache: DocumentXmlCache): this {
|
|
this.cache = cache;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the current cache instance
|
|
*/
|
|
public getCache(): DocumentXmlCache | null {
|
|
return this.cache;
|
|
}
|
|
|
|
/**
|
|
* Get DOM document with intelligent caching
|
|
* Returns cached version if valid, otherwise generates new document
|
|
*/
|
|
public async toXmlDocument(): Promise<XMLBuilder | null> {
|
|
const dataset = this.config.model;
|
|
|
|
// Try to get from cache first
|
|
let cachedDocument: XMLBuilder | null = await this.retrieveFromCache();
|
|
|
|
if (cachedDocument) {
|
|
logger.debug(`Using cached XML for dataset ${dataset.id}`);
|
|
return cachedDocument;
|
|
}
|
|
|
|
// Generate fresh document
|
|
logger.debug(`[DatasetXmlSerializer] Cache miss - generating fresh XML for dataset ${dataset.id}`);
|
|
const freshDocument = await this.strategy.createDomDocument();
|
|
|
|
if (!freshDocument) {
|
|
logger.error(`[DatasetXmlSerializer] Failed to generate XML for dataset ${dataset.id}`);
|
|
return null;
|
|
}
|
|
|
|
// Cache if caching is enabled
|
|
if (this.cachingEnabled) {
|
|
await this.persistToCache(freshDocument, dataset);
|
|
}
|
|
|
|
// Extract the dataset-specific node
|
|
return this.extractDatasetNode(freshDocument);
|
|
}
|
|
|
|
/**
|
|
* Generate XML string representation
|
|
* Convenience method that converts XMLBuilder to string
|
|
*/
|
|
public async toXmlString(): Promise<string | null> {
|
|
const document = await this.toXmlDocument();
|
|
return document ? document.end({ prettyPrint: false }) : null;
|
|
}
|
|
|
|
/**
|
|
* Persist generated XML document to cache
|
|
* Non-blocking - failures are logged but don't interrupt the flow
|
|
*/
|
|
private async persistToCache(domDocument: XMLBuilder, dataset: Dataset): Promise<void> {
|
|
try {
|
|
this.cache = this.cache || new DocumentXmlCache();
|
|
this.cache.document_id = dataset.id;
|
|
this.cache.xml_version = 1;
|
|
this.cache.server_date_modified = dataset.server_date_modified.toFormat('yyyy-MM-dd HH:mm:ss');
|
|
this.cache.xml_data = domDocument.end();
|
|
|
|
await this.cache.save();
|
|
logger.debug(`Cached XML for dataset ${dataset.id}`);
|
|
} catch (error) {
|
|
logger.error(`Failed to cache XML for dataset ${dataset.id}: ${error.message}`);
|
|
// Don't throw - caching failure shouldn't break the flow
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract the Rdr_Dataset node from full document
|
|
*/
|
|
private extractDatasetNode(domDocument: XMLBuilder): XMLBuilder | null {
|
|
const node = domDocument.find((n) => n.node.nodeName === 'Rdr_Dataset', false, true)?.node;
|
|
|
|
if (node) {
|
|
return builder({ version: '1.0', encoding: 'UTF-8', standalone: true }, node);
|
|
}
|
|
|
|
return domDocument;
|
|
}
|
|
|
|
/**
|
|
* Attempt to retrieve valid cached XML document
|
|
* Returns null if cache doesn't exist or is stale
|
|
*/
|
|
private async retrieveFromCache(): Promise<XMLBuilder | null> {
|
|
const dataset: Dataset = this.config.model;
|
|
if (!this.cache) {
|
|
return null;
|
|
}
|
|
|
|
// Check if cache is still valid
|
|
const actuallyCached = await DocumentXmlCache.hasValidEntry(dataset.id, dataset.server_date_modified);
|
|
|
|
if (!actuallyCached) {
|
|
logger.debug(`Cache invalid for dataset ${dataset.id}`);
|
|
return null;
|
|
}
|
|
|
|
//cache is actual return cached document
|
|
try {
|
|
if (this.cache) {
|
|
return this.cache.getDomDocument();
|
|
} else {
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Failed to retrieve cached document for dataset ${dataset.id}: ${error.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
}
|