Struts 2 File Upload Interceptor configuration problems - file-upload

I'm having two problems when trying to configure the Struts 2 File Upload Interceptor in my application. I want to change the parameter maximumSize (the default value is 2 MB, I need it to be 5 MB) and the message resource struts.messages.error.file.too.large (the app locale is pt_BR, so the message is in portuguese, not english).
The app current configuration follows:
struts.properties
struts.locale=pt_BR
struts.custom.i18n.resources=MessageResources
struts.xml
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="login" class="br.com.probank.interceptor.LoginInterceptor"/>
<interceptor-stack name="defaultLoginStack">
<interceptor-ref name="login" />
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="defaultLoginStack" />
...
</package>
...
<package name="proposta" namespace="/proposta" extends="default">
<action name="salvarAnexoProposta" method="salvarAnexoProposta" class="br.com.probank.action.AnexoPropostaAction">
<interceptor-ref name="defaultLoginStack">
<param name="fileUpload.maximumSize">5242880</param>
</interceptor-ref>
<result name="success">/jsp/listagemAnexosPropostaForm.jsp</result>
<result name="input">/jsp/crudAnexoPropostaForm.jsp</result>
<result name="error">/jsp/error.jsp</result>
<result name="redirect" type="redirect">${redirectLink}</result>
</action>
</package>
MessageResources.properties
...
struts.messages.error.file.too.large=O tamanho do arquivo...
There is nothing special about my Action implementation and my JSP code. They follow the example found http://struts.apache.org/2.1.6/docs/file-upload-interceptor.html. When I try to upload a file with more than 5 MB the app shows the message "the request was rejected because its size (6229458) exceeds the configured maximum (2097152)" - the default File Upload message with the default maximumSize value.
I try to put the message resource struts.messages.error.file.too.large in a struts-messages.properties but the message didn't change after that. What is the proper way to configure the File Upload Interceptor? I'm using Struts 2 2.1.7. Thanks in advance.

Finally solved the entire puzzle! struts.xml and MessageResource.properties were correctly configured. The problem was struts.multipart.maxSize value. This value have to be bigger than the desired upload limit (5242880 in my app), so I set it as 10000000. If struts.multipart.maxSize value is equal or less then fileUpload.maximumSize the library used by Struts 2 to do the upload stops the upload process (and writes the error message) before the file upload interceptor has a chance to do its job.

