Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Показаны сообщения с ярлыком skeleton. Показать все сообщения
Показаны сообщения с ярлыком skeleton. Показать все сообщения

All-In-One Code Framework

All-In-One Code Framework(AIO) delineates the framework and skeleton of most Microsoft development techniques (e.g. COM, Data Access, IPC) using typical sample codes in different programming languages (e.g. Visual C#, VB.NET, Visual C++). For instance, the code example ATLDllCOMServer shows the skeleton of an ATL ActiveX DLL as its name implies. Each example is elaborately selected, composed, and documented to demonstrate one frequently-asked, tested or used scenario based on our experience as support engineers. If you are a software developer, you can fill the skeleton with blood, muscle and soul. If you are a software tester or a support engineer like us, you may extend the sample codes a little to fit your specific test scenario or refer your customer to this project if the customer's question coincides with what we collected.

А если вкратце, то это набор исходных кодов скелетных приложений, которые демонстрируют различные технологии и подходы к реализации приложений. Например, там есть хостинг CLR 4.0, Out of process .NET exe COM server и прочие извращения. Ооооочень полезный наборчик.

Pretty print XML using MSXML6

Задача простая - отформатировать красиво XML.

MSXML6 я обычно использую следующим образом:

#import <msxml6.dll> rename_namespace("MSXML6")

Заголовочный файл с классом XMLUtility.h:

#pragma once

struct XMLUtility
{

    static _bstr_t PrettyPrint(const MSXML6::IXMLDOMNodePtr &pSrc);

};

cpp файл с классом XMLUtility.cpp:

#include "stdafx.h"
#include "XMLUtility.h"

namespace {

    using namespace MSXML6;

    class SAXContentHandlerFilter : public ISAXContentHandler
    {

    public:
        SAXContentHandlerFilter(const MSXML6::ISAXContentHandlerPtr &pTarget) : _pTarget(pTarget), _charactersSkipThreshold(INT_MAX) {
            ASSERT(pTarget);
        }
        virtual ~SAXContentHandlerFilter() {}

    public:
        // IUnknown implementation
        HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObj) {
            if (!ppvObj)
                return(E_POINTER);

            if (riid == IID_IUnknown || riid == __uuidof(ISAXContentHandler)) {
                *ppvObj = this;
            }
            else {
                *ppvObj = NULL;
                return(E_NOINTERFACE);
            }

            AddRef();
            return(S_OK);
        }

        ULONG STDMETHODCALLTYPE AddRef() {
            return(InterlockedIncrement(&_refCount));
        }

        ULONG STDMETHODCALLTYPE Release() {
            const ULONG count = InterlockedDecrement(&_refCount);
            if (count == 0)
                delete this;
            return(count);
        }


        // ISAXContentHandler implementation
    public:
        STDMETHODIMP raw_putDocumentLocator(struct ISAXLocator *pLocator) {
            return(_pTarget->raw_putDocumentLocator(pLocator));
        }

        STDMETHODIMP raw_startDocument() {
            return(_pTarget->raw_startDocument());
        }

        STDMETHODIMP raw_endDocument() {
            return(_pTarget->raw_endDocument());
        }

        STDMETHODIMP raw_startPrefixMapping(unsigned short *pwchPrefix, int cchPrefix, unsigned short *pwchUri, int cchUri) {
            return(_pTarget->raw_startPrefixMapping(pwchPrefix, cchPrefix, pwchUri, cchUri));
        }

        STDMETHODIMP raw_endPrefixMapping(unsigned short *pwchPrefix, int cchPrefix) {
            return(_pTarget->raw_endPrefixMapping(pwchPrefix, cchPrefix));
        }

        STDMETHODIMP raw_startElement(unsigned short *pwchNamespaceUri, int cchNamespaceUri, unsigned short *pwchLocalName, int cchLocalName, unsigned short *pwchQName, int cchQName, struct ISAXAttributes *pAttributes) {
            return(_pTarget->raw_startElement(pwchNamespaceUri, cchNamespaceUri, pwchLocalName, cchLocalName, pwchQName, cchQName, pAttributes));
        }

        STDMETHODIMP raw_endElement(unsigned short *pwchNamespaceUri, int cchNamespaceUri, unsigned short *pwchLocalName, int cchLocalName, unsigned short *pwchQName, int cchQName) {
            return(_pTarget->raw_endElement(pwchNamespaceUri, cchNamespaceUri, pwchLocalName, cchLocalName, pwchQName, cchQName));
        }

        STDMETHODIMP raw_characters(unsigned short *pwchChars, int cchChars) {
            const bool skip = _charactersSkipThreshold < cchChars && _pSAXLexicalHandler;
            if (!skip) {
                return(_pTarget->raw_characters(pwchChars, cchChars));
            }
            else {
                CStringW skipped;
                skipped.Format(L" %d characters have been skipped ", cchChars);
                return(_pSAXLexicalHandler->comment(reinterpret_cast<unsigned short *>(skipped.LockBuffer()), skipped.GetLength()));
            }
        }

        STDMETHODIMP raw_ignorableWhitespace(unsigned short *pwchChars, int cchChars) {
            return(_pTarget->raw_ignorableWhitespace(pwchChars, cchChars));
        }

        STDMETHODIMP raw_processingInstruction(unsigned short *pwchTarget, int cchTarget, unsigned short *pwchData, int cchData) {
            return(_pTarget->raw_processingInstruction(pwchTarget, cchTarget, pwchData, cchData));
        }

        STDMETHODIMP raw_skippedEntity(unsigned short *pwchName, int cchName) {
            return(_pTarget->raw_skippedEntity(pwchName, cchName));
        }

    public:
        void SetLexicalHandler(const MSXML6::ISAXLexicalHandlerPtr &pSAXLexicalHandler) {
            ASSERT(pSAXLexicalHandler);

            _pSAXLexicalHandler = pSAXLexicalHandler;
        }

        void SetCharactersSkipThreshold(int charactersSkipThreshold) {
            ASSERT(_pSAXLexicalHandler);

            _charactersSkipThreshold = charactersSkipThreshold;
        }

    private:
        const MSXML6::ISAXContentHandlerPtr _pTarget;
        LONG _refCount;
        // Filter options
        ISAXLexicalHandlerPtr _pSAXLexicalHandler;
        int _charactersSkipThreshold;

    };

}

