001package io.jstach.opt.dropwizard; 002 003import java.io.IOException; 004import java.io.OutputStream; 005import java.util.LinkedHashMap; 006import java.util.Locale; 007import java.util.Map; 008import java.util.ServiceLoader; 009 010import org.eclipse.jdt.annotation.Nullable; 011 012import io.dropwizard.views.common.View; 013import io.dropwizard.views.common.ViewRenderException; 014import io.dropwizard.views.common.ViewRenderer; 015import io.jstach.jstachio.JStachio; 016import io.jstach.jstachio.Output.EncodedOutput; 017import io.jstach.jstachio.spi.JStachioConfig; 018import io.jstach.jstachio.spi.JStachioExtension; 019import io.jstach.jstachio.spi.JStachioFactory; 020 021/** 022 * 023 * Dropwizard view support. Doing the below will automatically pick up JStachio 024 * <pre><code class="language-java"> 025 * bootstrap.addBundle(new ViewBundle<>()); 026 * </code> </pre> Or alternatively you can pass it directly: 027 * <pre><code class="language-java"> 028 * JStachio jstachio = ...; // See JStachioFactory 029 * bootstrap.addBundle(new ViewBundle<>(new JStachioViewRenderer(jstachio))); 030 * </code> </pre> 031 * 032 * @author agentgt 033 */ 034@SuppressWarnings("exports") 035public class JStachioViewRenderer implements ViewRenderer { 036 037 private @Nullable JStachio jstachio = null; 038 039 /** 040 * ServiceLoader will call this 041 */ 042 public JStachioViewRenderer() { 043 // Empty on purpose (placate sonar) 044 } 045 046 /** 047 * Programmatically create the renderer with the given jstachio. 048 * @param jstachio a jstachio instance. 049 * @see JStachioFactory 050 */ 051 public JStachioViewRenderer(JStachio jstachio) { 052 this.jstachio = jstachio; 053 } 054 055 @Override 056 public boolean isRenderable(View view) { 057 return view instanceof JStachioView; 058 } 059 060 @Override 061 public void render(View view, Locale locale, OutputStream output) throws IOException { 062 if (view instanceof JStachioView jv) { 063 try (var out = EncodedOutput.of(output, jv.charset())) { 064 jstachio().write(jv.model(), out); 065 } 066 } 067 else { 068 throw new ViewRenderException("Not a JStachioView: " + view); 069 } 070 } 071 072 /** 073 * Internal getter for the jstachio backing this renderer 074 * @return not null 075 * @throws NullPointerException if jstachio has not been set yet. 076 */ 077 protected JStachio jstachio() { 078 var j = jstachio; 079 if (j == null) { 080 throw new NullPointerException("JStachio has not been set!"); 081 } 082 return j; 083 } 084 085 @Override 086 public void configure(Map<String, String> options) { 087 if (jstachio != null) { 088 return; 089 } 090 if (options.isEmpty()) { 091 jstachio = JStachio.of(); 092 } 093 else { 094 Map<String, String> resolved = new LinkedHashMap<>(); 095 for (var e : options.entrySet()) { 096 if (e.getKey() != null && e.getValue() != null) { 097 resolved.put(getConfigurationKey() + "." + e.getKey(), e.getValue()); 098 } 099 } 100 resolved = Map.copyOf(resolved); 101 JStachioConfig dropWizardConfig = resolved::get; 102 /* 103 * Hmm I wonder if we should add system properties here 104 */ 105 var j = jstachio = JStachioFactory.builder() // 106 .add(dropWizardConfig) // 107 .add(ServiceLoader.load(JStachioExtension.class)) // 108 .build(); 109 110 JStachio.setStatic(() -> j); 111 } 112 } 113 114 @Override 115 public final String getConfigurationKey() { 116 return "jstachio"; 117 } 118 119}