The solution provided by you isn't entirely correct in the sense that If I want strict upload limits along with i18n, this won't work. I've also created an issue with strut2 for this. Please look at the following link https://issues.apache.org/jira/browse/WW-3177. It's due to be fixed in struts2.1.9 and is already assigned to a struts team member.
In between, I'm using a hack. I browsed the struts2 source code and found the code for FileUploadInterceptor. Using that code, I created my own. Here's the code below. You can find details of problem at the link above. Hope this helps.
import java.io.File;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
public class CustomFileUploaderInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = -4764627478894962478L;
protected static final Logger LOG = LoggerFactory.getLogger(CustomFileUploaderInterceptor.class);
private static final String DEFAULT_MESSAGE = "no.message.found";
protected boolean useActionMessageBundle;
protected Long maximumSize;
protected Set<String> allowedTypesSet = Collections.emptySet();
protected Set<String> allowedExtensionsSet = Collections.emptySet();
public void setUseActionMessageBundle(String value) {
this.useActionMessageBundle = Boolean.valueOf(value);
}
/**
* Sets the allowed extensions
*
* #param allowedExtensions A comma-delimited list of extensions
*/
public void setAllowedExtensions(String allowedExtensions) {
allowedExtensionsSet = TextParseUtil.commaDelimitedStringToSet(allowedExtensions);
}
/**
* Sets the allowed mimetypes
*
* #param allowedTypes A comma-delimited list of types
*/
public void setAllowedTypes(String allowedTypes) {
allowedTypesSet = TextParseUtil.commaDelimitedStringToSet(allowedTypes);
}
/**
* Sets the maximum size of an uploaded file
*
* #param maximumSize The maximum size in bytes
*/
public void setMaximumSize(Long maximumSize) {
this.maximumSize = maximumSize;
}
/* (non-Javadoc)
* #see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
*/
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Map<String, Object> params1 = ac.getParameters();
Set<String> keySet = params1.keySet();
for(String s : keySet){
LOG.debug("Key: "+ s +", Value: " + params1.get(s).toString());
}
HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);
if (!(request instanceof MultiPartRequestWrapper)) {
if (LOG.isDebugEnabled()) {
ActionProxy proxy = invocation.getProxy();
LOG.debug(getTextMessage("struts.messages.bypass.request", new Object[]{proxy.getNamespace(), proxy.getActionName()}, ac.getLocale()));
}
return invocation.invoke();
}
ValidationAware validation = null;
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
validation = (ValidationAware) action;
}
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
if (multiWrapper.hasErrors()) {
String inputName = null;
if(multiWrapper.getFileParameterNames().hasMoreElements()){
inputName = (String)multiWrapper.getFileParameterNames().nextElement();
}
for (String error : multiWrapper.getErrors()) {
if (validation != null) {
Object[] args = new Object[]{inputName};
validation.addActionError(getTextMessage(action, "struts.messages.error.file.too.large", args, ac.getLocale()));
}
LOG.error(error);
}
}
// bind allowed Files
Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
// get the value of this input tag
String inputName = (String) fileParameterNames.nextElement();
// get the content type
String[] contentType = multiWrapper.getContentTypes(inputName);
if (isNonEmpty(contentType)) {
// get the name of the file from the input tag
String[] fileName = multiWrapper.getFileNames(inputName);
if (isNonEmpty(fileName)) {
// get a File object for the uploaded File
File[] files = multiWrapper.getFiles(inputName);
if (files != null && files.length > 0) {
List<File> acceptedFiles = new ArrayList<File>(files.length);
List<String> acceptedContentTypes = new ArrayList<String>(files.length);
List<String> acceptedFileNames = new ArrayList<String>(files.length);
String contentTypeName = inputName + "ContentType";
String fileNameName = inputName + "FileName";
for (int index = 0; index < files.length; index++) {
if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation, ac.getLocale())) {
acceptedFiles.add(files[index]);
acceptedContentTypes.add(contentType[index]);
acceptedFileNames.add(fileName[index]);
}
}
if (!acceptedFiles.isEmpty()) {
Map<String, Object> params = ac.getParameters();
params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
params.put(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
params.put(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
}
}
} else {
LOG.error(getTextMessage(action, "struts.messages.invalid.file", new Object[]{inputName}, ac.getLocale()));
}
} else {
LOG.error(getTextMessage(action, "struts.messages.invalid.content.type", new Object[]{inputName}, ac.getLocale()));
}
}
// invoke action
String result = invocation.invoke();
// cleanup
fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
String inputValue = (String) fileParameterNames.nextElement();
File[] files = multiWrapper.getFiles(inputValue);
for (File currentFile : files) {
if (LOG.isInfoEnabled()) {
LOG.info(getTextMessage(action, "struts.messages.removing.file", new Object[]{inputValue, currentFile}, ac.getLocale()));
}
if ((currentFile != null) && currentFile.isFile()) {
currentFile.delete();
}
}
}
return result;
}
/**
* Override for added functionality. Checks if the proposed file is acceptable based on contentType and size.
*
* #param action - uploading action for message retrieval.
* #param file - proposed upload file.
* #param contentType - contentType of the file.
* #param inputName - inputName of the file.
* #param validation - Non-null ValidationAware if the action implements ValidationAware, allowing for better
* logging.
* #param locale
* #return true if the proposed file is acceptable by contentType and size.
*/
protected boolean acceptFile(Object action, File file, String filename, String contentType, String inputName, ValidationAware validation, Locale locale) {
boolean fileIsAcceptable = false;
// If it's null the upload failed
if (file == null) {
String errMsg = getTextMessage(action, "struts.messages.error.uploading", new Object[]{inputName}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.error(errMsg);
} else if (maximumSize != null && maximumSize < file.length()) {
String errMsg = getTextMessage(action, "struts.messages.error.file.too.large", new Object[]{inputName, filename, file.getName(), "" + file.length()}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.error(errMsg);
} else if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) {
String errMsg = getTextMessage(action, "struts.messages.error.content.type.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.error(errMsg);
} else if ((! allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, filename))) {
String errMsg = getTextMessage(action, "struts.messages.error.file.extension.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.error(errMsg);
} else {
fileIsAcceptable = true;
}
return fileIsAcceptable;
}
/**
* #param extensionCollection - Collection of extensions (all lowercase).
* #param filename - filename to check.
* #return true if the filename has an allowed extension, false otherwise.
*/
private static boolean hasAllowedExtension(Collection<String> extensionCollection, String filename) {
if (filename == null) {
return false;
}
String lowercaseFilename = filename.toLowerCase();
for (String extension : extensionCollection) {
if (lowercaseFilename.endsWith(extension)) {
return true;
}
}
return false;
}
/**
* #param itemCollection - Collection of string items (all lowercase).
* #param item - Item to search for.
* #return true if itemCollection contains the item, false otherwise.
*/
private static boolean containsItem(Collection<String> itemCollection, String item) {
return itemCollection.contains(item.toLowerCase());
}
private static boolean isNonEmpty(Object[] objArray) {
boolean result = false;
for (int index = 0; index < objArray.length && !result; index++) {
if (objArray[index] != null) {
result = true;
}
}
return result;
}
private String getTextMessage(String messageKey, Object[] args, Locale locale) {
return getTextMessage(null, messageKey, args, locale);
}
private String getTextMessage(Object action, String messageKey, Object[] args, Locale locale) {
if (args == null || args.length == 0) {
if ( action != null && useActionMessageBundle) {
return LocalizedTextUtil.findText(action.getClass(), messageKey, locale);
}
return LocalizedTextUtil.findText(this.getClass(), messageKey, locale);
} else {
if ( action != null && useActionMessageBundle) {
return LocalizedTextUtil.findText(action.getClass(), messageKey, locale, DEFAULT_MESSAGE, args);
}
return LocalizedTextUtil.findText(this.getClass(), messageKey, locale, DEFAULT_MESSAGE, args);
}
}
}

Try this in your struts.xml, where xxxxxxxx is the limit:
<constant name="struts.multipart.maxSize" value="xxxxxxxxx" />

First use validate method in your action file........
public void validate(){
if(getFileUpload() !=null){
System.out.println("======File size validation before upload: size in bytes: "+getFileUpload().length());
if(getFileUpload().length()>202400){
//Give the size in bytes whatever you want to take
addActionError("File is too large ! Select less than 200Kb file");
}else{
addActionMessage("File Uploaded successfully!");
}
}
}
For complete code,please visit http://knowledge-serve.blogspot.com/2011/10/upload-file-using-in-struts-2.html

Related

Override the dependencies added during running a project as an Eclipse Application

I am trying to write a custom launch configuration while running a plugin project as an eclipse application. I have to run the plugin with limited dependencies. Is it possible to override methods in org.eclipse.pde.launching.EclipseApplicationLaunchConfiguration ? If yes then how do I do it ?
You can't easily override the methods in EclipseApplicationLaunchConfiguration. That would require writing a new launch configuration - probably by using the org.eclipse.debug.core.launchConfigurationTypes extension point to define a new launch type.
EclipseApplicationLaunchConfiguration always uses the settings from the current entry in the 'Eclipse Application' section of the 'Run Configurations'. You can always edit the run configuration to change the dependencies or create another run configuration with different dependencies.
To write a custom configuration file
Extend the class org.eclipse.jdt.junit.launcher.JUnitLaunchShortcut
Override the method createLaunchConfiguration
Invoking super.createLaunchConfiguration(element) will return a ILaunchConfigurationWorkingCopy
Use the copy and set your own attributes that have to be modified
Attributes can be found in IPDELauncherConstants
Eclipse by default runs all the projects found in the workspace. This behavior can be modified by using the configuration created and overriding it with custom configuration.
public class LaunchShortcut extends JUnitLaunchShortcut {
class PluginModelNameBuffer {
private List<String> nameList;
PluginModelNameBuffer() {
super();
this.nameList = new ArrayList<>();
}
void add(final IPluginModelBase model) {
this.nameList.add(getPluginName(model));
}
private String getPluginName(final IPluginModelBase model) {
IPluginBase base = model.getPluginBase();
String id = base.getId();
StringBuilder buffer = new StringBuilder(id);
ModelEntry entry = PluginRegistry.findEntry(id);
if ((entry != null) && (entry.getActiveModels().length > 1)) {
buffer.append('*');
buffer.append(model.getPluginBase().getVersion());
}
return buffer.toString();
}
#Override
public String toString() {
Collections.sort(this.nameList);
StringBuilder result = new StringBuilder();
for (String name : this.nameList) {
if (result.length() > 0) {
result.append(',');
}
result.append(name);
}
if (result.length() == 0) {
return null;
}
return result.toString();
}
}
#Override
protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(final IJavaElement element)
throws CoreException {
ILaunchConfigurationWorkingCopy configuration = super.createLaunchConfiguration(element);
configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "memory");
configuration.setAttribute(IPDELauncherConstants.USE_PRODUCT, false);
configuration.setAttribute(IPDELauncherConstants.USE_DEFAULT, false);
configuration.setAttribute(IPDELauncherConstants.AUTOMATIC_ADD, false);
addDependencies(configuration);
return configuration;
}
#SuppressWarnings("restriction")
private void addDependencies(final ILaunchConfigurationWorkingCopy configuration) throws CoreException {
PluginModelNameBuffer wBuffer = new PluginModelNameBuffer();
PluginModelNameBuffer tBuffer = new PluginModelNameBuffer();
Set<IPluginModelBase> addedModels = new HashSet<>();
String projectName = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "");
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
IPluginModelBase model = PluginRegistry.findModel(project);
wBuffer.add(model);
addedModels.add(model);
IPluginModelBase[] externalModels = PluginRegistry.getExternalModels();
for (IPluginModelBase externalModel : externalModels) {
String id = externalModel.getPluginBase().getId();
if (id != null) {
switch (id) {
case "org.eclipse.ui.ide.application":
case "org.eclipse.equinox.ds":
case "org.eclipse.equinox.event":
tBuffer.add(externalModel);
addedModels.add(externalModel);
break;
default:
break;
}
}
}
TreeSet<String> checkedWorkspace = new TreeSet<>();
IPluginModelBase[] workspaceModels = PluginRegistry.getWorkspaceModels();
for (IPluginModelBase workspaceModel : workspaceModels) {
checkedWorkspace.add(workspaceModel.getPluginBase().getId());
}
EclipsePluginValidationOperation eclipsePluginValidationOperation = new EclipsePluginValidationOperation(
configuration);
eclipsePluginValidationOperation.run(null);
while (eclipsePluginValidationOperation.hasErrors()) {
Set<String> additionalIds = DependencyManager.getDependencies(addedModels.toArray(), true, null);
if (additionalIds.isEmpty()) {
break;
}
additionalIds.stream().map(PluginRegistry::findEntry).filter(Objects::nonNull).map(ModelEntry::getModel)
.forEach(addedModels::add);
for (String id : additionalIds) {
IPluginModelBase plugin = findPlugin(id);
if (checkedWorkspace.contains(plugin.getPluginBase().getId())
&& (!plugin.getPluginBase().getId().endsWith("tests"))) {
wBuffer.add(plugin);
} else {
tBuffer.add(plugin);
}
}
eclipsePluginValidationOperation.run(null);
}
configuration.setAttribute(IPDELauncherConstants.SELECTED_WORKSPACE_PLUGINS, wBuffer.toString());
configuration.setAttribute(IPDELauncherConstants.SELECTED_TARGET_PLUGINS, tBuffer.toString());
}
protected IPluginModelBase findPlugin(final String id) {
ModelEntry entry = PluginRegistry.findEntry(id);
if (entry != null) {
return entry.getModel();
}
return null;
}
}