_bstr_t XMLUtility::PrettyPrint(const MSXML6::IXMLDOMNodePtr &pSrc) {
    ASSERT(pSrc);
    if (!pSrc) {
        return(L"<NULL/>");
    }

    using namespace MSXML6;

    HRESULT hr = S_OK;

    IMXWriterPtr pMXWriter;
    if (FAILED(hr = pMXWriter.CreateInstance(__uuidof(MXXMLWriter60)))) {
        return(pSrc->xml);
    }

    pMXWriter->indent = true;
    pMXWriter->omitXMLDeclaration = true;

    const ISAXContentHandlerPtr pSAXContentHandler = pMXWriter;
    const ISAXErrorHandlerPtr pSAXErrorHandler = pMXWriter;
    const ISAXDTDHandlerPtr pSAXDTDHandler = pMXWriter;
    const ISAXLexicalHandlerPtr pSAXLexicalHandler = pMXWriter;
    const ISAXDeclHandlerPtr pSAXDeclHandler = pMXWriter;
    if (!pSAXContentHandler || !pSAXErrorHandler || !pSAXDTDHandler || !pSAXLexicalHandler || !pSAXDeclHandler) {
        return(pSrc->xml);
    }

    ISAXXMLReaderPtr pSAXReader;
    if (FAILED(hr = pSAXReader.CreateInstance(__uuidof(SAXXMLReader60)))) {
        return(pSrc->xml);
    }

    SAXContentHandlerFilter * const pFilter = new SAXContentHandlerFilter(pSAXContentHandler);
    const ISAXContentHandlerPtr pContentHandlerProxy(pFilter);
    pFilter->SetLexicalHandler(pSAXLexicalHandler);
    pFilter->SetCharactersSkipThreshold(200);

    if    (FAILED(hr = pSAXReader->putContentHandler(pContentHandlerProxy))
            || FAILED(hr = pSAXReader->putDTDHandler(pSAXDTDHandler))
            || FAILED(hr = pSAXReader->putErrorHandler(pSAXErrorHandler))
            || FAILED(hr = pSAXReader->putProperty(reinterpret_cast<unsigned short *>(L"http://xml.org/sax/properties/lexical-handler"), _variant_t(pSAXLexicalHandler.GetInterfacePtr())))
            || FAILED(hr = pSAXReader->putProperty(reinterpret_cast<unsigned short *>(L"http://xml.org/sax/properties/declaration-handler"), _variant_t(pSAXDeclHandler.GetInterfacePtr())))) {
        return(pSrc->xml);
    }

    if    (FAILED(hr = pSAXReader->parse(_variant_t(pSrc.GetInterfacePtr())))) {
        return(pSrc->xml);
    }

    return(pMXWriter->output);
}

