001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2017 MicroBean.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.helm.chart;
018
019import java.io.IOException;
020
021import java.util.Objects;
022
023import com.google.protobuf.AnyOrBuilder;
024import com.google.protobuf.ByteString;
025
026import hapi.chart.ChartOuterClass.ChartOrBuilder;
027import hapi.chart.ConfigOuterClass.ConfigOrBuilder;
028import hapi.chart.MetadataOuterClass.MetadataOrBuilder;
029import hapi.chart.TemplateOuterClass.TemplateOrBuilder;
030
031/**
032 * A partial {@link AbstractChartWriter} whose implementations save
033 * {@link ChartOrBuilder} objects to a destination that can
034 * be considered an archive of some sort.
035 *
036 * @author <a href="https://about.me/lairdnelson"
037 * target="_parent">Laird Nelson</a>
038 */
039public abstract class AbstractArchiveChartWriter extends AbstractChartWriter {
040
041
042  /*
043   * Constructors.
044   */
045
046
047  /**
048   * Creates a new {@link AbstractArchiveChartWriter}.
049   */
050  protected AbstractArchiveChartWriter() {
051    super();
052  }
053
054
055  /*
056   * Instance methods.
057   */
058
059
060  /**
061   * {@inheritDoc}
062   *
063   * <p>The {@link AbstractArchiveChartWriter} implementation stores a
064   * {@link String} representing the required path layout under a
065   * "{@code path}" key in the supplied {@link Context}.</p>
066   */
067  @Override
068  protected void beginWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
069    Objects.requireNonNull(context);
070    Objects.requireNonNull(chartBuilder);
071    if (parent == chartBuilder) {
072      throw new IllegalArgumentException("parent == chartBuilder");
073    }
074    final MetadataOrBuilder metadata = chartBuilder.getMetadataOrBuilder();
075    if (metadata == null) {
076      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata() == null"));
077    }
078    final String chartName = metadata.getName();
079    if (chartName == null) {
080      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata().getName() == null"));
081    }
082    
083    if (parent == null) {
084      context.put("path", new StringBuilder(chartName).append("/").toString());
085    } else {
086      final MetadataOrBuilder parentMetadata = parent.getMetadataOrBuilder();
087      if (parentMetadata == null) {
088        throw new IllegalArgumentException("parent", new IllegalStateException("parent.getMetadata() == null"));
089      }
090      final String parentChartName = parentMetadata.getName();
091      if (parentChartName == null) {
092        throw new IllegalArgumentException("parent", new IllegalStateException("parent.getMetadata().getName() == null"));
093      }      
094      context.put("path", new StringBuilder(context.get("path", String.class)).append("charts/").append(chartName).append("/").toString());
095    }
096  }
097
098  /**
099   * {@inheritDoc}
100   *
101   * <p>The {@link AbstractArchiveChartWriter} implementation writes
102   * the {@linkplain #toYAML(Context, Object) YAML representation} of
103   * the supplied {@link MetadataOrBuilder} to an appropriate archive
104   * entry named {@code Chart.yaml} within the current chart path.</p>
105   *
106   * @exception NullPointerException if either {@code context} or
107   * {@code metadata} is {@code null}
108   */
109  @Override
110  protected void writeMetadata(final Context context, final MetadataOrBuilder metadata) throws IOException {
111    Objects.requireNonNull(context);
112    Objects.requireNonNull(metadata);
113
114    final String yaml = this.toYAML(context, metadata);
115    this.writeEntry(context, "Chart.yaml", yaml);
116  }
117
118  /**
119   * {@inheritDoc}
120   *
121   * <p>This implementation writes the {@linkplain #toYAML(Context,
122   * Object) YAML representation} of the supplied {@link
123   * ConfigOrBuilder} to an appropriate archive entry named {@code
124   * values.yaml} within the current chart path.</p>
125   *
126   * @exception NullPointerException if {@code context} is {@code
127   * null}
128   */
129  @Override
130  protected void writeConfig(final Context context, final ConfigOrBuilder config) throws IOException {
131    Objects.requireNonNull(context);
132    
133    if (config != null) {
134      final String yaml = this.toYAML(context, config);
135      this.writeEntry(context, "values.yaml", yaml);
136    }
137  }
138
139  /**
140   * {@inheritDoc}
141   *
142   * <p>This implementation writes the {@linkplain
143   * TemplateOrBuilder#getData() data} of the supplied {@link
144   * TemplateOrBuilder} to an appropriate archive entry named in part
145   * by the return value of the {@link TemplateOrBuilder#getName()}
146   * method within the current chart path.</p>
147   *
148   * @exception NullPointerException if {@code context} is {@code
149   * null}
150   */
151  @Override
152  protected void writeTemplate(final Context context, final TemplateOrBuilder template) throws IOException {
153    Objects.requireNonNull(context);
154    
155    if (template != null) {
156      final String templateName = template.getName();
157      if (templateName != null && !templateName.isEmpty()) {
158        final ByteString data = template.getData();
159        if (data != null && data.size() > 0) {
160          final String dataString = data.toStringUtf8();
161          assert dataString != null;
162          assert !dataString.isEmpty();
163          this.writeEntry(context, templateName, dataString);
164        }
165      }
166    }
167  }
168
169  /**
170   * {@inheritDoc}
171   *
172   * <p>This implementation writes the {@linkplain
173   * AnyOrBuilder#getValue() contents} of the supplied {@link
174   * AnyOrBuilder} to an appropriate archive entry named in part by
175   * the return value of the {@link AnyOrBuilder#getTypeUrl()} method
176   * within the current chart path.</p>
177   *
178   * @exception NullPointerException if {@code context} is {@code
179   * null}
180   */
181  @Override
182  protected void writeFile(final Context context, final AnyOrBuilder file) throws IOException {
183    Objects.requireNonNull(context);
184    
185    if (file != null) {
186      final String fileName = file.getTypeUrl();
187      if (fileName != null && !fileName.isEmpty()) {
188        final ByteString data = file.getValue();
189        if (data != null && data.size() > 0) {
190          final String dataString = data.toStringUtf8();
191          assert dataString != null;
192          assert !dataString.isEmpty();
193          this.writeEntry(context, fileName, dataString);
194        }
195      }
196    }
197  }
198
199  /**
200   * {@inheritDoc}
201   *
202   * <p>This implementation ensures that the current chart path,
203   * residing under the "{@code path}" key in the supplied {@link
204   * AbstractChartWriter.Context}, is reset properly.</p>
205   *
206   * @exception NullPointerException if either {@code context} or
207   * {@code chartBuilder} is {@code null}
208   */
209  @Override
210  protected void endWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
211    Objects.requireNonNull(context);
212    Objects.requireNonNull(chartBuilder);
213    if (chartBuilder == parent) {
214      throw new IllegalArgumentException("chartBuilder == parent");
215    }
216    
217    if (parent == null) {
218      context.remove("path");
219    } else {
220      final String path = context.get("path", String.class);
221      assert path != null;
222      final int chartsIndex = path.lastIndexOf("/charts/");
223      assert chartsIndex > 0;
224      context.put("path", path.substring(0, chartsIndex + 1));
225    }
226  }
227
228  /**
229   * Writes the supplied {@code contents} to an appropriate archive
230   * entry that is expected to be suffixed with the supplied {@code
231   * path} in the context of the write operation described by the
232   * supplied {@link Context}.
233   *
234   * @param context the {@link Context} describing the write operation
235   * in effect; must not be {@code null}
236   *
237   * @param path the path within an abstract archive to write;
238   * interpreted as being relative to the current notional chart path,
239   * whatever that might be; must not be {@code null} or {@linkplain
240   * String#isEmpty() empty}
241   *
242   * @param contents the contents to write; must not be {@code null}
243   *
244   * @exception IOException if a write error occurs
245   *
246   * @exception NullPointerException if {@code context}, {@code path}
247   * or {@code contents} is {@code null}
248   *
249   * @exception IllegalArgumentException if {@code path} {@linkplain
250   * String#isEmpty() is empty}
251   */
252  protected abstract void writeEntry(final Context context, final String path, final String contents) throws IOException;
253
254}