EF Core decimal precision for Always Encrypted column

Hello I have SQL server with setting up always encrypted feature, also I setup EF for work with always encrypted columns, but when I try to add/update, for Db manipulation I use DbContext, entry in my Db I get follow error:
Operand type clash: decimal(1,0) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '****', column_encryption_key_database_name = '****') is incompatible with decimal(6,2) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '*****', column_encryption_key_database_name = '****')
Model that I use
public class Model
{
/// <summary>
/// Payment method name
/// </summary>
[Column(TypeName = "nvarchar(MAX)")]
public string Name { get; set; }
/// <summary>
/// Payment method description
/// </summary>
[Column(TypeName = "nvarchar(MAX)")]
public string Description { get; set; }
/// <summary>
/// Fee charges for using payment method
/// </summary>
[Column(TypeName = "decimal(6,2)")]
public decimal Fee { get; set; }
}
Also I tried to specify decimal format in OnModelCreating method
builder.Entity<Model>().Property(x => x.Fee).HasColumnType("decimal(6,2)");
What I missed ?
Thanks for any advice
My colleague and I have found a workaround to the problem using the DiagnosticSource.
You must know that:
Entity Framework Core hooks itself into DiagnosticSource.
DiagnosticSource uses the observer pattern to notify its observers.
The idea is to populate the 'Precision' and 'Scale' fields of the command object (created by EFCore), in this way the call made to Sql will contain all the information necessary to correctly execute the query.
First of all, create the listener:
namespace YOUR_NAMESPACE_HERE
{
public class EfGlobalListener : IObserver<DiagnosticListener>
{
private readonly CommandInterceptor _interceptor = new CommandInterceptor();
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(DiagnosticListener value)
{
if (value.Name == DbLoggerCategory.Name)
{
value.Subscribe(_interceptor);
}
}
}
}
Where CommandInterceptor is:
namespace YOUR_NAMESPACE_HERE
{
public class CommandInterceptor : IObserver<KeyValuePair<string, object>>
{
// This snippet of code is only as example, you could maybe use Reflection to retrieve Field mapping instead of using Dictionary
private Dictionary<string, (byte Precision, byte Scale)> _tableMapping = new Dictionary<string, (byte Precision, byte Scale)>
{
{ "Table1.DecimalField1", (18, 2) },
{ "Table2.DecimalField1", (12, 6) },
{ "Table2.DecimalField2", (10, 4) },
};
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key == RelationalEventId.CommandExecuting.Name)
{
// After that EF Core generates the command to send to the DB, this method will be called
// Cast command object
var command = ((CommandEventData)value.Value).Command;
// command.CommandText -> contains SQL command string
// command.Parameters -> contains all params used in sql command
// ONLY FOR EXAMPLE PURPOSES
// This code may contain errors.
// It was written only as an example.
string table = null;
string[] columns = null;
string[] parameters = null;
var regex = new Regex(#"^INSERT INTO \[(.+)\] \((.*)\)|^VALUES \((.*)\)|UPDATE \[(.*)\] SET (.*)$", RegexOptions.Multiline);
var matches = regex.Matches(command.CommandText);
foreach (Match match in matches)
{
if(match.Groups[1].Success)
{
// INSERT - TABLE NAME
table = match.Groups[1].Value;
}
if (match.Groups[2].Success)
{
// INSERT - COLS NAMES
columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
}
if (match.Groups[3].Success)
{
// INSERT - PARAMS VALUES
parameters = match.Groups[3].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
}
if (match.Groups[4].Success)
{
// UPDATE - TABLE NAME
table = match.Groups[4].Value;
}
if (match.Groups[5].Success)
{
// UPDATE - COLS/PARAMS NAMES/VALUES
var colParams = match.Groups[5].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
}
}
// After taking all the necessary information from the sql command
// we can add Precision and Scale to all decimal parameters
foreach (var item in command.Parameters.OfType<SqlParameter>().Where(p => p.DbType == DbType.Decimal))
{
var index = Array.IndexOf<string>(parameters, item.ParameterName);
var columnName = columns.ElementAt(index);
var key = $"{table}.{columnName}";
// Add Precision and Scale, that fix our problems w/ always encrypted columns
item.Precision = _tableMapping[key].Precision;
item.Scale = _tableMapping[key].Scale;
}
}
}
}
}
Finally add in the Startup.cs the following line of code to register the listener:
DiagnosticListener.AllListeners.Subscribe(new EfGlobalListener());
Ecountered the same issue.
Adjusted #SteeBono interceptor to work with commands which contain multiple statements:
public class AlwaysEncryptedDecimalParameterInterceptor : DbCommandInterceptor, IObserver<KeyValuePair<string, object>>
{
private Dictionary<string, (SqlDbType DataType, byte? Precision, byte? Scale)> _decimalColumnSettings =
new Dictionary<string, (SqlDbType DataType, byte? Precision, byte? Scale)>
{
// MyTableDecimal
{ $"{nameof(MyTableDecimal)}.{nameof(MyTableDecimal.MyDecimalColumn)}", (SqlDbType.Decimal, 18, 6) },
// MyTableMoney
{ $"{nameof(MyTableMoney)}.{nameof(MyTableMoney.MyMoneyColumn)}", (SqlDbType.Money, null, null) },
};
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
// After that EF Core generates the command to send to the DB, this method will be called
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key == RelationalEventId.CommandExecuting.Name)
{
System.Data.Common.DbCommand command = ((CommandEventData)value.Value).Command;
Regex regex = new Regex(#"INSERT INTO \[(.+)\] \((.*)\)(\r\n|\r|\n)+VALUES \(([^;]*)\);|UPDATE \[(.*)\] SET (.*)|MERGE \[(.+)\] USING \((\r\n|\r|\n)+VALUES \(([^A]*)\) AS \w* \((.*)\)");
MatchCollection matches = regex.Matches(command.CommandText);
foreach (Match match in matches)
{
(string TableName, string[] Columns, string[] Params) commandComponents = GetCommandComponents(match);
int countOfColumns = commandComponents.Columns.Length;
// After taking all the necessary information from the sql command
// we can add Precision and Scale to all decimal parameters and set type for Money ones
for (int index = 0; index < commandComponents.Params.Length; index++)
{
SqlParameter decimalSqlParameter = command.Parameters.OfType<SqlParameter>()
.FirstOrDefault(p => commandComponents.Params[index] == p.ParameterName);
if (decimalSqlParameter == null)
{
continue;
}
string columnName = commandComponents.Columns.ElementAt(index % countOfColumns);
string settingKey = $"{commandComponents.TableName}.{columnName}";
if (_decimalColumnSettings.ContainsKey(settingKey))
{
(SqlDbType DataType, byte? Precision, byte? Scale) settings = _decimalColumnSettings[settingKey];
decimalSqlParameter.SqlDbType = settings.DataType;
if (settings.Precision.HasValue)
{
decimalSqlParameter.Precision = settings.Precision.Value;
}
if (settings.Scale.HasValue)
{
decimalSqlParameter.Scale = settings.Scale.Value;
}
}
}
}
}
}
private (string TableName, string[] Columns, string[] Params) GetCommandComponents(Match match)
{
string tableName = null;
string[] columns = null;
string[] parameters = null;
// INSERT
if (match.Groups[1].Success)
{
tableName = match.Groups[1].Value;
columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Replace("[", string.Empty)
.Replace("]", string.Empty)
.Trim()).ToArray();
parameters = match.Groups[4].Value
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim()
.Replace($"),{Environment.NewLine}(", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty))
.ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
// UPDATE
if (match.Groups[5].Success)
{
tableName = match.Groups[5].Value;
string[] colParams = match.Groups[6].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim())
.ToArray();
columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
// MERGE
if (match.Groups[7].Success)
{
tableName = match.Groups[7].Value;
parameters = match.Groups[9].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim()
.Replace($"),{Environment.NewLine}(", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty))
.ToArray();
columns = match.Groups[10].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
throw new Exception($"{nameof(AlwaysEncryptedDecimalParameterInterceptor)} was not able to parse the command");
}
}