Кроме форматирования я делаю еще некоторую фильтрацию - выбрасываю слишком большие текстовые фрагменты, заменяя их комментариями.

WCF cyclic references support

WCF DataContract serializer isn't by default aware of cyclic object graphs. If you encounter the Object graph for type 'X.Y.Z' contains cycles and cannot be serialized if reference tracking is disabled error - read to the end.

There is a simple solution and it's well documented in the Preserving Object Reference in WCF article. However the code is very chaotic and contains mistakes. I've made several classes to support a simple and powerful control over the DataContract serialization parameters.

Support classes

  1. ApplyCyclicDataContractSerializerOperationBehavior

    An operation behavior and The DataContractSerializerOperationBehavior descendant. Used to inject custom configured DataContractSerializer instances into WCF pipeline.

    internal class ApplyCyclicDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior{
        private readonly Int32 _maxItemsInObjectGraph;
        private readonly bool _ignoreExtensionDataObject;
        private readonly bool _preserveObjectReferences;
    
        public ApplyCyclicDataContractSerializerOperationBehavior(OperationDescription operationDescription, Int32 maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences)
            : base(operationDescription) {
            _maxItemsInObjectGraph = maxItemsInObjectGraph;
            _ignoreExtensionDataObject = ignoreExtensionDataObject;
            _preserveObjectReferences = preserveObjectReferences;
        }
    
        public override XmlObjectSerializer CreateSerializer(Type type, String name, String ns, IList<Type> knownTypes) {
            return (new DataContractSerializer(type, name, ns, knownTypes, _maxItemsInObjectGraph, _ignoreExtensionDataObject, _preserveObjectReferences, null /*dataContractSurrogate*/));
        }
    
        public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes) {
            return (new DataContractSerializer(type, name, ns, knownTypes, _maxItemsInObjectGraph, _ignoreExtensionDataObject, _preserveObjectReferences, null /*dataContractSurrogate*/));
        }
    
    }
    
  2. CyclicReferencesAwareContractBehavior

    A contract behavior. Used to apply the DataContractSerializerOperationBehavior operation behavior to every operation within a contract.

    public class CyclicReferencesAwareContractBehavior : IContractBehavior{
        private const Int32 maxItemsInObjectGraph = 0xFFFF;
        private const bool ignoreExtensionDataObject = false;
    
        private bool _on;
    
        public CyclicReferencesAwareContractBehavior(bool on) {
            _on = on;
        }
    
        #region IContractBehavior Members
    
        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) {
        }
    
        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) {
            ReplaceDataContractSerializerOperationBehaviors(contractDescription, _on);
        }
    
        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) {
            ReplaceDataContractSerializerOperationBehaviors(contractDescription, _on);
        }
    
        internal static void ReplaceDataContractSerializerOperationBehaviors(ContractDescription contractDescription, bool on) {
            foreach (var operation in contractDescription.Operations) {
                ReplaceDataContractSerializerOperationBehavior(operation, on);
            }
        }
    
        internal static void ReplaceDataContractSerializerOperationBehavior(OperationDescription operation, bool on) {
            if (operation.Behaviors.Remove(typeof(DataContractSerializerOperationBehavior)) || operation.Behaviors.Remove(typeof(ApplyCyclicDataContractSerializerOperationBehavior))) {
                operation.Behaviors.Add(new ApplyCyclicDataContractSerializerOperationBehavior(operation, maxItemsInObjectGraph, ignoreExtensionDataObject, on));
            }
        }
    
        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) {
        }
    
        #endregion
    }
    
  3. CyclicReferencesAwareAttribute

    The most valuable member of the triade. Used to apply cycling support policy to the whole service interface or an individual interface operation.

    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)]
    public class CyclicReferencesAwareAttribute : Attribute, IContractBehavior, IOperationBehavior{
        private readonly bool _on = true;
    
        public CyclicReferencesAwareAttribute(bool on) {
            _on = on;
        }
    
        public bool On {
            get { return (_on); }
        }
    
        #region IOperationBehavior Members
    
        void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {
        }
    
        void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehavior(operationDescription, On);
        }
    
        void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehavior(operationDescription, On);
        }
    
        void IOperationBehavior.Validate(OperationDescription operationDescription) {
        }
    
        #endregion
    
        #region IContractBehavior Members
    
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {
        }
    
        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehaviors(contractDescription, On);
        }
    
        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehaviors(contractDescription, On);
        }
    
        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) {
        }
    
        #endregion
    }
    

