I am attempting to generate a PDF file that contains object information and then attach it to another object that is stored in the database. The attachmentable plugin I am using is working now for user end attachments, but I need my system to be able to do it automatically.
I am using:
Grails 1.3.9
Attachmentable 0.3.0 http://grails.org/plugin/attachmentable
Rendering 0.4.3 http://grails.org/plugin/rendering
I have been able to generate and display the pdf, but do not know how to attach it using the attachmentable plugin. I need some way to take the generated pdf byte array and convert it to a MultipartFile for the attachmentable plugin function I call. The error I get shows that my argument types are invalid.
I save object1 and object2, then generate the pdf of object1 and try to attach it to object2.
Thanks in advance for you help!
Thing1 Controller Snippets:
ByteArrayOutputStream bytes = pdfRenderingService.render(template: "/thing1/pdf", model: [thing1: thing1])
attachmentableService.addAttachment("unknown", thing2.id, bytes)
AttachmentableService function I am attempting to call:
def addAttachment(def poster, def reference, CommonsMultipartFile file) {
addAttachment(CH.config, poster, reference, file)
}
def addAttachment(def config,
def poster,
def reference,
CommonsMultipartFile file) {
if (reference.ident() == null) {
throw new AttachmentableException(
"You must save the entity [${delegate}] before calling addAttachment.")
}
if (!file?.size) {
throw new EmptyFileException(file.name, file.originalFilename)
}
String delegateClassName = AttachmentableUtil.fixClassName(reference.class)
String posterClass = (poster instanceof String) ? poster : AttachmentableUtil.fixClassName(poster.class.name)
Long posterId = (poster instanceof String) ? 0L : poster.id
String filename = file.originalFilename
// link
def link = AttachmentLink.findByReferenceClassAndReferenceId(
delegateClassName, reference.ident())
if (!link) {
link = new AttachmentLink(
referenceClass: delegateClassName,
referenceId: reference.ident())
}
// attachment
Attachment attachment = new Attachment(
// file
name: FilenameUtils.getBaseName(filename),
ext: FilenameUtils.getExtension(filename),
length: 0L,
contentType: file.contentType,
// poster
posterClass: posterClass,
posterId: posterId,
// input
inputName: file.name)
link.addToAttachments attachment
if (!link.save(flush: true)) {
throw new AttachmentableException(
"Cannot create Attachment for arguments [$user, $file], they are invalid.")
}
// save file to disk
File diskFile = AttachmentableUtil.getFile(config, attachment, true)
file.transferTo(diskFile)
attachment.length = diskFile.length()
// interceptors
if(reference.respondsTo('onAddAttachment')) {
reference.onAddAttachment(attachment)
}
attachment.save(flush:true) // Force update so searchable can try to index it again.
return reference
}
Grails runtime error:
groovy.lang.MissingMethodException: No signature of method: com.macrobit.grails.plugins.attachmentable.services.AttachmentableService.addAttachment() is applicable for argument types: (java.lang.String, java.lang.Long, java.io.ByteArrayOutputStream) values: [unknown, 80536, %PDF-1.4 and a long string of unreadable data...]
Possible solutions: addAttachment(java.lang.Object, java.lang.Object, org.springframework.web.multipart.commons.CommonsMultipartFile), addAttachment(java.lang.Object, java.lang.Object, java.lang.Object, org.springframework.web.multipart.commons.CommonsMultipartFile)
Service Method I Added:
def customAddMethod(def poster, def reference, def pdfBytes) {
customAddMethod(CH.config, poster, reference, pdfBytes)
}
def customAddMethod(def config,
def poster,
def reference,
def pdfBytes) {
if (reference.ident() == null) {
throw new AttachmentableException(
"You must save the entity [${delegate}] before calling customAddMethod.")
}
String delegateClassName = AttachmentableUtil.fixClassName(reference.class)
String posterClass = (poster instanceof String) ? poster : AttachmentableUtil.fixClassName(poster.class.name)
Long posterId = (poster instanceof String) ? 0L : poster.id
String filename = "File Name"
// link
def link = AttachmentLink.findByReferenceClassAndReferenceId(
delegateClassName, reference.ident())
if (!link) {
link = new AttachmentLink(
referenceClass: delegateClassName,
referenceId: reference.ident())
}
// attachment
Attachment attachment = new Attachment(
// file
name: "File Name",
ext: "pdf",
length: 0L,
contentType: "application/pdf",
// poster
posterClass: posterClass,
posterId: posterId,
// input
inputName: "File Name")
link.addToAttachments attachment
if (!link.save(flush: true)) {
throw new AttachmentableException(
"Cannot create Attachment for arguments [$user, $file], they are invalid.")
}
// save file to disk
byte[] bytes = pdfBytes.toByteArray(); //convert ByteArrayOutputStream to ByteArray
File diskFile = AttachmentableUtil.getFile(config, attachment, true) //file path
FileOutputStream fos = new FileOutputStream(diskFile); //open file output stream to write to
fos.write(bytes); //write rendered pdf bytes to file
fos.flush();
fos.close();
attachment.length = diskFile.length()
// interceptors
if(reference.respondsTo('onAddAttachment')) {
reference.onAddAttachment(attachment)
}
attachment.save(flush:true) // Force update so searchable can try to index it again.
return reference
}
It looks like the AttachmentableService you referenced (from the Attachmentable plugin) assumes it's dealing with a file-upload scenario, such that you could easily grab the MultipartFile instance via request.getFile(). That's not the case for you - you're creating the file via the Rendering plugin, and you want that file attached to a domain object, right?
You could try constructing a CommonsMultipartFile instance manually by first writing the pdf bytes to disk, and then create a DiskFileItem via DiskFileItemFactory.
See this post for an example of what I'm thinking:
How to make CommonsMultipartFile from absolute file path?
Another, better, option might be to checkout that plugin's source and add a method that doesn't require you to go through those gyrations - perhaps a version of the addAttachment method that accepts a File or an OutputStream instead - and submit a PR to the plugin author.
(Looks like they're adding an 'addAttachment' method to qualifying domain objects, which also expects a CommonsMultipartFile).
Otherwise, you might just have to create your own service to basically provide the same end result, which apparently is to create an AttachmentLink and associated Attachment instance.
I am using SuiteCRM ( Sugar CRM 6.x community edition ) & want to create a custom login page and after successful login I want to redirect based on user type
tried to create some modules but there is no clear documentation except few of useful links, below are my queries :
Can we create custom modules without using module builder, if yes then what would be steps?
Do we need to write module in /module folder or /custom/module folder or on both place?
any link is also appreciated.
You can create a custom Login Page by modfying "modules/Users/login.tpl"
Custom Modules can be created through Modulebuilder or manually.
When creating modules manually it's important to use the right names.
The easiest way is a Plural name for the folder, table and Module and a singular name for the class.
Manual steps:
You need a Folder in modules/ named like you module (i.e. CAccounts)
In this folder you need a file named like the class (i.e CAccount.php) with something like that as content:
require_once('data/SugarBean.php');
require_once('include/utils.php');
class CAccount extends SugarBean{
var $table_name = 'caccounts';
var $object_name = 'CAccount';
var $module_dir = 'CAccounts';
var $new_schema = true;
var $name;
var $created_by;
var $id;
var $deleted;
var $date_entered;
var $date_modified;
var $modified_user_id;
var $modified_by_name;
function CAccount (){
parent::SugarBean();
}
function get_summary_text(){
return $this->name;
}
function bean_implements($interface)
{
switch($interface)
{
case 'ACL':return true;
}
return false;
}
}
In this folder you need a vardefs.php file:
$dictionary['CAccount'] = array(
'table'=>'caccounts',
'audited'=>false,
'fields'=>array (
//Your fielddefs here
)
);
require_once('include/SugarObjects/VardefManager.php');
VardefManager::createVardef('CAccounts','CAccount', array('basic'));
For the language and metadata folders look at any other module.
Next is a file at "custom/Extension/application/Ext/Include/CAccounts.include.php"
$moduleList[] = 'CAccounts';
$beanList['CAccounts'] = 'CAccount';
$beanFiles['CAccount'] = 'modules/CAccounts/CAccount.php';
A language file for the module name must be in "custom/Extension/application/Ext/Language/"
$app_list_strings['moduleList']['CAccounts'] = 'Custom Accounts';
To display the Module in your tabs you need to use "rebuild and repair" and then the "Display Modules and Subpanels" option in the admin menu.
For a custom module you don't need the "custom/" folder structure. Files there will be used by sugar if provided, but often there's no need for that in a custom module.
Guides about the Module Framework can be found on the sugarcrm support site:
http://support.sugarcrm.com/02_Documentation/04_Sugar_Developer/Sugar_Developer_Guide_6.5/03_Module_Framework
The following code is great for renaming a file if you know the file is in applicationStorageDirectory
var sourceFile:File = File.applicationStorageDirectory;
sourceFile = sourceFile.resolvePath("Kalimba.snd");
var destination:File = File.applicationStorageDirectory;
destination = destination.resolvePath("test.snd");
try
{
sourceFile.moveTo(destination, true);
}
catch (error:Error)
{
trace("Error:" + error.message);
}
How do you set the sourceFile if all you have is the file's native path in a string? Like this:
D:\Software\files\testList.db
This throws errors:
sourceFile = sourceFile.resolvePath("D:\Software\files\testList.db");
The idea is I want to rename a file I had previously loaded into a var. I figured I'd extract the native path to a String var, null the File var (so the OS doesn't tell me it can't be renamed while the file is opened in flash), resolve that nativePath as the sourceFile, and use moveTo to rename the file on the hard drive.
Cheers for taking a look.
EDIT:
I've set up a test AIR app with only the following in it:
import flash.events.*;
import flash.filesystem.*;
var original = File.documentsDirectory;
original = original.resolvePath("D:\\Software\\test\\October.db");
var destination:File = File.documentsDirectory;
destination = destination.resolvePath("copy.db");
original.addEventListener(Event.COMPLETE, fileMoveCompleteHandler);
original.addEventListener(IOErrorEvent.IO_ERROR, fileMoveIOErrorEventHandler);
original.moveToAsync(destination);
function fileMoveCompleteHandler(event:Event):void {
trace(event.target); // [object File]
}
function fileMoveIOErrorEventHandler(event:IOErrorEvent):void {
trace("I/O Error.");
}
This fails, as does using D:\Software\test\October.db
I guess what I want to know it - how do you do the resolvePath thing if you already know the full path?
I guess what I want to know it - how do you do the resolvePath thing if you already know the full path?
You don't AFAIK. If your path is actually d:\software\test\october.db you can set a File ref like:
var original:File = new File();
original.nativePath = "d:\software\test\october.db";
I use a module that creates customized html forms through template partails
I need to manage 4 file uploads of a form.
In actions I pull all field data like so:
$allData = $request->getParameterHolder()->getAll();
which would get me the uploaded file name, but I need to also save that file to the uploads dir.
I am trying to bind the form like so:
if( isset($customData['getAttachments']) && count($allData)){
$attachments = $formInput->bind($request->getParameter('forms_input'), $request->getFiles('forms_input'));
}
which succesfully generates the $attachments object. Sanity check on that is a var_dump($attachments);die; which dumps:
object(FormsInput)#225 (18) { ["_node":protected]=> NULL ["_id":protected]=> array(1) { ["id"]=> string(5) "32171" } ["_data":protected]=> array(6) { ["id"]=> string(5) "32171" ["form_uid"]=> string(32) "aef79b2d47722ca154788cc9c8f8de2b" ["data_array"]=> string(770) "a:20:{s:9:...
How to I extract the file and push to a directory. I tried this:
$file = $attachments->getValue('file');
if($file) {
$filename = sha1($file->getOriginalName()).$file->getExtension($file->getOriginalExtension());
$file->save(sfConfig::get('sf_upload_dir').'/'.$formTitle.'/'.$usrDir.''.$filename);
}
but it throws this error:
Unknown record property / related component "value" on "FormsInput"
How can I get the uploaded into a sf dir?
UPDATE
Tried to use the sfValidatorFile class, but I cannot figure out how to pass it the parameters from the $attachment object:
$file = new sfValidatorFile();
if($file) {
$filename = sha1($file->getOriginalName()).$file->getExtension($file->getOriginalExtension());
$file->save(sfConfig::get('sf_upload_dir').'/uploadedDir');
}
throws this:
Fatal error: Call to undefined method sfValidatorFile::save()
What you intended to do with the $file->save requires an sfValidatedFile object.
If FormsInput is an instance of sfFormObject, you can configure your form with 4 sfValidatorFile with the path you wanted in option. It will do the job you need.
How can I do different project settings for users in the same project. I have a project with some .properties file like email.properties which contains user-specific settings.I need something like user-specific enviroment settings, something like this: email..properties, and variable contains in OS enviroment or, maybe, in project file
Here's how I've done this in some projects. I create a System Property to the path of the property file. The path is outside of the project so the properties file is never committed. But to help others who are running the project for the first time, I commit a template properties file with default options.
In intellij, I start the project with a different -D option than my coworkers do and the startup options don't get committed (because we don't commit the .idea folder).
As a result a do the same: at first I created properties folder in propject root, add there file env.properties which contains current environment name (or user can add it as JVM start paramenter -Denv=<env name>. I add static class and method Properties getProperty(String fileName) which receive property file name as parameter and return all records in file as java.util.Properties.
public static Properties loadProperties(String fileName)
{
Properties properties = null;
if (propertiesMap.containsKey(fileName)) {
properties = (Properties)properties.get(fileName);
} else {
String environment = getEnvironment();
try {
properties = (new PropertiesLoaderImpl()).LoadAllPropertiesForUtilAndEnv(environment, fileName);
} catch (FileNotFoundException exception) {}
}
return properties;
}
private static String getEnvironment() {
// Проверка на наличие параметра при запуске java-машины
String environment = System.getProperty("env");
if (environment == null) {
try {
// Попытка найти файл env.properties
ResourceBundle bundle = ResourceBundle.getBundle("properties/env");
environment = bundle.getString("env");
} catch (MissingResourceException exception) {
environment = "";
}
}
return environment;
}
Property search implements that way:
1. look in ..proeprties;
2. if there is no suitable property, then look in .property;
3. else return empy.