How to add help in Web API for external library classes

Good morning,
I have a solution consisting of two projects. One is a class library, containing common classes that will be used in other projects. The other is a WebAPI 2.1 project.
I am generating the help files for the API by using the automatic help page generator, but I've noticed that when it references classes in the Common project, it doesn't use the summaries.
Is there any way of making it do this? I've searched online but I can't find any solution to this. I've also tried installing the help page generator in the Common project, but to no avail.
I had the same problem and this is just because the documentation provider takes only one xml document which is the one generated from current project (if you followed the instructions you may remember adding:
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/[YOUR XML DOCUMENT]"));
The rest of your classes and their metadata is added to a different xml document. What I did is I modified the xml documentation provider to accept multiple xml document path and search through each document for metadata related to the class been enquired about. You would need to add the xml document from the various dlls you are referencing but this definitely solved my issue. See below for the variation of the XmlDocumentationProvider:
public class XmlMultiDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
private List<XPathNavigator> _documentNavigator;
private const string TypeExpression = "/doc/members/member[#name='T:{0}']";
private const string MethodExpression = "/doc/members/member[#name='M:{0}']";
private const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
private const string FieldExpression = "/doc/members/member[#name='F:{0}']";
private const string ParameterExpression = "param[#name='{0}']";
/// <summary>
/// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class.
/// </summary>
/// <param name="documentPath">The physical path to XML document.</param>
public XmlMultiDocumentationProvider(params string[] documentPath)
{
if (documentPath == null)
{
throw new ArgumentNullException("documentPath");
}
_documentNavigator = new List<XPathNavigator>();
foreach (string s in documentPath)
{
XPathDocument xpath = new XPathDocument(s);
_documentNavigator.Add(xpath.CreateNavigator());
}
}
public string GetDocumentation(HttpControllerDescriptor controllerDescriptor)
{
XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType);
return GetTagValue(typeNode, "summary");
}
public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor)
{
XPathNavigator methodNode = GetMethodNode(actionDescriptor);
return GetTagValue(methodNode, "summary");
}
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
return null;
}
public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor)
{
XPathNavigator methodNode = GetMethodNode(actionDescriptor);
return GetTagValue(methodNode, "returns");
}
public string GetDocumentation(MemberInfo member)
{
string memberName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(member.DeclaringType), member.Name);
string expression = member.MemberType == MemberTypes.Field ? FieldExpression : PropertyExpression;
string selectExpression = String.Format(CultureInfo.InvariantCulture, expression, memberName);
XPathNavigator propertyNode = null;
foreach(XPathNavigator navigator in _documentNavigator )
{
XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
if (temp != null)
{
propertyNode = temp;
break;
}
}
return GetTagValue(propertyNode, "summary");
}
public string GetDocumentation(Type type)
{
XPathNavigator typeNode = GetTypeNode(type);
return GetTagValue(typeNode, "summary");
}
private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor)
{
ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor;
if (reflectedActionDescriptor != null)
{
string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo));
foreach (XPathNavigator navigator in _documentNavigator)
{
XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
if (temp != null)
{
return temp;
}
}
}
return null;
}
private static string GetMemberName(MethodInfo method)
{
string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(method.DeclaringType), method.Name);
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 0)
{
string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray();
name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames));
}
return name;
}
private static string GetTagValue(XPathNavigator parentNode, string tagName)
{
if (parentNode != null)
{
XPathNavigator node = parentNode.SelectSingleNode(tagName);
if (node != null)
{
return node.Value.Trim();
}
}
return null;
}
private XPathNavigator GetTypeNode(Type type)
{
string controllerTypeName = GetTypeName(type);
string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName);
foreach (XPathNavigator navigator in _documentNavigator)
{
XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
if (temp != null)
{
return temp;
}
}
return null;
}
private static string GetTypeName(Type type)
{
string name = type.FullName;
if (type.IsGenericType)
{
// Format the generic type name to something like: Generic{System.Int32,System.String}
Type genericType = type.GetGenericTypeDefinition();
Type[] genericArguments = type.GetGenericArguments();
string genericTypeName = genericType.FullName;
// Trim the generic parameter counts from the name
genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray();
name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames));
}
if (type.IsNested)
{
// Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax.
name = name.Replace("+", ".");
}
return name;
}
}
You can get the idea or simply use the whole class at your discretion. Just remember to replace in your HelpPageConfig -> SetDocumentationProvider call the class name and add the path to the various xml documents.