Examples:

CyclicReferencesAware applied to the whole interface:

[ServiceContract]
[CyclicReferencesAware(true)]
public interface TestFacade
{
    [OperationContract]
    void Hello(SomeData data);
}

CyclicReferencesAware applied to individual operation(s):

[ServiceContract]
public interface TestFacade
{
    [OperationContract]
    [CyclicReferencesAware(true)]
    void Hello(SomeData data);
}

CyclicReferencesAware applied to the whole interface and individual operation(s):

[ServiceContract]
[CyclicReferencesAware(false)]
public interface TestFacade
{
    [OperationContract]
    [CyclicReferencesAware(true)]
    void Hello(SomeData data);
}

Easy reporting to Word, PDF, HTML

Тяжело работать с людьми, которые сами не знают, чего хотят. Еще хуже, если эти люди являются заказчиками программного обеспечения. Они требуют за копейки кнопку "Сделать все". А потом еще чуть-чуть. И я уже не первый раз наблюдаю эту ситуацию. А самое веселое, когда дело доходит до отчетов. Мы хотим, чтобы был дизайнер. А еще мы хотим, чтобы все было попроще. А еще мы не знаем, что это будут за отчеты, так что сделайте универсальное решение. И ломай потом голову.

Я считаю, что дизайнер стоит кучу денег, для его написания нужен не один человеко-год. И пока что не видел ни одного универсального решения, с которым пользователь ощущал бы себя не обезьяной. Наиболее приемлемый вариант - вбивать логику построения отчета в программу. Дешево и сердито! Ну, дальше изложу один из вариантов такого решения.

Для получения отчета нужно:

  1. Данные

    Строки, числа, даты необходимые для построения отчета.

  2. Шаблон

    Набор правил построения отчета.

  3. Процессор

    Механизм, преобразующий данные по правилам.

В самом сложном варианте эти компоненты создаются индивидуально для каждого отчета. Но, лучше, конечно, по-максимуму автоматизировать каждую часть и их взаимодействие. В идеале, шаблон редактируется с помощью developer-friendly designer, процессор является универсальным готовым решением, а данные представляют собой расширяемый формат, в который можно положить любые данные.

XML, XSLT 2.0 и Altova StyleVision

Итак, данные в формате XML преобразуются XSLT процессором в выходной файл.

Altova StyleVision является нашим самым главным фигурантом и помощником, он же developer-friendly designer. Это визуальный редактор, который позволяет получить на выходе XSLT 2.0 файл, преобразующий XML в HTML, RTF или PDF!

Я уже точно не помню, зачем понадобился именно XSLT 2.0, вероятно из-за каких-то фич дизайнера, но вот найти бесплатный процессор для Java было напряжно. Из коммерческих есть Oracle XML Developer's Kit, из некоммерческих - Saxon. Других не нашел! С .NET ситуация хуже, под него только Saxon. Ну, Saxon так Saxon!

Исходные данные

Исходные данные получаем из источника данных (БД, например) и формируем XML файл с определенной схемой, которая описывается в XSD. Механизм формирования не имеет значения, но, если данные качаются из БД, я бы предпочел возложить этот процесс на сервер (далее в примере я буду получать от любимой СУБД Oracle сразу XML, а если поднатужиться, то и файл с отчетом можно получить!).

Я буду использовать существующую схему SH (Sales History). Как ее установить написано здесь.

