http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java b/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java deleted file mode 100644 index abf3edc..0000000 --- a/src/main/java/com/kenshoo/freemarker/util/LengthLimitedWriter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package com.kenshoo.freemarker.util; - -import java.io.FilterWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; - -/** - * A {@link StringWriter} that limits its buffer size, and throws {@link LengthLimitExceededException} when that's - * exceeded. - */ -public class LengthLimitedWriter extends FilterWriter { - - private int lengthLeft; - - public LengthLimitedWriter(Writer writer, int lengthLimit) { - super(writer); - this.lengthLeft = lengthLimit; - } - - @Override - public void write(int c) throws IOException { - if (lengthLeft < 1) { - throw new LengthLimitExceededException(); - } - - super.write(c); - - lengthLeft--; - } - - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - final boolean lengthExceeded; - if (lengthLeft < len) { - len = lengthLeft; - lengthExceeded = true; - } else { - lengthExceeded = false; - } - - super.write(cbuf, off, len); - lengthLeft -= len; - - if (lengthExceeded) { - throw new LengthLimitExceededException(); - } - } - - @Override - public void write(String str, int off, int len) throws IOException { - final boolean lengthExceeded; - if (lengthLeft < len) { - len = lengthLeft; - lengthExceeded = true; - } else { - lengthExceeded = false; - } - - super.write(str, off, len); - lengthLeft -= len; - - if (lengthExceeded) { - throw new LengthLimitExceededException(); - } - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java ---------------------------------------------------------------------- diff --git a/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java b/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java deleted file mode 100644 index fb256f1..0000000 --- a/src/main/java/com/kenshoo/freemarker/view/FreeMarkerOnlineView.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package com.kenshoo.freemarker.view; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import com.kenshoo.freemarker.model.SelectionOption; -import com.kenshoo.freemarker.services.AllowedSettingValuesMaps; -import com.yammer.dropwizard.views.View; - -import freemarker.template.Configuration; - -public class FreeMarkerOnlineView extends View { - - private static final List<SelectionOption> LOCALE_SELECTION_OPTIONS = toLocaleSelectionOptions(AllowedSettingValuesMaps.LOCALE_MAP); - private static final List<SelectionOption> TIME_ZONE_SELECTION_OPTIONS = toSelectionOptions(AllowedSettingValuesMaps.TIME_ZONE_MAP); - private static final List<SelectionOption> OUTPUT_FORMAT_SELECTION_OPTIONS = toSelectionOptions(AllowedSettingValuesMaps.OUTPUT_FORMAT_MAP); - - private String template = ""; - private String dataModel = ""; - private String outputFormat = AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT_KEY; - private String locale = AllowedSettingValuesMaps.DEFAULT_LOCALE_KEY; - private String timeZone = AllowedSettingValuesMaps.DEFAULT_TIME_ZONE_KEY; - - private boolean execute; - - private static List<SelectionOption> toSelectionOptions(Map<String, ?> settingValueMap) { - ArrayList<SelectionOption> selectionOptions = new ArrayList<SelectionOption>(settingValueMap.size()); - for (String key : settingValueMap.keySet()) { - selectionOptions.add(new SelectionOption(key, truncate(key, 25))); - } - Collections.sort(selectionOptions); - return selectionOptions; - } - - private static List<SelectionOption> toLocaleSelectionOptions(Map<String, Locale> localeMap) { - ArrayList<SelectionOption> selectionOptions = new ArrayList<SelectionOption>(localeMap.size()); - for (Map.Entry<String, Locale> ent : localeMap.entrySet()) { - Locale locale = ent.getValue(); - selectionOptions.add( - new SelectionOption(ent.getKey(), - truncate(locale.getDisplayName(Locale.US), 18) + "; " + locale.toString())); - } - Collections.sort(selectionOptions); - return selectionOptions; - } - - private static String truncate(String s, int maxLength) { - if (s == null) { - return null; - } - return s.length() <= maxLength ? s : s.substring(0, Math.max(maxLength - 3, 0)) + "[...]"; - } - - /** - * - * @param template - * @param dataModel - * @param execute set to true if the execution should be triggered on page load. - */ - public FreeMarkerOnlineView() { - super("/view/freemarker-online.ftl", Charset.forName("utf-8")); - } - - public String getTemplate() { - return template; - } - - public void setTemplate(String template) { - this.template = withDefault(template, ""); - } - - public String getDataModel() { - return dataModel; - } - - public void setDataModel(String dataModel) { - this.dataModel = withDefault(dataModel, ""); - } - - public String getFreeMarkerVersion() { - return Configuration.getVersion().toString(); - } - - public List<SelectionOption> getOutputFormats() { - return OUTPUT_FORMAT_SELECTION_OPTIONS; - } - - public List<SelectionOption> getLocales() { - return LOCALE_SELECTION_OPTIONS; - } - - public List<SelectionOption> getTimeZones() { - return TIME_ZONE_SELECTION_OPTIONS; - } - - public String getOutputFormat() { - return outputFormat; - } - - public void setOutputFormat(String outputFormat) { - this.outputFormat = withDefault(outputFormat, AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT_KEY); - } - - public String getLocale() { - return locale; - } - - public void setLocale(String locale) { - this.locale = withDefault(locale, AllowedSettingValuesMaps.DEFAULT_LOCALE_KEY); - } - - public String getTimeZone() { - return timeZone; - } - - public void setTimeZone(String timeZone) { - this.timeZone = withDefault(timeZone, AllowedSettingValuesMaps.DEFAULT_TIME_ZONE_KEY); - } - - public boolean isExecute() { - return execute; - } - - public void setExecute(boolean executeImmediately) { - this.execute = executeImmediately; - } - - private static String withDefault(String value, String defaultValue) { - return !StringUtils.isBlank(value) ? value : defaultValue; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/dropwizard/ApplicationStartup.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/dropwizard/ApplicationStartup.java b/src/main/java/org/apache/freemarker/onlinetester/dropwizard/ApplicationStartup.java new file mode 100644 index 0000000..4caf07c --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/dropwizard/ApplicationStartup.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.dropwizard; + +import com.berico.fallwizard.SpringConfiguration; +import com.berico.fallwizard.SpringService; +import com.yammer.dropwizard.assets.AssetsBundle; +import com.yammer.dropwizard.config.Bootstrap; +import com.yammer.dropwizard.views.ViewBundle; + +public class ApplicationStartup extends SpringService<SpringConfiguration> { + + public static void main(String[] args) throws Exception { + new ApplicationStartup().run(args); + } + + @Override + public void initialize(Bootstrap<SpringConfiguration> bootstrap) { + bootstrap.setName("freemarker-online"); + bootstrap.addBundle(new ViewBundle()); + bootstrap.addBundle(new AssetsBundle("/assets/css", "/css")); + bootstrap.addBundle(new AssetsBundle("/assets/js", "/js")); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/healthchecks/MyProjectHealthCheck.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/healthchecks/MyProjectHealthCheck.java b/src/main/java/org/apache/freemarker/onlinetester/healthchecks/MyProjectHealthCheck.java new file mode 100644 index 0000000..192fa46 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/healthchecks/MyProjectHealthCheck.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.healthchecks; + +import com.yammer.metrics.core.HealthCheck; +import org.springframework.stereotype.Component; + +@Component +public class MyProjectHealthCheck extends HealthCheck { + + // note that this is due to the default spring CTR + public MyProjectHealthCheck() { + super("MyProjectHealthCheck"); + } + + @Override + protected Result check() throws Exception { + return Result.healthy(); // we're always healthy! + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/ErrorCode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/ErrorCode.java b/src/main/java/org/apache/freemarker/onlinetester/model/ErrorCode.java new file mode 100644 index 0000000..c983e87 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/ErrorCode.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.model; + +public enum ErrorCode { + FREEMARKER_SERVICE_TIMEOUT + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/ErrorResponse.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/ErrorResponse.java b/src/main/java/org/apache/freemarker/onlinetester/model/ErrorResponse.java new file mode 100644 index 0000000..68ddf69 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/ErrorResponse.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.model; + +public class ErrorResponse { + private ErrorCode errorCode; + private String errorDescription; + + public ErrorResponse(ErrorCode errorCode, String errorDescription) { + this.errorCode = errorCode; + this.errorDescription = errorDescription; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteRequest.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteRequest.java b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteRequest.java new file mode 100644 index 0000000..a206131 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteRequest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.apache.freemarker.onlinetester.model; + + +public class ExecuteRequest { + private String template; + private String dataModel; + private String outputFormat; + private String locale; + private String timeZone; + + public ExecuteRequest() { + } + + public ExecuteRequest(String template, String dataModel) { + this.template = template; + this.dataModel = dataModel; + } + + public String getDataModel() { + return dataModel; + } + + public void setDataModel(String dataModel) { + this.dataModel = dataModel; + } + + public String getTemplate() { + + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + public String getOutputFormat() { + return outputFormat; + } + + public void setOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceField.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceField.java b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceField.java new file mode 100644 index 0000000..71ad454 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceField.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ExecuteResourceField { + DATA_MODEL("dataModel"), + TEMPLATE("template"), + OUTPUT_FORMAT("outputFormat"), + LOCALE("locale"), + TIME_ZONE("timeZone"); + + private final String fieldName; + + private ExecuteResourceField(String filedName) { + this.fieldName = filedName; + } + + public String toString() { + return getFieldName(); + } + + @JsonValue + public String getFieldName() { + return fieldName; + } + + @JsonCreator + public static ExecuteResourceField fromEnumString(String val) { + for(ExecuteResourceField field : values()) { + if(field.getFieldName().equals(val)) { + return field; + } + } + throw new IllegalArgumentException("Invalid string value passed: " + val); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceProblem.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceProblem.java b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceProblem.java new file mode 100644 index 0000000..3c69d43 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResourceProblem.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.model; + +public class ExecuteResourceProblem { + + private ExecuteResourceField field; + private String message; + + // Needed for JSON unmarshalling + public ExecuteResourceProblem() { + // + } + + public ExecuteResourceProblem(ExecuteResourceField field, String message) { + this.field = field; + this.message = message; + } + + public ExecuteResourceField getField() { + return field; + } + + public void setField(ExecuteResourceField field) { + this.field = field; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResponse.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResponse.java b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResponse.java new file mode 100644 index 0000000..41c33f1 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/ExecuteResponse.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.model; + +import java.util.List; + +public class ExecuteResponse { + private String result; + private List<ExecuteResourceProblem> problems; + private boolean truncatedResult; + + public ExecuteResponse(String result, List<ExecuteResourceProblem> problems, boolean truncatedResult) { + this.result = result; + this.problems = problems; + this.truncatedResult = truncatedResult; + } + + public ExecuteResponse() { + + } + + public List<ExecuteResourceProblem> getProblems() { + return problems; + } + + public void setProblems(List<ExecuteResourceProblem> problems) { + this.problems = problems; + } + + public boolean isTruncatedResult() { + return truncatedResult; + } + + public void setTruncatedResult(boolean truncatedResult) { + this.truncatedResult = truncatedResult; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/model/SelectionOption.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/model/SelectionOption.java b/src/main/java/org/apache/freemarker/onlinetester/model/SelectionOption.java new file mode 100644 index 0000000..d7dfe49 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/model/SelectionOption.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.model; + +public class SelectionOption implements Comparable<SelectionOption> { + + private final String value; + private final String label; + + public String getValue() { + return value; + } + + public String getLabel() { + return label; + } + + public SelectionOption(String value, String label) { + this.value = value; + this.label = label; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((label == null) ? 0 : label.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SelectionOption other = (SelectionOption) obj; + if (label == null) { + if (other.label != null) { + return false; + } + } else if (!label.equals(other.label)) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + + @Override + public int compareTo(SelectionOption o) { + int r = label.compareTo(o.label); + if (r != 0) { + return r; + } + + return value.compareTo(o.value); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineExecuteResource.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineExecuteResource.java b/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineExecuteResource.java new file mode 100644 index 0000000..c850720 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineExecuteResource.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.resources; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.RejectedExecutionException; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.commons.lang3.StringUtils; +import org.apache.freemarker.onlinetester.model.ErrorCode; +import org.apache.freemarker.onlinetester.model.ExecuteResourceProblem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import org.apache.freemarker.onlinetester.model.ErrorResponse; +import org.apache.freemarker.onlinetester.model.ExecuteRequest; +import org.apache.freemarker.onlinetester.model.ExecuteResourceField; +import org.apache.freemarker.onlinetester.model.ExecuteResponse; +import org.apache.freemarker.onlinetester.services.AllowedSettingValuesMaps; +import org.apache.freemarker.onlinetester.services.FreeMarkerService; +import org.apache.freemarker.onlinetester.services.FreeMarkerServiceResponse; +import org.apache.freemarker.onlinetester.util.DataModelParser; +import org.apache.freemarker.onlinetester.util.DataModelParsingException; +import org.apache.freemarker.onlinetester.util.ExceptionUtils; + +import freemarker.core.OutputFormat; + +@Path("/api/execute") +@Component +public class FreeMarkerOnlineExecuteResource { + private static final int MAX_TEMPLATE_INPUT_LENGTH = 10000; + + private static final int MAX_DATA_MODEL_INPUT_LENGTH = 10000; + + private static final String MAX_TEMPLATE_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE + = "The template length has exceeded the {0} character limit set for this service."; + + private static final String MAX_DATA_MODEL_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE + = "The data model length has exceeded the {0} character limit set for this service."; + + private static final String UNKNOWN_OUTPUT_FORMAT_ERROR_MESSAGE = "Unknown output format: {0}"; + private static final String UNKNOWN_LOCALE_ERROR_MESSAGE = "Unknown locale: {0}"; + private static final String UNKNOWN_TIME_ZONE_ERROR_MESSAGE = "Unknown time zone: {0}"; + + private static final String SERVICE_OVERBURDEN_ERROR_MESSAGE + = "Sorry, the service is overburden and couldn't handle your request now. Try again later."; + + static final String DATA_MODEL_ERROR_MESSAGE_HEADING = "Failed to parse data model:"; + static final String DATA_MODEL_ERROR_MESSAGE_FOOTER = "Note: This is NOT a FreeMarker error message. " + + "The data model syntax is specific to this online service."; + + @Autowired + private FreeMarkerService freeMarkerService; + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response formResult( + ExecuteRequest req) { + ExecuteResponse resp = new ExecuteResponse(); + + if (StringUtils.isBlank(req.getTemplate()) && StringUtils.isBlank(req.getDataModel())) { + return Response.status(400).entity("Empty Template & data").build(); + } + + List<ExecuteResourceProblem> problems = new ArrayList<ExecuteResourceProblem>(); + + String template = getTemplate(req, problems); + Map<String, Object> dataModel = getDataModel(req, problems); + OutputFormat outputFormat = getOutputFormat(req, problems); + Locale locale = getLocale(req, problems); + TimeZone timeZone = getTimeZone(req, problems); + + if (!problems.isEmpty()) { + resp.setProblems(problems); + return buildFreeMarkerResponse(resp); + } + + FreeMarkerServiceResponse freeMarkerServiceResponse; + try { + freeMarkerServiceResponse = freeMarkerService.calculateTemplateOutput( + template, dataModel, + outputFormat, locale, timeZone); + } catch (RejectedExecutionException e) { + String error = SERVICE_OVERBURDEN_ERROR_MESSAGE; + return Response.serverError().entity(new ErrorResponse(ErrorCode.FREEMARKER_SERVICE_TIMEOUT, error)).build(); + } + if (!freeMarkerServiceResponse.isSuccesful()){ + Throwable failureReason = freeMarkerServiceResponse.getFailureReason(); + String error = ExceptionUtils.getMessageWithCauses(failureReason); + problems.add(new ExecuteResourceProblem(ExecuteResourceField.TEMPLATE, error)); + resp.setProblems(problems); + return buildFreeMarkerResponse(resp); + } + + String result = freeMarkerServiceResponse.getTemplateOutput(); + resp.setResult(result); + resp.setTruncatedResult(freeMarkerServiceResponse.isTemplateOutputTruncated()); + return buildFreeMarkerResponse(resp); + } + + private String getTemplate(ExecuteRequest req, List<ExecuteResourceProblem> problems) { + String template = req.getTemplate(); + + if (template.length() > MAX_TEMPLATE_INPUT_LENGTH) { + String error = formatMessage(MAX_TEMPLATE_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE, MAX_TEMPLATE_INPUT_LENGTH); + problems.add(new ExecuteResourceProblem(ExecuteResourceField.TEMPLATE, error)); + return null; + } + + return template; + } + + private Map<String, Object> getDataModel(ExecuteRequest req, List<ExecuteResourceProblem> problems) { + String dataModel = req.getDataModel(); + + if (dataModel.length() > MAX_DATA_MODEL_INPUT_LENGTH) { + String error = formatMessage( + MAX_DATA_MODEL_INPUT_LENGTH_EXCEEDED_ERROR_MESSAGE, MAX_DATA_MODEL_INPUT_LENGTH); + problems.add(new ExecuteResourceProblem(ExecuteResourceField.DATA_MODEL, error)); + return null; + } + + try { + return DataModelParser.parse(dataModel, freeMarkerService.getFreeMarkerTimeZone()); + } catch (DataModelParsingException e) { + problems.add(new ExecuteResourceProblem(ExecuteResourceField.DATA_MODEL, decorateResultText(e.getMessage()))); + return null; + } + } + + private OutputFormat getOutputFormat(ExecuteRequest req, List<ExecuteResourceProblem> problems) { + String outputFormatStr = req.getOutputFormat(); + + if (StringUtils.isBlank(outputFormatStr)) { + return AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT; + } + + OutputFormat outputFormat = AllowedSettingValuesMaps.OUTPUT_FORMAT_MAP.get(outputFormatStr); + if (outputFormat == null) { + problems.add(new ExecuteResourceProblem( + ExecuteResourceField.OUTPUT_FORMAT, + formatMessage(UNKNOWN_OUTPUT_FORMAT_ERROR_MESSAGE, outputFormatStr))); + } + return outputFormat; + } + + private Locale getLocale(ExecuteRequest req, List<ExecuteResourceProblem> problems) { + String localeStr = req.getLocale(); + + if (StringUtils.isBlank(localeStr)) { + return AllowedSettingValuesMaps.DEFAULT_LOCALE; + } + + Locale locale = AllowedSettingValuesMaps.LOCALE_MAP.get(localeStr); + if (locale == null) { + problems.add(new ExecuteResourceProblem( + ExecuteResourceField.LOCALE, + formatMessage(UNKNOWN_LOCALE_ERROR_MESSAGE, localeStr))); + } + return locale; + } + + private TimeZone getTimeZone(ExecuteRequest req, List<ExecuteResourceProblem> problems) { + String timeZoneStr = req.getTimeZone(); + + if (StringUtils.isBlank(timeZoneStr)) { + return AllowedSettingValuesMaps.DEFAULT_TIME_ZONE; + } + + TimeZone timeZone = AllowedSettingValuesMaps.TIME_ZONE_MAP.get(timeZoneStr); + if (timeZone == null) { + problems.add(new ExecuteResourceProblem( + ExecuteResourceField.TIME_ZONE, + formatMessage(UNKNOWN_TIME_ZONE_ERROR_MESSAGE, timeZoneStr))); + } + return timeZone; + } + + private Response buildFreeMarkerResponse(ExecuteResponse executeResponse){ + return Response.ok().entity(executeResponse).build(); + } + + private String decorateResultText(String resultText) { + return DATA_MODEL_ERROR_MESSAGE_HEADING + "\n\n" + resultText + "\n\n" + DATA_MODEL_ERROR_MESSAGE_FOOTER; + } + + private String formatMessage(String key, Object... params) { + return new MessageFormat(key, Locale.US).format(params); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineResource.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineResource.java b/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineResource.java new file mode 100644 index 0000000..ebf82a1 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/resources/FreeMarkerOnlineResource.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.resources; + +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.springframework.stereotype.Component; + +import org.apache.freemarker.onlinetester.view.FreeMarkerOnlineView; + +@Path("/") +@Component +public class FreeMarkerOnlineResource { + + @GET + @Produces(MediaType.TEXT_HTML) + public FreeMarkerOnlineView blankForm() { + return new FreeMarkerOnlineView(); + } + + @POST + @Produces(MediaType.TEXT_HTML) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public FreeMarkerOnlineView formResult( + @FormParam("template") String template, + @FormParam("dataModel") String dataModel, + @FormParam("outputFormat") String outputFormat, + @FormParam("locale") String locale, + @FormParam("timeZone") String timeZone) { + FreeMarkerOnlineView view = new FreeMarkerOnlineView(); + view.setTemplate(template); + view.setDataModel(dataModel); + view.setOutputFormat(outputFormat); + view.setLocale(locale); + view.setTimeZone(timeZone); + view.setExecute(true); + return view; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/services/AllowedSettingValuesMaps.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/services/AllowedSettingValuesMaps.java b/src/main/java/org/apache/freemarker/onlinetester/services/AllowedSettingValuesMaps.java new file mode 100644 index 0000000..e3111aa --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/services/AllowedSettingValuesMaps.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.services; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import freemarker.core.HTMLOutputFormat; +import freemarker.core.OutputFormat; +import freemarker.core.PlainTextOutputFormat; +import freemarker.core.RTFOutputFormat; +import freemarker.core.UndefinedOutputFormat; +import freemarker.core.XHTMLOutputFormat; +import freemarker.core.XMLOutputFormat; + +/** + * Maps of the setting values the caller can chose from (these are the value shown in a dropdown on the UI). + */ +public class AllowedSettingValuesMaps { + + public static final OutputFormat DEFAULT_OUTPUT_FORMAT = UndefinedOutputFormat.INSTANCE; + public static final String DEFAULT_OUTPUT_FORMAT_KEY = DEFAULT_OUTPUT_FORMAT.getName(); + public static final Map<String, OutputFormat> OUTPUT_FORMAT_MAP; + static { + Map<String, OutputFormat> map = new HashMap<String, OutputFormat>(); + + addOutputFormatToMap(map, UndefinedOutputFormat.INSTANCE); + addOutputFormatToMap(map, HTMLOutputFormat.INSTANCE); + addOutputFormatToMap(map, XMLOutputFormat.INSTANCE); + addOutputFormatToMap(map, XHTMLOutputFormat.INSTANCE); + addOutputFormatToMap(map, RTFOutputFormat.INSTANCE); + addOutputFormatToMap(map, PlainTextOutputFormat.INSTANCE); + + OUTPUT_FORMAT_MAP = Collections.unmodifiableMap(map); + } + + private static void addOutputFormatToMap(Map<String, OutputFormat> map, OutputFormat outputFormat) { + map.put(outputFormat.getName(), outputFormat); + } + + public static final Locale DEFAULT_LOCALE = Locale.US; + public static final String DEFAULT_LOCALE_KEY = DEFAULT_LOCALE.toString(); + public static final Map<String, Locale> LOCALE_MAP; + static { + List<Locale> availableLocales = new ArrayList<Locale>(Arrays.asList(Locale.getAvailableLocales())); + + for (Iterator<Locale> iterator = availableLocales.iterator(); iterator.hasNext();) { + Locale locale = iterator.next(); + // Don't bloat the list with "variants" + if (!StringUtils.isBlank(locale.getVariant())) { + iterator.remove(); + } + } + + if (!availableLocales.contains(DEFAULT_LOCALE)) { + availableLocales.add(DEFAULT_LOCALE); + } + + Map<String, Locale> map = new HashMap<String, Locale>(); + for (Locale locale : availableLocales) { + map.put(locale.toString(), locale); + } + + LOCALE_MAP = Collections.unmodifiableMap(map); + } + + public static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles"); + + public static final String DEFAULT_TIME_ZONE_KEY; + + public static final Map<String, TimeZone> TIME_ZONE_MAP; + static { + String[] availableIDs = TimeZone.getAvailableIDs(); + + DEFAULT_TIME_ZONE_KEY = AllowedSettingValuesMaps.DEFAULT_TIME_ZONE.getID(); + if (!ArrayUtils.contains(availableIDs, DEFAULT_TIME_ZONE_KEY)) { + ArrayUtils.add(availableIDs, DEFAULT_TIME_ZONE_KEY); + } + + Map<String, TimeZone> map = new HashMap<String, TimeZone>(); + for (String timeZoneId : availableIDs) { + map.put(timeZoneId, TimeZone.getTimeZone(timeZoneId)); + } + + TIME_ZONE_MAP = Collections.unmodifiableMap(map); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerService.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerService.java b/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerService.java new file mode 100644 index 0000000..4487abe --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerService.java @@ -0,0 +1,364 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.services; + +import java.io.StringReader; +import java.io.StringWriter; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.freemarker.onlinetester.util.LengthLimitedWriter; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import org.apache.freemarker.onlinetester.util.LengthLimitExceededException; + +import freemarker.core.FreeMarkerInternalsAccessor; +import freemarker.core.OutputFormat; +import freemarker.core.ParseException; +import freemarker.core.TemplateClassResolver; +import freemarker.core.TemplateConfiguration; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.TemplateExceptionHandler; + +@Service +public class FreeMarkerService { + + private static final int DEFAULT_MAX_OUTPUT_LENGTH = 100000; + private static final int DEFAULT_MAX_THREADS = Math.max(2, + (int) Math.round(Runtime.getRuntime().availableProcessors() * 3.0 / 4)); + /** Not implemented yet, will need 2.3.22, even then a _CoreAPI call. */ + private static final long DEFAULT_MAX_TEMPLATE_EXECUTION_TIME = 2000; + private static final int MIN_DEFAULT_MAX_QUEUE_LENGTH = 2; + private static final int MAX_DEFAULT_MAX_QUEUE_LENGTH_MILLISECONDS = 30000; + private static final long THREAD_KEEP_ALIVE_TIME = 4 * 1000; + private static final long ABORTION_LOOP_TIME_LIMIT = 5000; + private static final long ABORTION_LOOP_INTERRUPTION_DISTANCE = 50; + + private static final String MAX_OUTPUT_LENGTH_EXCEEDED_TERMINATION = "\n----------\n" + + "Aborted template processing, as the output length has exceeded the {0} character limit set for " + + "this service."; + + private static final Logger logger = LoggerFactory.getLogger(FreeMarkerService.class); + + private final Configuration freeMarkerConfig; + + private ExecutorService templateExecutor; + + private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH; + + private int maxThreads = DEFAULT_MAX_THREADS; + private Integer maxQueueLength; + private long maxTemplateExecutionTime = DEFAULT_MAX_TEMPLATE_EXECUTION_TIME; + + public FreeMarkerService() { + freeMarkerConfig = new Configuration(Configuration.getVersion()); + freeMarkerConfig.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER); + freeMarkerConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + freeMarkerConfig.setLogTemplateExceptions(false); + freeMarkerConfig.setLocale(AllowedSettingValuesMaps.DEFAULT_LOCALE); + freeMarkerConfig.setTimeZone(AllowedSettingValuesMaps.DEFAULT_TIME_ZONE); + freeMarkerConfig.setOutputFormat(AllowedSettingValuesMaps.DEFAULT_OUTPUT_FORMAT); + freeMarkerConfig.setOutputEncoding("UTF-8"); + } + + /** + * @param templateSourceCode + * The FTL to execute; not {@code null}. + * @param dataModel + * The FreeMarker data-model to execute the template with; maybe {@code null}. + * @param outputFormat + * The output format to execute the template with; maybe {@code null}. + * @param locale + * The locale to execute the template with; maybe {@code null}. + * @param timeZone + * The time zone to execute the template with; maybe {@code null}. + * + * @return The result of the template parsing and evaluation. The method won't throw exception if that fails due to + * errors in the template provided, instead it indicates this fact in the response object. That's because + * this is a service for trying out the template language, so such errors are part of the normal operation. + * + * @throws RejectedExecutionException + * If the service is overburden and thus doing the calculation was rejected. + * @throws FreeMarkerServiceException + * If the calculation fails from a reason that's not a mistake in the template and doesn't fit the + * meaning of {@link RejectedExecutionException} either. + */ + public FreeMarkerServiceResponse calculateTemplateOutput( + String templateSourceCode, Object dataModel, OutputFormat outputFormat, Locale locale, TimeZone timeZone) + throws RejectedExecutionException { + Objects.requireNonNull(templateExecutor, "templateExecutor was null - was postConstruct ever called?"); + + final CalculateTemplateOutput task = new CalculateTemplateOutput( + templateSourceCode, dataModel, outputFormat, locale, timeZone); + Future<FreeMarkerServiceResponse> future = templateExecutor.submit(task); + + synchronized (task) { + while (!task.isTemplateExecutionStarted() && !task.isTaskEnded() && !future.isDone()) { + try { + task.wait(50); // Timeout is needed to periodically check future.isDone() + } catch (InterruptedException e) { + throw new FreeMarkerServiceException("Template execution task was interrupted.", e); + } + } + } + + try { + return future.get(maxTemplateExecutionTime, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + throw new FreeMarkerServiceException("Template execution task unexpectedly failed", e.getCause()); + } catch (InterruptedException e) { + throw new FreeMarkerServiceException("Template execution task was interrupted.", e); + } catch (TimeoutException e) { + // Exactly one interruption should be enough, and it should abort template processing pretty much + // immediately. But to be on the safe side we will interrupt in a loop, with a timeout. + final long abortionLoopStartTime = System.currentTimeMillis(); + long timeLeft = ABORTION_LOOP_TIME_LIMIT; + boolean templateExecutionEnded = false; + do { + synchronized (task) { + Thread templateExecutorThread = task.getTemplateExecutorThread(); + if (templateExecutorThread == null) { + templateExecutionEnded = true; + } else { + FreeMarkerInternalsAccessor.interruptTemplateProcessing(templateExecutorThread); + logger.debug("Trying to interrupt overly long template processing (" + timeLeft + " ms left)."); + } + } + if (!templateExecutionEnded) { + try { + timeLeft = ABORTION_LOOP_TIME_LIMIT - (System.currentTimeMillis() - abortionLoopStartTime); + if (timeLeft > 0) { + Thread.sleep(ABORTION_LOOP_INTERRUPTION_DISTANCE); + } + } catch (InterruptedException eInt) { + logger.error("Template execution abortion loop was interrupted", eInt); + timeLeft = 0; + } + } + } while (!templateExecutionEnded && timeLeft > 0); + + if (templateExecutionEnded) { + logger.debug("Long template processing has ended."); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e1) { + throw new FreeMarkerServiceException("Failed to get result from template executor task", e); + } + } else { + throw new FreeMarkerServiceException( + "Couldn't stop long running template processing within " + ABORTION_LOOP_TIME_LIMIT + + " ms. It's possibly stuck forever. Such problems can exhaust the executor pool. " + + "Template (quoted): " + StringEscapeUtils.escapeJava(templateSourceCode)); + } + } + } + + public int getMaxOutputLength() { + return maxOutputLength; + } + + public void setMaxOutputLength(int maxOutputLength) { + this.maxOutputLength = maxOutputLength; + } + + public int getMaxThreads() { + return maxThreads; + } + + public void setMaxThreads(int maxThreads) { + this.maxThreads = maxThreads; + } + + public int getMaxQueueLength() { + return maxQueueLength; + } + + public void setMaxQueueLength(int maxQueueLength) { + this.maxQueueLength = maxQueueLength; + } + + public long getMaxTemplateExecutionTime() { + return maxTemplateExecutionTime; + } + + public void setMaxTemplateExecutionTime(long maxTemplateExecutionTime) { + this.maxTemplateExecutionTime = maxTemplateExecutionTime; + } + + /** + * Returns the time zone used by the FreeMarker templates. + */ + public TimeZone getFreeMarkerTimeZone() { + return freeMarkerConfig.getTimeZone(); + } + + private FreeMarkerServiceResponse createFailureResponse(Throwable e) { + logger.debug("The template had error(s)", e); + return new FreeMarkerServiceResponse.Builder().buildForFailure(e); + } + + @PostConstruct + public void postConstruct() { + int actualMaxQueueLength = maxQueueLength != null + ? maxQueueLength + : Math.max( + MIN_DEFAULT_MAX_QUEUE_LENGTH, + (int) (MAX_DEFAULT_MAX_QUEUE_LENGTH_MILLISECONDS / maxTemplateExecutionTime)); + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + maxThreads, maxThreads, + THREAD_KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, + new BlockingArrayQueue<Runnable>(actualMaxQueueLength)); + threadPoolExecutor.allowCoreThreadTimeOut(true); + templateExecutor = threadPoolExecutor; + } + + private class CalculateTemplateOutput implements Callable<FreeMarkerServiceResponse> { + + private boolean templateExecutionStarted; + private Thread templateExecutorThread; + private final String templateSourceCode; + private final Object dataModel; + private final OutputFormat outputFormat; + private final Locale locale; + private final TimeZone timeZone; + private boolean taskEnded; + + private CalculateTemplateOutput(String templateSourceCode, Object dataModel, + OutputFormat outputFormat, Locale locale, TimeZone timeZone) { + this.templateSourceCode = templateSourceCode; + this.dataModel = dataModel; + this.outputFormat = outputFormat; + this.locale = locale; + this.timeZone = timeZone; + } + + @Override + public FreeMarkerServiceResponse call() throws Exception { + try { + Template template; + try { + TemplateConfiguration tCfg = new TemplateConfiguration(); + tCfg.setParentConfiguration(freeMarkerConfig); + if (outputFormat != null) { + tCfg.setOutputFormat(outputFormat); + } + if (locale != null) { + tCfg.setLocale(locale); + } + if (timeZone != null) { + tCfg.setTimeZone(timeZone); + } + + template = new Template(null, null, + new StringReader(templateSourceCode), freeMarkerConfig, tCfg, null); + + tCfg.apply(template); + } catch (ParseException e) { + // Expected (part of normal operation) + return createFailureResponse(e); + } catch (Exception e) { + // Not expected + throw new FreeMarkerServiceException("Unexpected exception during template parsing", e); + } + + FreeMarkerInternalsAccessor.makeTemplateInterruptable(template); + + boolean resultTruncated; + StringWriter writer = new StringWriter(); + try { + synchronized (this) { + templateExecutorThread = Thread.currentThread(); + templateExecutionStarted = true; + notifyAll(); + } + try { + template.process(dataModel, new LengthLimitedWriter(writer, maxOutputLength)); + } finally { + synchronized (this) { + templateExecutorThread = null; + FreeMarkerInternalsAccessor.clearAnyPendingTemplateProcessingInterruption(); + } + } + resultTruncated = false; + } catch (LengthLimitExceededException e) { + // Not really an error, we just cut the output here. + resultTruncated = true; + writer.write(new MessageFormat(MAX_OUTPUT_LENGTH_EXCEEDED_TERMINATION, AllowedSettingValuesMaps.DEFAULT_LOCALE) + .format(new Object[] { maxOutputLength })); + // Falls through + } catch (TemplateException e) { + // Expected (part of normal operation) + return createFailureResponse(e); + } catch (Exception e) { + if (FreeMarkerInternalsAccessor.isTemplateProcessingInterruptedException(e)) { + return new FreeMarkerServiceResponse.Builder().buildForFailure(new TimeoutException( + "Template processing was aborted for exceeding the " + getMaxTemplateExecutionTime() + + " ms time limit set for this online service. This is usually because you have " + + "a very long running #list (or other kind of loop) in your template.")); + } + // Not expected + throw new FreeMarkerServiceException("Unexpected exception during template evaluation", e); + } + + return new FreeMarkerServiceResponse.Builder().buildForSuccess(writer.toString(), resultTruncated); + } finally { + synchronized (this) { + taskEnded = true; + notifyAll(); + } + } + } + + private synchronized boolean isTemplateExecutionStarted() { + return templateExecutionStarted; + } + + private synchronized boolean isTaskEnded() { + return taskEnded; + } + + /** + * @return non-{@code null} after the task execution has actually started, but before it has finished. + */ + private synchronized Thread getTemplateExecutorThread() { + return templateExecutorThread; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceException.java b/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceException.java new file mode 100644 index 0000000..2ca7ee4 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceException.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.services; + +/** + * When {@link FreeMarkerService} fails on an unexpected way (non-user error). + */ +public class FreeMarkerServiceException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public FreeMarkerServiceException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public FreeMarkerServiceException(String message, Throwable cause) { + super(message, cause); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceResponse.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceResponse.java b/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceResponse.java new file mode 100644 index 0000000..3950717 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerServiceResponse.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.services; + +public class FreeMarkerServiceResponse { + + private final String templateOutput; + private final boolean templateOutputTruncated; + private final Throwable failureReason; + + FreeMarkerServiceResponse(String templateOutput, boolean templateOutputTruncated) { + this.templateOutput = templateOutput; + this.templateOutputTruncated = templateOutputTruncated; + this.failureReason = null; + } + + FreeMarkerServiceResponse(Throwable failureReason) { + this.templateOutput = null; + this.templateOutputTruncated = false; + this.failureReason = failureReason; + } + + public String getTemplateOutput() { + return templateOutput; + } + + public boolean isTemplateOutputTruncated() { + return templateOutputTruncated; + } + + public boolean isSuccesful() { + return failureReason == null; + } + + public Throwable getFailureReason() { + return failureReason; + } + + public static class Builder { + + public FreeMarkerServiceResponse buildForSuccess(String result, boolean resultTruncated){ + return new FreeMarkerServiceResponse(result, resultTruncated); + } + + public FreeMarkerServiceResponse buildForFailure(Throwable failureReason){ + return new FreeMarkerServiceResponse(failureReason); + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParser.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParser.java b/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParser.java new file mode 100644 index 0000000..b356be6 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParser.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.util; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; + +import org.springframework.util.StringUtils; +import org.w3c.dom.Document; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import freemarker.ext.dom.NodeModel; +import freemarker.template.utility.DateUtil; +import freemarker.template.utility.DateUtil.CalendarFieldsToDateConverter; +import freemarker.template.utility.DateUtil.DateParseException; +import freemarker.template.utility.DateUtil.TrivialCalendarFieldsToDateConverter; + +/** + * Parses the text that the user enters into the data model input field. + */ +public final class DataModelParser { + + private static final String KEYWORD_NEGATIVE_INFINITY = "-Infinity"; + + private static final String KEYWORD_POSITIVE_INFINITY = "+Infinity"; + + private static final String KEYWORD_INFINITY = "Infinity"; + + private static final String KEYWORD_TRUE = "true"; + + private static final String KEYWORD_FALSE = "false"; + + private static final String KEYWORD_NULL = "null"; + + private static final String KEYWORD_NAN = "NaN"; + + /** Matches a line starting like "someVariable=". */ + private static final Pattern ASSIGNMENT_START = Pattern.compile( + "^\\s*" + + "(\\p{L}[\\p{L}\\p{N}\\.:\\-_$@]*)" // name + + "[ \t]*=\\s*", + Pattern.MULTILINE); + + /** Matches a value that starts like a number, or probably meant to be number at least. */ + private static final Pattern NUMBER_LIKE = Pattern.compile("[+-]?[\\.,]?[0-9].*", Pattern.DOTALL); + + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + + private DataModelParser() { + // Not meant to be instantiated + } + + public static Map<String, Object> parse(String src, TimeZone timeZone) throws DataModelParsingException { + if (!StringUtils.hasText(src)) { + return Collections.emptyMap(); + } + + Map<String, Object> dataModel = new LinkedHashMap<>(); + + String lastName = null; + int lastAssignmentStartEnd = 0; + final Matcher assignmentStart = ASSIGNMENT_START.matcher(src); + findAssignments: while (true) { + boolean hasNextAssignment = assignmentStart.find(lastAssignmentStartEnd); + + if (lastName != null) { + String value = src.substring( + lastAssignmentStartEnd, hasNextAssignment ? assignmentStart.start() : src.length()) + .trim(); + final Object parsedValue; + try { + parsedValue = parseValue(value, timeZone); + } catch (DataModelParsingException e) { + throw new DataModelParsingException( + "Failed to parse the value of \"" + lastName + "\":\n" + e.getMessage(), e.getCause()); + } + dataModel.put(lastName, parsedValue); + } + + if (lastName == null && (!hasNextAssignment || assignmentStart.start() != 0)) { + throw new DataModelParsingException( + "The data model specification must start with an assignment (name=value)."); + } + + if (!hasNextAssignment) { + break findAssignments; + } + + lastName = assignmentStart.group(1).trim(); + lastAssignmentStartEnd = assignmentStart.end(); + } + + return dataModel; + } + + private static Object parseValue(String value, TimeZone timeZone) throws DataModelParsingException { + // Note: Because we fall back to interpret the input as a literal string value when it doesn't look like + // anything else (like a number, boolean, etc.), it's important to avoid misunderstandings, and throw exception + // in suspicious situations. The user can always quote the string value if we are "too smart". But he will + // be confused about the rules of FreeMarker if what he believes to be a non-string is misinterpreted by this + // parser as a string. Getting sometimes an error and then quoting the string is better than that. + + if (value.endsWith(";")) { // Tolerate this habit of Java and JavaScript programmers + value = value.substring(value.length() - 1).trim(); + } + + if (NUMBER_LIKE.matcher(value).matches()) { + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + // Maybe it's a ISO 8601 Date/time/datetime + CalendarFieldsToDateConverter calToDateConverter = new TrivialCalendarFieldsToDateConverter(); + + DateParseException attemptedTemportalPExc = null; + String attemptedTemporalType = null; + final int dashIdx = value.indexOf('-'); + final int colonIdx = value.indexOf(':'); + if (value.indexOf('T') > 1 || (dashIdx > 1 && colonIdx > dashIdx)) { + try { + return new Timestamp( + DateUtil.parseISO8601DateTime(value, timeZone, calToDateConverter).getTime()); + } catch (DateParseException pExc) { + attemptedTemporalType = "date-time"; + attemptedTemportalPExc = pExc; + } + } else if (dashIdx > 1) { + try { + return new java.sql.Date( + DateUtil.parseISO8601Date(value, timeZone, calToDateConverter).getTime()); + } catch (DateParseException pExc) { + attemptedTemporalType = "date"; + attemptedTemportalPExc = pExc; + } + } else if (colonIdx > 1) { + try { + return new Time( + DateUtil.parseISO8601Time(value, timeZone, calToDateConverter).getTime()); + } catch (DateParseException pExc) { + attemptedTemporalType = "time"; + attemptedTemportalPExc = pExc; + } + } + if (attemptedTemportalPExc == null) { + throw new DataModelParsingException("Malformed number: " + value, e); + } else { + throw new DataModelParsingException( + "Malformed ISO 8601 " + attemptedTemporalType + " (or malformed number): " + + attemptedTemportalPExc.getMessage(), e.getCause()); + } + } + } else if (value.startsWith("\"")) { + try { + return JSON_MAPPER.readValue(value, String.class); + } catch (IOException e) { + throw new DataModelParsingException( + "Malformed quoted string (using JSON syntax): " + getMessageWithoutLocation(e), + e); + } + } else if (value.startsWith("\'")) { + throw new DataModelParsingException( + "Malformed quoted string (using JSON syntax): Use \" character for quotation, not \' character."); + } else if (value.startsWith("[")) { + try { + return JSON_MAPPER.readValue(value, List.class); + } catch (IOException e) { + throw new DataModelParsingException( + "Malformed list (using JSON syntax): " + getMessageWithoutLocation(e), + e); + } + } else if (value.startsWith("{")) { + try { + return JSON_MAPPER.readValue(value, LinkedHashMap.class); + } catch (IOException e) { + throw new DataModelParsingException( + "Malformed list (using JSON syntax): " + getMessageWithoutLocation(e), + e); + } + } else if (value.startsWith("<")) { + try { + DocumentBuilder builder = NodeModel.getDocumentBuilderFactory().newDocumentBuilder(); + ErrorHandler errorHandler = NodeModel.getErrorHandler(); + if (errorHandler != null) builder.setErrorHandler(errorHandler); + final Document doc = builder.parse(new InputSource(new StringReader(value))); + NodeModel.simplify(doc); + return doc; + } catch (SAXException e) { + final String saxMsg = e.getMessage(); + throw new DataModelParsingException("Malformed XML: " + (saxMsg != null ? saxMsg : e), e); + } catch (Exception e) { + throw new DataModelParsingException("XML parsing has failed with internal error: " + e, e); + } + } else if (value.equalsIgnoreCase(KEYWORD_TRUE)) { + checkKeywordCase(value, KEYWORD_TRUE); + return Boolean.TRUE; + } else if (value.equalsIgnoreCase(KEYWORD_FALSE)) { + checkKeywordCase(value, KEYWORD_FALSE); + return Boolean.FALSE; + } else if (value.equalsIgnoreCase(KEYWORD_NULL)) { + checkKeywordCase(value, KEYWORD_NULL); + return null; + } else if (value.equalsIgnoreCase(KEYWORD_NAN)) { + checkKeywordCase(value, KEYWORD_NAN); + return Double.NaN; + } else if (value.equalsIgnoreCase(KEYWORD_INFINITY)) { + checkKeywordCase(value, KEYWORD_INFINITY); + return Double.POSITIVE_INFINITY; + } else if (value.equalsIgnoreCase(KEYWORD_POSITIVE_INFINITY)) { + checkKeywordCase(value, KEYWORD_POSITIVE_INFINITY); + return Double.POSITIVE_INFINITY; + } else if (value.equalsIgnoreCase(KEYWORD_NEGATIVE_INFINITY)) { + checkKeywordCase(value, KEYWORD_NEGATIVE_INFINITY); + return Double.NEGATIVE_INFINITY; + } else if (value.length() == 0) { + throw new DataModelParsingException( + "Empty value. (If you indeed wanted a 0 length string, quote it, like \"\".)"); + } else { + return value; + } + } + + private static String getMessageWithoutLocation(IOException e) { + return e instanceof JsonProcessingException + ? ((JsonProcessingException) e).getOriginalMessage() + : e.getMessage(); + } + + private static void checkKeywordCase(String inputKeyword, String correctKeyword) throws DataModelParsingException { + if (!correctKeyword.equals(inputKeyword)) { + throw new DataModelParsingException("Keywords are case sensitive; the correct form is: " + + correctKeyword); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParsingException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParsingException.java b/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParsingException.java new file mode 100644 index 0000000..8c62127 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/util/DataModelParsingException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.util; + +import java.util.TimeZone; + +/** + * Thrown by {@link DataModelParser#parse(String, TimeZone)}. + */ +public class DataModelParsingException extends Exception { + + private static final long serialVersionUID = 1L; + + public DataModelParsingException(String message, Throwable cause) { + super(message, cause); + } + + public DataModelParsingException(String message) { + super(message); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/util/ExceptionUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/util/ExceptionUtils.java b/src/main/java/org/apache/freemarker/onlinetester/util/ExceptionUtils.java new file mode 100644 index 0000000..dfbe293 --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/util/ExceptionUtils.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.util; + +import freemarker.core.ParseException; +import freemarker.template.TemplateException; + +public final class ExceptionUtils { + + private ExceptionUtils() { + // Not meant to be instantiated + } + + /** + * The error message (and sometimes also the class), and then the same with the cause exception, and so on. Doesn't + * contain the stack trace or other location information. + */ + public static String getMessageWithCauses(final Throwable exc) { + StringBuilder sb = new StringBuilder(); + + Throwable curExc = exc; + while (curExc != null) { + if (curExc != exc) { + sb.append("\n\nCaused by:\n"); + } + String msg = curExc.getMessage(); + if (msg == null || !(curExc instanceof TemplateException || curExc instanceof ParseException)) { + sb.append(curExc.getClass().getName()).append(": "); + } + sb.append(msg); + curExc = curExc.getCause(); + } + return sb.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker-online-tester/blob/2f0c0424/src/main/java/org/apache/freemarker/onlinetester/util/LengthLimitExceededException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/onlinetester/util/LengthLimitExceededException.java b/src/main/java/org/apache/freemarker/onlinetester/util/LengthLimitExceededException.java new file mode 100644 index 0000000..f7bb00c --- /dev/null +++ b/src/main/java/org/apache/freemarker/onlinetester/util/LengthLimitExceededException.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.onlinetester.util; + +import java.io.IOException; + +/** + * Thrown by {@link LengthLimitedWriter}. + */ +public class LengthLimitExceededException extends IOException { + + private static final long serialVersionUID = 1L; + + public LengthLimitExceededException() { + super("The outout String length limit of the Writer was exceeded."); + } + +}