How to validate file upload? [Play Framework]

I have a simple bean, like that:
package models;
import play.data.validation.Constraints;
public class Upload
{
#Constraints.Required
#Constraints.MinLength(4)
#Constraints.MaxLength(40)
public String name;
#Constraints.Required
public String inputFile;
}
and form, like that:
#form(action = routes.Application.submit(), 'enctype -> "multipart/form-data") {
#inputText(
uploadForm("name"),
'_label -> "Name"
)
#inputFile(
uploadForm("inputFile"),
'_label -> "Queries"
)
}
What is the best way to validate inputFile?
Is it possible do to that with annotations?
#Required constraint does not work at all.
I want it to be selected + add some limitation on size.
make your form like:
<input type="file" name="inputFile">
In you submit method add this:
// from official documentation
public static Result submit() {
MultipartFormData body = request().body().asMultipartFormData();
FilePart file = body.getFile("inputFile");
if (inputFile != null) {
String fileName = picture.getFilename();
String contentType = picture.getContentType();
File file = picture.getFile();
// method the check size
if(!validateFileSize){
return redirect(routes.Application.index()); // error in file size
}
return ok("File uploaded");
} else {
// here comes the validation
flash("error", "Missing file");
return redirect(routes.Application.index());
}
}
Something like the following, maybe?
MultipartFormData body = request().body().asMultipartFormData();
if (!body.getFiles().isEmpty()) {
// do your work
}