Итак, наш отчет будет выдавать некую информацию о клиенте - имя, фамилия, пол, дата рождения и т.д. StyleVision хочет видеть XSD схему документов, которые прийдется использовать в качестве данных. Схему можно создавать либо вручную, подгоняя потом под нее XML, либо построить по существующему XML. Все это, кстати умеет делать Altova XMLSpy. Вот моя схема (Customer.xsd):

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:simpleType name="T_Valid">
      <xs:restriction base="xs:string">
         <xs:enumeration value="I"/>
         <xs:enumeration value="A"/>
      </xs:restriction>
   </xs:simpleType>
   <xs:simpleType name="T_Gender">
      <xs:restriction base="xs:string">
         <xs:enumeration value="M"/>
         <xs:enumeration value="F"/>
      </xs:restriction>
   </xs:simpleType>
   <xs:complexType name="T_Customer">
      <xs:sequence>
         <xs:element ref="FirstName"/>
         <xs:element ref="LastName"/>
         <xs:element ref="Gender"/>
         <xs:element ref="YearOfBirth"/>
         <xs:element ref="Address"/>
         <xs:element ref="Valid"/>
      </xs:sequence>
   </xs:complexType>
   <xs:complexType name="T_Address">
      <xs:sequence>
         <xs:element ref="Street"/>
         <xs:element ref="PostalCode"/>
         <xs:element ref="City"/>
         <xs:element ref="StateProvince"/>
         <xs:element ref="Country"/>
      </xs:sequence>
   </xs:complexType>
   <xs:element name="YearOfBirth" type="xs:short"/>
   <xs:element name="Valid" type="T_Valid"/>
   <xs:element name="Street" type="xs:string"/>
   <xs:element name="StateProvince" type="xs:string"/>
   <xs:element name="PostalCode" type="xs:int"/>
   <xs:element name="LastName" type="xs:string"/>
   <xs:element name="Gender" type="T_Gender"/>
   <xs:element name="FirstName" type="xs:string"/>
   <xs:element name="Customer" type="T_Customer"/>
   <xs:element name="Country" type="xs:string"/>
   <xs:element name="City" type="xs:string"/>
   <xs:element name="Address" type="T_Address"/>
</xs:schema>

Соответственно, Oracle SQL запрос (в SQL Server этот запрос был бы на пару страниц неразбираемого текста):

SELECT XMLElement("Customer",
                  XMLElement("FirstName", c.cust_first_name),
                  XMLElement("LastName", c.cust_last_name),
                  XMLElement("Gender", c.cust_gender),
                  XMLElement("YearOfBirth", c.cust_year_of_birth),
                  XMLElement("Address",
                             XMLElement("Street", c.cust_street_address),
                             XMLElement("PostalCode", c.cust_postal_code),
                             XMLElement("City", c.cust_city),
                             XMLElement("StateProvince", c.cust_state_province),
                             XMLElement("Country",
                                        (SELECT country_name FROM sh.countries WHERE country_id = c.country_id)
                             )
                  ),
                  XMLElement("Valid", c.cust_valid)) AS DATA
  FROM sh.customers c
 WHERE c.cust_id = 43228

Вместо 43228, конечно, нужно сделать bindable переменную, но мне лень.

И его результат (Customer.xml):

<Customer>
   <FirstName>Abner</FirstName>
   <LastName>Everett</LastName>
   <Gender>M</Gender>
   <YearOfBirth>1957</YearOfBirth>
   <Address>
      <Street>117 West Gloucester Avenue</Street>
      <PostalCode>72059</PostalCode>
      <City>Los Angeles</City>
      <StateProvince>CA</StateProvince>
      <Country>United States of America</Country>
   </Address>
   <Valid>I</Valid>
</Customer>

Altova StyleVision

File -> New -> New from XML Schema / DTD..., указать путь к XSD файлу и желательно предоставить ему сразу файл-пример с данными, соответствующий этой схеме, конечно же.

После окончания создания шаблона (template.sps) нужно его сохранить в XSLT (transform-rtf.xslt): File -> Save Generated Files -> Save Generated XSLT-RTF File....

Saxon

What is Saxon?

The Saxon package is a collection of tools for processing XML documents. The main components are:

  • An XSLT 2.0 processor, which can be used from the command line, or invoked from an application, using a supplied API. This can also be used to run XSLT 1.0 stylesheets.
  • An XPath 2.0 processor accessible to applications via a supplied API.
  • An XQuery 1.0 processor that can be used from the command line, or invoked from an application by use of a supplied API.
  • An XML Schema 1.0 processor. This can be used on its own to validate a schema for correctness, or to validate a source document against the definitions in a schema. It is also used to support the schema-aware functionality of the XSLT and XQuery processors. Like the other tools, it can be run from the command line, or invoked from an application.
  • On the Java platform, when using XSLT, XPath, or XML schema validation, the API supported by Saxon is the JAXP API, which means it is possible for a Java application to switch between different XSLT, XPath, and XML Schema processors without changing the application code. At the time of writing, however, Saxon is still the only JAXP implementation that offers support for XSLT 2.0. Saxon offers two APIs for XQuery processing: its own native API, which has been present in the product for some time, and an early implementation of the XQJ specification which is under development as part of the Java Community Process.
  • On the .NET platform, Saxon offers an API that enables close integration with other services available from .NET, notably the XML-related classes in the System.Xml namespace. It isn't possible to use Saxon as a transparent plug-in replacement for the System.Xml.Xsl processor, because the API for the Microsoft engine using concrete classes rather than abstract interfaces. However, it is possible to use it as a functional replacement with minor changes to your application code.

