/** * Simple implementation of the ExternalContext and related interfaces. When embedding Orbeon Forms * (e.g. in Eclipse), this class can be used directly or can be subclassed. */ public class SimpleExternalContext implements ExternalContext { private static final Logger logger = LoggerFactory.createLogger(SimpleExternalContext.class); protected class Request implements ExternalContext.Request { protected Map<String, Object> attributesMap = new HashMap<String, Object>(); public String getContainerType() { return "simple"; } public String getContainerNamespace() { return ""; } public String getContextPath() { return ""; } public String getPathInfo() { return ""; } public String getRemoteAddr() { return "127.0.0.1"; } public Map<String, Object> getAttributesMap() { return attributesMap; } public Map<String, String> getHeaderMap() { return Collections.emptyMap(); } public Map<String, String[]> getHeaderValuesMap() { return Collections.emptyMap(); } public Map<String, Object[]> getParameterMap() { return Collections.emptyMap(); } public String getAuthType() { return "basic"; } public String getRemoteUser() { return null; } public boolean isSecure() { return false; } public boolean isUserInRole(String role) { return false; } public void sessionInvalidate() {} public String getCharacterEncoding() { return "utf-8"; } public int getContentLength() { return 0; } public String getContentType() { return ""; } public String getServerName() { return ""; } public int getServerPort() { return 0; } public String getMethod() { return "GET"; } public String getProtocol() { return "http"; } public String getRemoteHost() { return ""; } public String getScheme() { return ""; } public String getPathTranslated() { return ""; } public String getQueryString() { return ""; } public String getRequestedSessionId() { return ""; } public String getRequestPath() { return ""; } public String getRequestURI() { return ""; } public String getRequestURL() { return ""; } public String getServletPath() { return ""; } public String getClientContextPath(String urlString) { return getContextPath(); } public Reader getReader() throws IOException { return null; } public InputStream getInputStream() throws IOException { return null; } public Locale getLocale() { return null; } public Enumeration getLocales() { return null; } public boolean isRequestedSessionIdValid() { return false; } public Principal getUserPrincipal() { return null; } public Object getNativeRequest() { return SimpleExternalContext.this.getNativeRequest(); } public String getPortletMode() { return null; } public String getWindowState() { return null; } } protected class Response implements ExternalContext.Response { protected ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); protected StringWriter writer = new StringWriter(); protected String contentType; protected int status; protected Map<String, String> headers = new HashMap<String, String>(); public OutputStream getOutputStream() throws IOException { return outputStream; } public PrintWriter getWriter() throws IOException { return new PrintWriter(writer); } public boolean isCommitted() { return false; } public void reset() { outputStream.reset(); writer.getBuffer().delete(0, writer.getBuffer().length()); } public void setContentType(String contentType) { this.contentType = contentType; } public void setStatus(int status) { this.status = status; } public void setHeader(String name, String value) { headers.put(name, value); } public void addHeader(String name, String value) { headers.put(name, value); } public void sendRedirect( String pathInfo, Map parameters, boolean isServerSide, boolean isExitPortal, boolean isNoRewrite) throws IOException {} public void setContentLength(int len) {} public void sendError(int code) throws IOException {} public String getCharacterEncoding() { return "utf-8"; } public void setCaching(long lastModified, boolean revalidate, boolean allowOverride) {} public void setResourceCaching(long lastModified, long expires) {} public boolean checkIfModifiedSince(long lastModified, boolean allowOverride) { return true; } public String rewriteActionURL(String urlString) { return ""; } public String rewriteRenderURL(String urlString) { return ""; } public String rewriteActionURL(String urlString, String portletMode, String windowState) { return ""; } public String rewriteRenderURL(String urlString, String portletMode, String windowState) { return ""; } public String rewriteResourceURL(String urlString) { return ""; } public String rewriteResourceURL(String urlString, boolean generateAbsoluteURL) { return ""; } public String rewriteResourceURL(String urlString, int rewriteMode) { return ""; } public String getNamespacePrefix() { return ""; } public void setTitle(String title) {} public Object getNativeResponse() { return SimpleExternalContext.this.getNativeResponse(); } } protected class Session implements ExternalContext.Session { protected Map<String, Object> sessionAttributesMap = new HashMap<String, Object>(); public long getCreationTime() { return 0; } public String getId() { return Integer.toString(sessionAttributesMap.hashCode()); } public long getLastAccessedTime() { return 0; } public int getMaxInactiveInterval() { return 0; } public void invalidate() { sessionAttributesMap = new HashMap<String, Object>(); } public boolean isNew() { return false; } public void setMaxInactiveInterval(int interval) {} public Map<String, Object> getAttributesMap() { return sessionAttributesMap; } public Map<String, Object> getAttributesMap(int scope) { if (scope != APPLICATION_SCOPE) throw new OXFException( "Invalid session scope scope: only the application scope is allowed in Eclipse"); return getAttributesMap(); } public void addListener(SessionListener sessionListener) {} public void removeListener(SessionListener sessionListener) {} } protected class Application implements ExternalContext.Application { public void addListener(ApplicationListener applicationListener) {} public void removeListener(ApplicationListener applicationListener) {} } protected Request request = new Request(); protected Response response = new Response(); protected Session session = new Session(); protected Application application = new Application(); public Object getNativeContext() { return null; } public Object getNativeRequest() { return null; } public Object getNativeResponse() { return null; } public Object getNativeSession(boolean create) { return null; } public ExternalContext.Request getRequest() { return request; } public ExternalContext.Response getResponse() { return response; } public ExternalContext.Session getSession(boolean create) { return session; } public ExternalContext.Application getApplication() { return application; } public Map<String, Object> getAttributesMap() { return Collections.emptyMap(); } public Map<String, String> getInitAttributesMap() { return Collections.emptyMap(); } public String getRealPath(String path) { return null; } public String getStartLoggerString() { return "Running processor"; } public String getEndLoggerString() { return "Done running processor"; } public void log(String message, Throwable throwable) { logger.error(message, throwable); } public void log(String msg) { logger.info(msg); } public ExternalContext.RequestDispatcher getNamedDispatcher(String name) { return null; } public ExternalContext.RequestDispatcher getRequestDispatcher( String path, boolean isContextRelative) { return null; } public String rewriteServiceURL(String urlString, boolean forceAbsolute) { return URLRewriterUtils.rewriteServiceURL(getRequest(), urlString, forceAbsolute); } }
/** * This processor handles XForms initialization and produces an XHTML document which is a * translation from the source XForms + XHTML. */ public class XFormsToXHTML extends ProcessorImpl { public static final String LOGGING_CATEGORY = "html"; private static final Logger logger = LoggerFactory.createLogger(XFormsToXHTML.class); private static final String INPUT_ANNOTATED_DOCUMENT = "annotated-document"; private static final String OUTPUT_DOCUMENT = "document"; public XFormsToXHTML() { addInputInfo(new ProcessorInputOutputInfo(INPUT_ANNOTATED_DOCUMENT)); addInputInfo( new ProcessorInputOutputInfo( "namespace")); // This input ensures that we depend on a portlet namespace addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DOCUMENT)); } /** Case where an XML response must be generated. */ @Override public ProcessorOutput createOutput(final String outputName) { final ProcessorOutput output = new URIProcessorOutputImpl(XFormsToXHTML.this, outputName, INPUT_ANNOTATED_DOCUMENT) { public void readImpl(final PipelineContext pipelineContext, XMLReceiver xmlReceiver) { doIt(pipelineContext, xmlReceiver, this, outputName); } @Override protected boolean supportsLocalKeyValidity() { return true; } @Override public KeyValidity getLocalKeyValidity( PipelineContext pipelineContext, URIReferences uriReferences) { // NOTE: As of 2010-03, caching of the output should never happen // o more work is needed to make this work properly // o not many use cases benefit return null; } }; addOutput(outputName, output); return output; } @Override public ProcessorInput createInput(final String inputName) { if (inputName.equals(INPUT_ANNOTATED_DOCUMENT)) { // Insert processor on the fly to handle dependencies. This is a bit tricky: we used to have // an // XSLT/XInclude before XFormsToXHTML. This step handled XBL dependencies. Now that it is // removed, we // need a mechanism to detect dependencies. So we insert a step here. // Return an input which handles dependencies // The system actually has two processors: // o stage1 is the processor automatically inserted below for the purpose of handling // dependencies // o stage2 is the actual oxf:xforms-to-xhtml which actually does XForms processing final ProcessorInput originalInput = super.createInput(inputName); return new DependenciesProcessorInput(XFormsToXHTML.this, inputName, originalInput) { @Override protected URIProcessorOutputImpl.URIReferences getURIReferences( PipelineContext pipelineContext) { // Return dependencies object, set by stage2 before reading its input return ((Stage2TransientState) XFormsToXHTML.this.getState(pipelineContext)) .stage1CacheableState; } }; } else { return super.createInput(inputName); } } @Override public void reset(PipelineContext context) { setState(context, new Stage2TransientState()); } // State passed by the second stage to the first stage. // NOTE: This extends URIReferencesState because we use URIProcessorOutputImpl. // It is not clear that we absolutely need URIProcessorOutputImpl in the second stage, but right // now we keep it, // because XFormsURIResolver requires URIProcessorOutputImpl. private class Stage2TransientState extends URIProcessorOutputImpl.URIReferencesState { public Stage1CacheableState stage1CacheableState; } private static final boolean DO_TEST_STATE = false; private static Stage2CacheableState TEST_STATE; private void doIt( final PipelineContext pipelineContext, XMLReceiver xmlReceiver, final URIProcessorOutputImpl processorOutput, String outputName) { final ExternalContext externalContext = XFormsUtils.getExternalContext(pipelineContext); final IndentedLogger indentedLogger = XFormsContainingDocument.getIndentedLogger( XFormsToXHTML.logger, XFormsServer.getLogger(), LOGGING_CATEGORY); // ContainingDocument and XFormsState created below final XFormsContainingDocument[] containingDocument = new XFormsContainingDocument[1]; final boolean[] cachedStatus = new boolean[] {false}; final Stage2CacheableState stage2CacheableState; if (TEST_STATE == null) { // Read and try to cache the complete XForms+XHTML document with annotations stage2CacheableState = (Stage2CacheableState) readCacheInputAsObject( pipelineContext, getInputByName(INPUT_ANNOTATED_DOCUMENT), new CacheableInputReader() { public Object read( PipelineContext pipelineContext, ProcessorInput processorInput) { // Compute annotated XForms document + static state document final Stage1CacheableState stage1CacheableState = new Stage1CacheableState(); final Stage2CacheableState stage2CacheableState; final XFormsStaticState[] staticState = new XFormsStaticState[1]; { // Store dependencies container in state before reading ((Stage2TransientState) XFormsToXHTML.this.getState(pipelineContext)) .stage1CacheableState = stage1CacheableState; // Read static state from input stage2CacheableState = readStaticState( pipelineContext, externalContext, indentedLogger, staticState); } // Create containing document and initialize XForms engine // NOTE: Create document here so we can do appropriate analysis of caching // dependencies final XFormsURIResolver uriResolver = new XFormsURIResolver( XFormsToXHTML.this, processorOutput, pipelineContext, INPUT_ANNOTATED_DOCUMENT, URLGenerator.DEFAULT_HANDLE_XINCLUDE); containingDocument[0] = new XFormsContainingDocument( pipelineContext, staticState[0], stage2CacheableState.getAnnotatedTemplate(), uriResolver); // Gather set caching dependencies gatherInputDependencies( pipelineContext, containingDocument[0], indentedLogger, stage1CacheableState); return stage2CacheableState; } @Override public void foundInCache() { cachedStatus[0] = true; } }, false); TEST_STATE = DO_TEST_STATE ? stage2CacheableState : null; } else { stage2CacheableState = TEST_STATE; } try { // Create containing document if not done yet if (containingDocument[0] == null) { assert cachedStatus[0]; // In this case, we found the static state digest and more in the cache, but we must now // create a new XFormsContainingDocument from this information indentedLogger.logDebug( "", "annotated document and static state digest obtained from cache", "digest", stage2CacheableState.getStaticStateDigest()); final XFormsStaticState staticState; { final XFormsStaticState cachedState = XFormsStaticStateCache.instance() .getDocument(pipelineContext, stage2CacheableState.getStaticStateDigest()); if (cachedState != null && cachedState.getMetadata().checkBindingsIncludes()) { // Found static state in cache indentedLogger.logDebug("", "found up-to-date static state by digest in cache"); staticState = cachedState; } else { // Not found static state in cache OR it is out of date, create static state from input // NOTE: In out of date case, could clone static state and reprocess instead? if (cachedState != null) indentedLogger.logDebug("", "found out-of-date static state by digest in cache"); else indentedLogger.logDebug("", "did not find static state by digest in cache"); final StaticStateBits staticStateBits = new StaticStateBits( pipelineContext, externalContext, indentedLogger, stage2CacheableState.getStaticStateDigest()); staticState = new XFormsStaticState( pipelineContext, staticStateBits.staticStateDocument, stage2CacheableState.getStaticStateDigest(), staticStateBits.metadata); // Store in cache XFormsStaticStateCache.instance().storeDocument(pipelineContext, staticState); } } final XFormsURIResolver uriResolver = new XFormsURIResolver( XFormsToXHTML.this, processorOutput, pipelineContext, INPUT_ANNOTATED_DOCUMENT, URLGenerator.DEFAULT_HANDLE_XINCLUDE); containingDocument[0] = new XFormsContainingDocument( pipelineContext, staticState, stage2CacheableState.getAnnotatedTemplate(), uriResolver); } else { assert !cachedStatus[0]; indentedLogger.logDebug( "", "annotated document and static state digest not obtained from cache."); } // Output resulting document if (outputName.equals("document")) { // Normal case where we output XHTML outputResponseDocument( pipelineContext, externalContext, indentedLogger, stage2CacheableState.getAnnotatedTemplate(), containingDocument[0], xmlReceiver); } else { // Output in test mode testOutputResponseState( pipelineContext, containingDocument[0], indentedLogger, xmlReceiver); } // Notify state manager XFormsStateManager.instance().afterInitialResponse(pipelineContext, containingDocument[0]); } catch (Throwable e) { indentedLogger.logDebug("", "throwable caught during initialization."); throw new OXFException(e); } } private Stage2CacheableState readStaticState( PipelineContext pipelineContext, ExternalContext externalContext, IndentedLogger indentedLogger, XFormsStaticState[] staticState) { final StaticStateBits staticStateBits = new StaticStateBits(pipelineContext, externalContext, indentedLogger, null); { final XFormsStaticState cachedState = XFormsStaticStateCache.instance() .getDocument(pipelineContext, staticStateBits.staticStateDigest); if (cachedState != null && cachedState.getMetadata().checkBindingsIncludes()) { // Found static state in cache indentedLogger.logDebug("", "found up-to-date static state by digest in cache"); staticState[0] = cachedState; } else { // Not found static state in cache OR it is out of date, create and initialize static state // object // NOTE: In out of date case, could clone static state and reprocess instead? if (cachedState != null) indentedLogger.logDebug("", "found out-of-date static state by digest in cache"); else indentedLogger.logDebug("", "did not find static state by digest in cache"); staticState[0] = new XFormsStaticState( pipelineContext, staticStateBits.staticStateDocument, staticStateBits.staticStateDigest, staticStateBits.metadata); // Store in cache XFormsStaticStateCache.instance().storeDocument(pipelineContext, staticState[0]); } } // Update input dependencies object return new Stage2CacheableState( staticStateBits.annotatedTemplate, staticStateBits.staticStateDigest); } private class StaticStateBits { private final boolean isLogStaticStateInput = XFormsProperties.getDebugLogging().contains("html-static-state"); public final XFormsStaticState.Metadata metadata = new XFormsStaticState.Metadata(); public final SAXStore annotatedTemplate = new SAXStore(); public final Document staticStateDocument; public final String staticStateDigest; public StaticStateBits( PipelineContext pipelineContext, ExternalContext externalContext, IndentedLogger indentedLogger, String existingStaticStateDigest) { final boolean computeDigest = isLogStaticStateInput || existingStaticStateDigest == null; indentedLogger.startHandleOperation( "", "reading input", "existing digest", existingStaticStateDigest); final TransformerXMLReceiver documentReceiver = TransformerUtils.getIdentityTransformerHandler(); final LocationDocumentResult documentResult = new LocationDocumentResult(); documentReceiver.setResult(documentResult); final XMLUtils.DigestContentHandler digestReceiver = computeDigest ? new XMLUtils.DigestContentHandler("MD5") : null; final XMLReceiver extractorOutput; if (isLogStaticStateInput) { extractorOutput = computeDigest ? new TeeXMLReceiver( documentReceiver, digestReceiver, getDebugReceiver(indentedLogger)) : new TeeXMLReceiver(documentReceiver, getDebugReceiver(indentedLogger)); } else { extractorOutput = computeDigest ? new TeeXMLReceiver(documentReceiver, digestReceiver) : documentReceiver; } // Read the input through the annotator and gather namespace mappings // // Output of annotator is: // // o annotated page template (TODO: this should not include model elements) // o extractor // // Output of extractor is: // // o static state document // o optionally: digest // o optionally: debug output // readInputAsSAX( pipelineContext, INPUT_ANNOTATED_DOCUMENT, new XFormsAnnotatorContentHandler( annotatedTemplate, new XFormsExtractorContentHandler(extractorOutput, metadata), metadata)); this.staticStateDocument = documentResult.getDocument(); this.staticStateDigest = computeDigest ? NumberUtils.toHexString(digestReceiver.getResult()) : null; assert !isLogStaticStateInput || existingStaticStateDigest == null || this.staticStateDigest.equals(existingStaticStateDigest); indentedLogger.endHandleOperation("computed digest", this.staticStateDigest); } private XMLReceiver getDebugReceiver(final IndentedLogger indentedLogger) { final TransformerXMLReceiver identity = TransformerUtils.getIdentityTransformerHandler(); final StringBuilderWriter writer = new StringBuilderWriter(); identity.setResult(new StreamResult(writer)); return new ForwardingXMLReceiver(identity) { @Override public void endDocument() throws SAXException { super.endDocument(); // Log out at end of document indentedLogger.logDebug("", "static state input", "input", writer.toString()); } }; } } // What can be cached by the first stage: URI dependencies private static class Stage1CacheableState extends URIProcessorOutputImpl.URIReferences {} // What can be cached by the second stage: SAXStore and static state private static class Stage2CacheableState extends URIProcessorOutputImpl.URIReferences { private final SAXStore annotatedTemplate; private final String staticStateDigest; public Stage2CacheableState(SAXStore annotatedTemplate, String staticStateDigest) { this.annotatedTemplate = annotatedTemplate; this.staticStateDigest = staticStateDigest; } public SAXStore getAnnotatedTemplate() { return annotatedTemplate; } public String getStaticStateDigest() { return staticStateDigest; } } private void gatherInputDependencies( PipelineContext pipelineContext, XFormsContainingDocument containingDocument, IndentedLogger indentedLogger, Stage1CacheableState stage1CacheableState) { final String forwardSubmissionHeaders = XFormsProperties.getForwardSubmissionHeaders(containingDocument); // Add static instance source dependencies for top-level models // TODO: check all models/instances final XFormsStaticState staticState = containingDocument.getStaticState(); for (final Model model : staticState.getModelsForScope(staticState.getXBLBindings().getTopLevelScope())) { for (final Instance instance : model.instancesMap().values()) { if (instance.dependencyURL() != null) { final String resolvedDependencyURL = XFormsUtils.resolveServiceURL( pipelineContext, containingDocument, instance.element(), instance.dependencyURL(), ExternalContext.Response.REWRITE_MODE_ABSOLUTE); if (!instance.isCacheHint()) { stage1CacheableState.addReference( null, resolvedDependencyURL, instance.xxformsUsername(), instance.xxformsPassword(), instance.xxformsPassword(), forwardSubmissionHeaders); if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug( "", "adding document cache dependency for non-cacheable instance", "instance URI", resolvedDependencyURL); } else { // Don't add the dependency as we don't want the instance URI to be hit // For all practical purposes, globally shared instances must remain constant! if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug( "", "not adding document cache dependency for cacheable instance", "instance URI", resolvedDependencyURL); } } } } // Set caching dependencies if the input was actually read // TODO: check all models/instances // Q: should use static dependency information instead? what about schema imports and instance // replacements? for (final XFormsModel currentModel : containingDocument.getModels()) { // Add schema dependencies final String[] schemaURIs = currentModel.getSchemaURIs(); // TODO: We should also use dependencies computed in XFormsModelSchemaValidator.SchemaInfo if (schemaURIs != null) { for (final String currentSchemaURI : schemaURIs) { if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug( "", "adding document cache dependency for schema", "schema URI", currentSchemaURI); stage1CacheableState.addReference( null, currentSchemaURI, null, null, null, forwardSubmissionHeaders); // TODO: support username / password on schema refs } } } // TODO: Add @src attributes from controls? Not used often. // Set caching dependencies for XBL inclusions { final XFormsStaticState.Metadata metadata = containingDocument.getStaticState().getMetadata(); final Set<String> includes = metadata.getBindingsIncludes(); if (includes != null) { for (final String include : includes) { stage1CacheableState.addReference(null, "oxf:" + include, null, null, null, null); } } } } public static void outputResponseDocument( final PipelineContext pipelineContext, final ExternalContext externalContext, final IndentedLogger indentedLogger, final SAXStore annotatedDocument, final XFormsContainingDocument containingDocument, final XMLReceiver xmlReceiver) throws SAXException, IOException { final List<XFormsContainingDocument.Load> loads = containingDocument.getLoadsToRun(); if (containingDocument.isGotSubmissionReplaceAll()) { // 1. Got a submission with replace="all" // NOP: Response already sent out by a submission // TODO: modify XFormsModelSubmission accordingly indentedLogger.logDebug("", "handling response for submission with replace=\"all\""); } else if (loads != null && loads.size() > 0) { // 2. Got at least one xforms:load // Send redirect out // Get first load only final XFormsContainingDocument.Load load = loads.get(0); // Send redirect final String redirectResource = load.getResource(); indentedLogger.logDebug( "", "handling redirect response for xforms:load", "url", redirectResource); // Set isNoRewrite to true, because the resource is either a relative path or already contains // the servlet context externalContext.getResponse().sendRedirect(redirectResource, null, false, false, true); // Still send out a null document to signal that no further processing must take place XMLUtils.streamNullDocument(xmlReceiver); } else { // 3. Regular case: produce an XHTML document out final ElementHandlerController controller = new ElementHandlerController(); // Register handlers on controller (the other handlers are registered by the body handler) { controller.registerHandler( XHTMLHeadHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "head"); controller.registerHandler( XHTMLBodyHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "body"); // Register a handler for AVTs on HTML elements final boolean hostLanguageAVTs = XFormsProperties .isHostLanguageAVTs(); // TODO: this should be obtained per document, but we only // know about this in the extractor if (hostLanguageAVTs) { controller.registerHandler( XXFormsAttributeHandler.class.getName(), XFormsConstants.XXFORMS_NAMESPACE_URI, "attribute"); controller.registerHandler( XHTMLElementHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI); } // Swallow XForms elements that are unknown controller.registerHandler( NullHandler.class.getName(), XFormsConstants.XFORMS_NAMESPACE_URI); controller.registerHandler( NullHandler.class.getName(), XFormsConstants.XXFORMS_NAMESPACE_URI); controller.registerHandler(NullHandler.class.getName(), XFormsConstants.XBL_NAMESPACE_URI); } // Set final output controller.setOutput(new DeferredXMLReceiverImpl(xmlReceiver)); // Set handler context controller.setElementHandlerContext( new HandlerContext( controller, pipelineContext, containingDocument, externalContext, null)); // Process the entire input annotatedDocument.replay( new ExceptionWrapperXMLReceiver(controller, "converting XHTML+XForms document to XHTML")); } containingDocument.afterInitialResponse(); } private void testOutputResponseState( final PipelineContext pipelineContext, final XFormsContainingDocument containingDocument, final IndentedLogger indentedLogger, final XMLReceiver xmlReceiver) throws SAXException { // Output XML response XFormsServer.outputAjaxResponse( containingDocument, indentedLogger, null, pipelineContext, null, xmlReceiver, false, true); } }
/** XHTML to PDF converter using the Flying Saucer library. */ public class XHTMLToPDFProcessor extends HttpBinarySerializer { // TODO: HttpBinarySerializer is supposedly deprecated private static final Logger logger = LoggerFactory.createLogger(XHTMLToPDFProcessor.class); public static String DEFAULT_CONTENT_TYPE = "application/pdf"; public XHTMLToPDFProcessor() { addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA)); } protected String getDefaultContentType() { return DEFAULT_CONTENT_TYPE; } protected void readInput( final PipelineContext pipelineContext, final ProcessorInput input, Config config, OutputStream outputStream) { final ExternalContext externalContext = NetUtils.getExternalContext(); // Read the input as a DOM final Document domDocument = readInputAsDOM(pipelineContext, input); // Create renderer and add our own callback final float DEFAULT_DOTS_PER_POINT = 20f * 4f / 3f; final int DEFAULT_DOTS_PER_PIXEL = 14; final ITextRenderer renderer = new ITextRenderer(DEFAULT_DOTS_PER_POINT, DEFAULT_DOTS_PER_PIXEL); // Embed fonts if needed, based on configuration properties embedFonts(renderer); try { final ITextUserAgent callback = new ITextUserAgent(renderer.getOutputDevice()) { // Called for: // // - CSS URLs // - image URLs // - link clicked / form submission (not relevant for our usage) // - resolveAndOpenStream below public String resolveURI(String uri) { // Our own resolver // All resources we care about here are resource URLs. The PDF pipeline makes sure // that the servlet // URL rewriter processes the XHTML output to rewrite resource URLs to absolute paths, // including // the servlet context and version number if needed. In addition, CSS resources must // either use // relative paths when pointing to other CSS files or images, or go through the XForms // CSS rewriter, // which also generates absolute paths. // So all we need to do here is rewrite the resulting path to an absolute URL. // NOTE: We used to call rewriteResourceURL() here as the PDF pipeline did not do URL // rewriting. // However this caused issues, for example resources like background images referred // by CSS files // could be rewritten twice: once by the XForms resource rewriter, and a second time // here. return URLRewriterUtils.rewriteServiceURL( NetUtils.getExternalContext().getRequest(), uri, ExternalContext.Response.REWRITE_MODE_ABSOLUTE | ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_NO_CONTEXT); } // Called by: // // - getCSSResource // - getImageResource below // - getBinaryResource (not sure when called) // - getXMLResource (not sure when called) protected InputStream resolveAndOpenStream(String uri) { final String resolvedURI = resolveURI(uri); // TODO: Use xforms:submission code instead // Tell callee we are loading that we are a servlet environment, as in effect we act // like // a browser retrieving resources directly, not like a portlet. This is the case also // if we are // called by the proxy portlet or if we are directly within a portlet. final Map<String, String[]> headers = new HashMap<String, String[]>(); headers.put("Orbeon-Client", new String[] {"servlet"}); final ConnectionResult connectionResult = new Connection() .open( externalContext, new IndentedLogger(logger, ""), false, Connection.Method.GET.name(), URLFactory.createURL(resolvedURI), null, null, null, headers, Connection.getForwardHeaders()); if (connectionResult.statusCode != 200) { connectionResult.close(); throw new OXFException( "Got invalid return code while loading resource: " + uri + ", " + connectionResult.statusCode); } pipelineContext.addContextListener( new PipelineContext.ContextListener() { public void contextDestroyed(boolean success) { connectionResult.close(); } }); return connectionResult.getResponseInputStream(); } public ImageResource getImageResource(String uri) { final InputStream is = resolveAndOpenStream(uri); final String localURI = NetUtils.inputStreamToAnyURI(is, NetUtils.REQUEST_SCOPE); return super.getImageResource(localURI); } }; callback.setSharedContext(renderer.getSharedContext()); renderer.getSharedContext().setUserAgentCallback(callback); // renderer.getSharedContext().setDPI(150); // Set the document to process renderer.setDocument( domDocument, // No base URL if can't get request URL from context externalContext.getRequest() == null ? null : externalContext.getRequest().getRequestURL()); // Do the layout and create the resulting PDF renderer.layout(); final List pages = renderer.getRootBox().getLayer().getPages(); try { // Page count might be zero, and if so createPDF if (pages != null && pages.size() > 0) { renderer.createPDF(outputStream); } else { // TODO: log? } } catch (Exception e) { throw new OXFException(e); } finally { try { outputStream.close(); } catch (IOException e) { // NOP // TODO: log? } } } finally { // Free resources associated with the rendering context renderer.getSharedContext().reset(); } } public static void embedFonts(ITextRenderer renderer) { final PropertySet propertySet = Properties.instance().getPropertySet(); for (final String propertyName : propertySet.getPropertiesStartsWith("oxf.fr.pdf.font.path")) { final String path = StringUtils.trimToNull(propertySet.getString(propertyName)); if (path != null) { try { // Overriding the font family is optional final String family; { final String[] tokens = StringUtils.split(propertyName, '.'); if (tokens.length >= 6) { final String id = tokens[5]; family = StringUtils.trimToNull( propertySet.getString("oxf.fr.pdf.font.family" + '.' + id)); } else { family = null; } } // Add the font renderer .getFontResolver() .addFont(path, family, BaseFont.IDENTITY_H, BaseFont.EMBEDDED, null); } catch (Exception e) { logger.warn( "Failed to load font by path: '" + path + "' specified with property '" + propertyName + "'"); } } } } }
/** XForms events definitions. */ public class XFormsEvents { public static final String LOGGING_CATEGORY = "event"; public static final Logger logger = LoggerFactory.createLogger(XFormsEvents.class); // Custom initialization events public static final String XXFORMS_ALL_EVENTS_REQUIRED = "xxforms-all-events-required"; public static final String XXFORMS_READY = "xxforms-ready"; // Other custom events public static final String XXFORMS_SESSION_HEARTBEAT = "xxforms-session-heartbeat"; public static final String XXFORMS_SUBMIT = "xxforms-submit"; public static final String XXFORMS_SUBMIT_REPLACE = "xxforms-submit-replace"; public static final String XXFORMS_LOAD = "xxforms-load"; public static final String XXFORMS_REPEAT_FOCUS = "xxforms-repeat-focus"; public static final String XXFORMS_OFFLINE = "xxforms-offline"; public static final String XXFORMS_ONLINE = "xxforms-online"; public static final String XXFORMS_DIALOG_CLOSE = "xxforms-dialog-close"; public static final String XXFORMS_DIALOG_OPEN = "xxforms-dialog-open"; public static final String XXFORMS_INSTANCE_INVALIDATE = "xxforms-instance-invalidate"; public static final String XXFORMS_DND = "xxforms-dnd"; public static final String XXFORMS_VALID = "xxforms-valid"; public static final String XXFORMS_INVALID = "xxforms-invalid"; public static final String XXFORMS_VALUE_CHANGE_WITH_FOCUS_CHANGE = "xxforms-value-change-with-focus-change"; public static final String XXFORMS_VALUE_OR_ACTIVATE = "xxforms-value-or-activate"; public static final String XXFORMS_VALUE_CHANGED = "xxforms-value-changed"; public static final String XXFORMS_NODESET_CHANGED = "xxforms-nodeset-changed"; // Standard XForms events public static final String XFORMS_MODEL_CONSTRUCT = "xforms-model-construct"; public static final String XFORMS_MODEL_CONSTRUCT_DONE = "xforms-model-construct-done"; public static final String XFORMS_READY = "xforms-ready"; public static final String XFORMS_MODEL_DESTRUCT = "xforms-model-destruct"; public static final String XFORMS_REBUILD = "xforms-rebuild"; public static final String XFORMS_RECALCULATE = "xforms-recalculate"; public static final String XFORMS_REVALIDATE = "xforms-revalidate"; public static final String XFORMS_REFRESH = "xforms-refresh"; public static final String XFORMS_RESET = "xforms-reset"; public static final String XFORMS_SUBMIT = "xforms-submit"; public static final String XFORMS_SUBMIT_SERIALIZE = "xforms-submit-serialize"; public static final String XFORMS_SUBMIT_DONE = "xforms-submit-done"; public static final String XFORMS_VALUE_CHANGED = "xforms-value-changed"; public static final String XFORMS_VALID = "xforms-valid"; public static final String XFORMS_INVALID = "xforms-invalid"; public static final String XFORMS_REQUIRED = "xforms-required"; public static final String XFORMS_OPTIONAL = "xforms-optional"; public static final String XFORMS_READWRITE = "xforms-readwrite"; public static final String XFORMS_READONLY = "xforms-readonly"; public static final String XFORMS_ENABLED = "xforms-enabled"; public static final String XFORMS_DISABLED = "xforms-disabled"; public static final String XFORMS_DESELECT = "xforms-deselect"; public static final String XFORMS_SELECT = "xforms-select"; public static final String XFORMS_INSERT = "xforms-insert"; public static final String XFORMS_DELETE = "xforms-delete"; public static final String XFORMS_FOCUS = "xforms-focus"; public static final String XFORMS_SCROLL_FIRST = "xforms-scroll-first"; public static final String XFORMS_SCROLL_LAST = "xforms-scroll-last"; public static final String XFORMS_HELP = "xforms-help"; public static final String XFORMS_HINT = "xforms-hint"; // DOM events public static final String XFORMS_DOM_ACTIVATE = "DOMActivate"; public static final String XFORMS_DOM_FOCUS_OUT = "DOMFocusOut"; public static final String XFORMS_DOM_FOCUS_IN = "DOMFocusIn"; // Exceptions and errors public static final String XFORMS_LINK_EXCEPTION = "xforms-link-exception"; public static final String XFORMS_LINK_ERROR = "xforms-link-error"; public static final String XFORMS_COMPUTE_EXCEPTION = "xforms-compute-exception"; public static final String XFORMS_SUBMIT_ERROR = "xforms-submit-error"; public static final String XFORMS_BINDING_EXCEPTION = "xforms-binding-exception"; private XFormsEvents() {} }