Can I use RE-Captcha with Wicket?

Can I use recaptcha with apache wicket 1.5.3? Is there some good example?
In terms of Google reCAPTCHA v2, you can just follow its instruction, which is straightforward.
First of all, go to Google reCAPTCHA, and register your application there. Then you can work on the client and server sides respectively as below:
On the client side (see ref)
First, paste the snippet below <script...></script> before the closing tag on your HTML template, for example:
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
Then paste the snippet below <div...></div> at the end of the where you want the reCAPTCHA widget to appear, for example:
<div class="g-recaptcha" data-sitekey="{your public site key given by Google reCAPTCHA}"></div>
</form>
That's all on the client side.
On the server side (see ref)
When a user submits the form, you need to get the user response token from the g-recaptcha-response POST parameter. Then use the token, together with the secret key given by Google reCAPTCHA, and optional with the user's IP address, and then POST a request to the Google reCAPTCHA API. You'll then get the response from Google reCAPTHA, indicating whether the form verification succeeds or fails.
Below is the sample code on the server side.
User summits a Wicket form (Wicket 6 in this example):
protected void onSubmit() {
HttpServletRequest httpServletRequest = (HttpServletRequest)getRequest().getContainerRequest();
boolean isValidRecaptcha = ReCaptchaV2.getInstance().verify(httpServletRequest);
if(!isValidRecaptcha){
verificationFailedFeedbackPanel.setVisible(true);
return;
}
// reCAPTCHA verification succeeded, carry on handling form submission
...
}
ReCaptchaV2.java (Just Java, web framework independent)
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class ReCaptchaV2 {
private final static Logger logger = Logger.getLogger(ReCaptchaV2.class);
private final static String VERIFICATION_URL = "https://www.google.com/recaptcha/api/siteverify";
private final static String SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
private static ReCaptchaV2 instance = new ReCaptchaV2();
private ReCaptchaV2() {}
public static ReCaptchaV2 getInstance() {
return instance;
}
private boolean verify(String recaptchaUserResponse, String remoteip) {
boolean ret = false;
if (recaptchaUserResponse == null) {
return ret;
}
RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("secret", SECRET);
map.add("response", recaptchaUserResponse);
if (remoteip != null) {
map.add("remoteip", remoteip);
}
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<String> res = null;
try {
res = rt.exchange(VERIFICATION_URL, HttpMethod.POST, httpEntity, String.class);
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
}
if (res == null || res.getBody() == null) {
return ret;
}
Response response = null;
try {
response = new ObjectMapper().readValue(res.getBody(), Response.class);
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
}
if (response != null && response.isSuccess()) {
ret = true;
}
logger.info("Verification result: " + ret);
return ret;
}
public boolean verify(HttpServletRequest httpServletRequest) {
boolean ret = false;
if (httpServletRequest == null) {
return ret;
}
String recaptchaUserResponse = httpServletRequest.getParameter("g-recaptcha-response");
String remoteAddr = httpServletRequest.getRemoteAddr();
return verify(recaptchaUserResponse, remoteAddr);
}
}
Response.java (Java POJO)
public class Response {
private String challenge_ts;
private String hostname;
private boolean success;
public Response() {}
public String getChallenge_ts() {
return challenge_ts;
}
public void setChallenge_ts(String challenge_ts) {
this.challenge_ts = challenge_ts;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
#Override
public String toString() {
return "ClassPojo [challenge_ts = " + challenge_ts + ", hostname = " + hostname + ", success = " + success + "]";
}
}
Have you read this?
I have added the guide here in case page disappears.
Usage
We will create a panel called RecaptchaPanel. In order to use this component to your application all you'll have to do is this:
add(new RecaptchaPanel("recaptcha"));
and of course, add the component in your markup:
<div wicket:id="recaptcha"></div>
Implementation
Implementation is simple. All you have to do, is to follow several steps:
Add recaptcha dependency to your project
<dependency>
<groupid>net.tanesha.recaptcha4j</groupid>
<artifactid>recaptcha4j</artifactid>
<version>0.0.7</version>
</dependency>
This library hides the implementation details and expose an API for dealing with recaptcha service.
Create associated markup (RecaptchaPanel.html)
<wicket:panel><div wicket:id="captcha"></div></wicket:panel>
Create RecaptchaPanel.java
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
/**
* Displays recaptcha widget. It is configured using a pair of public/private keys which can be registered at the
* following location:
*
* https://www.google.com/recaptcha/admin/create
* <br>
* More details about recaptcha API: http://code.google.com/apis/recaptcha/intro.html
*
* #author Alex Objelean
*/
#SuppressWarnings("serial")
public class RecaptchaPanel extends Panel {
private static final Logger LOG = LoggerFactory.getLogger(RecaptchaPanel.class);
#SpringBean
private ServiceProvider serviceProvider;
public RecaptchaPanel(final String id) {
super(id);
final ReCaptcha recaptcha = ReCaptchaFactory.newReCaptcha(serviceProvider.getSettings().getRecaptchaPublicKey(),
serviceProvider.getSettings().getRecaptchaPrivateKey(), false);
add(new FormComponent<void>("captcha") {
#Override
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
replaceComponentTagBody(markupStream, openTag, recaptcha.createRecaptchaHtml(null, null));
}
#Override
public void validate() {
final WebRequest request = (WebRequest)RequestCycle.get().getRequest();
final String remoteAddr = request.getHttpServletRequest().getRemoteAddr();
final ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
reCaptcha.setPrivateKey(serviceProvider.getSettings().getRecaptchaPrivateKey());
final String challenge = request.getParameter("recaptcha_challenge_field");
final String uresponse = request.getParameter("recaptcha_response_field");
final ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr, challenge, uresponse);
if (!reCaptchaResponse.isValid()) {
LOG.debug("wrong captcha");
error("Invalid captcha!");
}
}
});
}
}
</void>
Things to notice:
ServiceProvider - is a spring bean containing reCaptcha configurations (public key and private key). These keys are different depending on the domain where your application is deployed (by default works for any key when using localhost domain). You can generate keys here: https://www.google.com/recaptcha/admin/create
The RecaptchaPanel contains a FormComponent, which allows implementing validate method, containing the validation logic.
Because reCaptcha use hardcoded values for hidden fields, this component cannot have multiple independent instances on the same page.
Maybe the xaloon wicket components can be a solution for you. They have a Recaptcha plugin.