Saxon implements XSLT 2.0, XPath 2.0, and XQuery 1.0 as defined in the final Recommendations of 23 January 2007. It also includes an implementation of the XML Schema 1.0 Recommendation.

Full details of Saxon's conformance to the specifications are provided in the Conformance section.

In addition, Saxon provides an extensive library of extensions, all implemented in conformance with the XSLT and XQuery Recommendations to ensure that portable stylesheets and queries can be written. These include the EXSLT extension libraries common, sets, math, and dates-and-times. Many of these extensions were pioneered in Saxon and have since become available in other products.

These extension functions are in general accessible from XQuery and XPath as well as XSLT, except where they depend on stylesheet information. Some extensions are available in Saxon-SA only.

Компиляция XSLT файла

Библиотека Saxon позволяет компилировать XSLT файл:

The term compile is stretching a point. The executable that is produced does not contain machine instructions, or even interpreted Java bytecode. It contains instructions in the form of a data structure that Saxon itself can interpret. Note that the format of compiled stylesheets is unlikely to be stable from one Saxon release to the next.

Можно, конечно, не компилировать, но я предпочитаю прятать исходники шаблона (в т.ч. из лицензионных соображений). Кстати, из-за этого на моей прошлой работе разработчику прийдется все переделывать с нуля - файлы шаблона и схемы безвозвратно утеряны с моим уходом (злобный смешок). Команда компиляции выглядит так:

java -cp d:\Development\Libs\Java\Saxon.v9\saxon9.jar net.sf.saxon.Compile transform-rtf.xslt transform-rtf.saxon

transform-rtf.xslt - входной файл, transform-rtf.saxon - выходной.

Java код

Все просто - подключение к БД, выполнение запроса, получение потока XML, загрузка скомпилированного шаблона, создание трансформера и преобразование XML в RTF.

 0: /**
 1:  *
 2:  */
 3: package com.chabster.reporting.app;
 4:
 5: import java.io.File;
 6: import java.io.InputStream;
 7: import java.sql.Connection;
 8: import java.sql.DriverManager;
 9:
10: import javax.xml.transform.*;
11: import javax.xml.transform.stream.StreamResult;
12: import javax.xml.transform.stream.StreamSource;
13:
14: import net.sf.saxon.Configuration;
15: import net.sf.saxon.PreparedStylesheet;
16: import oracle.jdbc.OraclePreparedStatement;
17: import oracle.jdbc.OracleResultSet;
18: import oracle.jdbc.driver.OracleDriver;
19: import oracle.sql.OPAQUE;
20:
21: /**
22:  * @author Chabster
23:  */
24: public class Startup
25: {
26:
27:   static String query = "SELECT XMLElement(\"Customer\",\r\n" +
28:         "                 XMLElement(\"FirstName\", c.cust_first_name),\r\n" +
29:         "                 XMLElement(\"LastName\", c.cust_last_name),\r\n" +
30:         "                 XMLElement(\"Gender\", c.cust_gender),\r\n" +
31:         "                 XMLElement(\"YearOfBirth\", c.cust_year_of_birth),\r\n" +
32:         "                 XMLElement(\"Address\",\r\n" +
33:         "                            XMLElement(\"Street\", c.cust_street_address),\r\n" +
34:         "                            XMLElement(\"PostalCode\", c.cust_postal_code),\r\n" +
35:         "                            XMLElement(\"City\", c.cust_city),\r\n" +
36:         "                            XMLElement(\"StateProvince\", c.cust_state_province),\r\n" +
37:         "                            XMLElement(\"Country\",\r\n" +
38:         "                                       (SELECT country_name FROM sh.countries WHERE country_id = c.country_id)\r\n" +
39:         "                            )\r\n" +
40:         "                 ),\r\n" +
41:         "                 XMLElement(\"Valid\", c.cust_valid)) AS DATA\r\n" +
42:         "  FROM sh.customers c\r\n" +
43:         " WHERE c.cust_id = 43228";
44:
45:   public static void main(String[] args) {
46:      try {
47:         DriverManager.registerDriver(new OracleDriver());
48:
49:         Connection conn = DriverManager.getConnection("jdbc:oracle:thin:chabster/***@localhost:1521/MAIN");
50:         OraclePreparedStatement preparedStmt = (OraclePreparedStatement) conn.prepareStatement(query);
51:
52:         OracleResultSet oraRS = (OracleResultSet) preparedStmt.executeQuery();
53:         oraRS.next();
54:         OPAQUE xmlObj = oraRS.getOPAQUE(1);
55:         Configuration configuration = new Configuration();
56:         InputStream xmlStream = xmlObj.getStream();
57:
58:         PreparedStylesheet preparedStylesheet = PreparedStylesheet.loadCompiledStylesheet(configuration, "transform-rtf.saxon");
59:
60:         Transformer transformer = preparedStylesheet.newTransformer();
61:         File outputFile = new File("out.rtf");
62:         Source xmlSource = new StreamSource(xmlStream);
63:         Result output = new StreamResult(outputFile);
64:
65:         transformer.transform(xmlSource, output);
66:
67:         xmlStream.close();
68:         oraRS.close();
69:         preparedStmt.close();
70:         conn.close();
71:      }
72:      catch (Exception ex) {
73:         ex.printStackTrace();
74:      }
75:   }
76: }

Выводы

Механизм прост и удобен для разработчика. Генерация файлов происходит быстро, поддерживаются основные форматы документов - HTML, RTF и PDF. Правда, есть и свои нюансы, связанные с ограниченными возможностями дизайнера Altova StyleVision. Например, если в отчет нужно вставить картинку из БД, то прийдется это писать частично вручную. Также минимальный размер генерируемого файла - 46 Kb! Плюс вечно плавающие стандарты W3C и отсутствие библиотек, которые их поддерживают.

Run as interactive user from service

The code below runs a program on interactive desktop with logged on user privileges, as it was started by user himself. Must be executed by Local System, for example, by Windows service.

stdafx.h:

#include <WtsApi32.h>
#pragma comment(lib, "WtsApi32.lib")

#include <Userenv.h>
#pragma comment(lib, "Userenv.lib")

RunAsInteractiveUser function:

BOOL bRet;
HRESULT hr;

HANDLE processToken = NULL;
TOKEN_PRIVILEGES oldTokenPrivileges = { 0 };

HANDLE impersonationToken = NULL;
HANDLE userToken = NULL;

LPVOID pEnvironment = NULL;
PROCESS_INFORMATION processInformation = { 0 };

__try {
    bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    // This step might not be necessary because SeTcbPrivilege is enabled by default for Local System
    LUID luid;
    bRet = LookupPrivilegeValue(NULL, _T("SeTcbPrivilege"), &luid);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    TOKEN_PRIVILEGES adjTokenPrivileges = { 0 };
    adjTokenPrivileges.PrivilegeCount = 1;
    adjTokenPrivileges.Privileges[0].Luid = luid;
    adjTokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    DWORD dwOldTPLen;
    bRet = AdjustTokenPrivileges(processToken, FALSE, &adjTokenPrivileges, sizeof(TOKEN_PRIVILEGES), &oldTokenPrivileges, &dwOldTPLen);
    if (bRet) {
        hr = GetLastError();
        if (hr == ERROR_SUCCESS);
        else if (hr == ERROR_NOT_ALL_ASSIGNED) {
            // Enabled by default
        }
    }
    else {
        hr = GetLastError();
        return hr;
    }

    DWORD conSessId = WTSGetActiveConsoleSessionId();
    if (conSessId == 0xFFFFFFFF) {
        // There is no session attached to the console
        return ERROR_SUCCESS;
    }

    bRet = WTSQueryUserToken(conSessId, &impersonationToken);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    bRet = DuplicateTokenEx(impersonationToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &userToken);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    STARTUPINFO si = { 0 };
    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = _T("winsta0\\default");

    bRet = CreateEnvironmentBlock(&pEnvironment, userToken, TRUE);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    bRet = CreateProcessAsUser(userToken, _T("C:\\Windows\\notepad.exe"), NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &processInformation);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }
}
__finally {
    if (processInformation.hThread) {
        CloseHandle(processInformation.hThread);
    }
    if (processInformation.hProcess) {
        CloseHandle(processInformation.hProcess);
    }
    if (pEnvironment) {
        bRet = DestroyEnvironmentBlock(pEnvironment);
    }
    if (userToken) {
        CloseHandle(userToken);
    }
    if (impersonationToken) {
        CloseHandle(impersonationToken);
    }
    if (processToken) {
        bRet = AdjustTokenPrivileges(processToken, FALSE, &oldTokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
        CloseHandle(processToken);
    }
}

JVM Hosting

Хостинг усправляемого кода в программных продуктах уже не редкость. Oracle поддерживает выполнение байткода JVM еще с 8-й версии и периодически выполняет апгрейд виртуальной машины (11g поставляется с версией 1.5). Сравнительно недавно добавили поддержку .NET 1.1 и .NET 2.0. Microsoft SQL Server 2005 имеет поддержку CLR 2.0 и, кстати, внутренняя архитектура последнего была усовершенствована (скорее заточена) под задачи хостинга. Дальше я расскажу, как добавить возможность выполнять JVM-код в приложение, написанное на С++.

Подготовка проекта для хостинга виртуальной машины Java.

Первое, что необходимо сделать, - поставить Java Development Kit версии 6. Далее в настройках проекта прописать папку поиска заголовочных файлов JDK - "C:\Program Files\Java\jdk1.6.0_03\include\", папку с библиотечными файлами - "C:\Program Files\Java\jdk1.6.0_03\lib" и добавить путь поиска jvm.dll - C:\Program Files\Java\jdk1.6.0_03\jre\bin\client\ как написано здесь. Еще нужно закинуть файл "c:\Program Files\Java\jdk1.6.0_03\include\win32\jni_md.h" в папку с проектом и включить его в проект. Традиционно изменяем stdafx.h:

#include <jni.h>
#pragma comment(lib, "jvm.lib")

Инициализация виртуальной машины Java.

Перед использованием, виртуальную машину Java необходимо загрузить и инициализировать. По-сути, она является песочницей (sandbox) для выполняемого байт-кода, другими словами, мини-операционной системой в пределах хост-процесса. Инициализация предполагает создание управляющих структур, выделение пулов памяти и других ресурсов ОС, связанных с конкретным екземпляром JVM (да, их можно создать несколько).

jint jRet;

JavaVMOption options[1];
options[0].optionString = "-verbose:class,gc,jni";

JavaVMInitArgs jvmInitArgs;
jvmInitArgs.version = JNI_VERSION_1_6;
jvmInitArgs.nOptions = _countof(options);
jvmInitArgs.options = options;
jvmInitArgs.ignoreUnrecognized = JNI_TRUE;

JavaVM *pJvm;
JNIEnv *pEnv;
jRet = JNI_CreateJavaVM(&pJvm, reinterpret_cast<LPVOID*>(&pEnv), &jvmInitArgs);
if (jRet != JNI_OK) {
   // Это залет, солдат!
   cout << "JNI_CreateJavaVM returned " << jRet << endl;
   return(jRet);
}

Структура JavaVMInitArgs содержит настройки создания екземпляра JVM - запрашиваемую версию и различные конфигурационные параметры. Функция JNI_CreateJavaVM, как нетрудно догадаться из ее названия, создает виртуальную машину и возвращает указатель на управляющий интерфейс - JNIEnv. Любые дальнейшие манипуляции выполняются посредством него.

Выполнение байт-кода JVM.

Не буду оригинален. Вашему вниманию представляется вариант "Hello World" в исполнении C++/Java:

void printHelloWorld(JNIEnv *pEnv) {
   jclass classSystem = pEnv->FindClass("java/lang/System");
   jfieldID fieldId = pEnv->GetStaticFieldID(classSystem, "out", "Ljava/io/PrintStream;");
   jobject system_out = pEnv->GetStaticObjectField(classSystem, fieldId);

   jclass classPrintStream = pEnv->FindClass("java/io/PrintStream");
   jmethodID methodId = pEnv->GetMethodID(classPrintStream, "println", "(Ljava/lang/String;)V");

   jvalue v;
   v.l = pEnv->NewStringUTF("Hello World!");
   pEnv->CallVoidMethodA(system_out, methodId, &v);
   if (jthrowable ex = pEnv->ExceptionOccurred()) {
      pEnv->ExceptionDescribe();
      pEnv->ExceptionClear();
   }
}

Завершение работы.

По завершению работы с JVM рантайм нужно удалить:

jRet = pJvm->DestroyJavaVM();
if (jRet != JNI_OK) {
   cout << "pJvm->DestroyJavaVM() returned " << jRet << endl;
   return(jRet);
}
Copyright 2007-2011 Chabster