/*
* gvNIX is an open source tool for rapid application development (RAD).
* Copyright (C) 2010 Generalitat Valenciana
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gvnix.addon.datatables.addon;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.addon.datatables.addon.listeners.DatatablesDependencyListener;
import org.gvnix.addon.datatables.annotations.GvNIXDatatables;
import org.gvnix.addon.web.mvc.addon.jquery.JQueryOperations;
import org.gvnix.support.MessageBundleUtils;
import org.gvnix.support.OperationUtils;
import org.gvnix.support.WebProjectUtils;
import org.gvnix.support.dependenciesmanager.DependenciesVersionManager;
import org.gvnix.web.i18n.roo.addon.ValencianCatalanLanguage;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.jpa.annotations.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.propfiles.PropFileOperations;
import org.springframework.roo.addon.web.mvc.controller.addon.WebMvcOperations;
import org.springframework.roo.addon.web.mvc.controller.annotations.scaffold.RooWebScaffold;
import org.springframework.roo.addon.web.mvc.jsp.i18n.I18n;
import org.springframework.roo.addon.web.mvc.jsp.i18n.I18nSupport;
import org.springframework.roo.addon.web.mvc.jsp.i18n.languages.SpanishLanguage;
import org.springframework.roo.addon.web.mvc.jsp.menu.MenuOperations;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.operations.AbstractOperations;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.FeatureNames;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.Property;
import org.springframework.roo.project.Repository;
import org.springframework.roo.project.maven.Pom;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.DomUtils;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Implementation of operations this add-on offers.
*
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
* href="http://www.dgti.gva.es">General Directorate for Information
* Technologies (DGTI)</a>
* @since 1.1
*/
@Component
@Service
public class DatatablesOperationsImpl extends AbstractOperations implements
DatatablesOperations {
private static final Logger LOGGER = HandlerUtils
.getLogger(DatatablesOperationsImpl.class);
private static final String ARGUMENT_RESOLVERS = "argument-resolvers";
private static final JavaType SCAFFOLD_ANNOTATION = new JavaType(
RooWebScaffold.class.getName());
private static final JavaType DATATABLES_ANNOTATION = new JavaType(
GvNIXDatatables.class.getName());
private static final JavaType JQUERY_ANNOTATION = new JavaType(
"org.gvnix.addon.web.mvc.annotations.jquery.GvNIXWebJQuery");
private static final JavaType JPA_ACTIVE_RECORD_ANNOTATION = new JavaType(
RooJpaActiveRecord.class.getName());
private static final String DATATABLES_CRITERIA_RESOLVER = "com.github.dandelion.datatables.extras.spring3.ajax.DatatablesCriteriasResolver";
private ComponentContext cContext;
private ProjectOperations projectOperations;
private TypeLocationService typeLocationService;
private TypeManagementService typeManagementService;
private MetadataService metadataService;
private MenuOperations menuOperations;
private I18nSupport i18nSupport;
private PropFileOperations propFileOperations;
private WebMvcOperations webMvcOperations;
/**
* Uses to ensure that dependencyListener will be loaded
*/
@Reference
private DatatablesDependencyListener dependencyListener;
private WebProjectUtils webProjectUtils;
private MessageBundleUtils messageBundleUtils;
private OperationUtils operationUtils;
/**
* Update dependencies if is needed
*
* @param context the component context
*/
protected void activate(ComponentContext componentContext) {
cContext = componentContext;
context = cContext.getBundleContext();
/*super.activate(componentContext);
// Check if setup is already executed
if (isAddAvailable()) {
// Update dependencies
setupProjectPom();
}*/
}
/** {@inheritDoc} */
public boolean isAddAvailable() {
return getProjectOperations().isFeatureInstalledInFocusedModule(
FEATURE_NAME_GVNIX_DATATABLES);
}
/** {@inheritDoc} */
public boolean isSetupAvailable() {
return getProjectOperations().isFeatureInstalledInFocusedModule(
FeatureNames.MVC)
&& getProjectOperations().isFeatureInstalledInFocusedModule(
JQueryOperations.FEATURE_NAME_GVNIX_JQUERY)
&& !getProjectOperations().isFeatureInstalledInFocusedModule(
FEATURE_NAME_GVNIX_DATATABLES);
}
/** {@inheritDoc} */
public boolean isUpdateTagsAvailable() {
return isAddAvailable();
}
/** {@inheritDoc} */
public void annotateController(JavaType javaType, boolean ajax) {
annotateController(javaType, ajax, "", false, null);
}
/** {@inheritDoc} */
public void annotateController(JavaType javaType, boolean ajax,
String mode, boolean inlineEditing, JavaSymbolName baseFilter) {
Validate.notNull(javaType, "Controller required");
ClassOrInterfaceTypeDetails existing = getControllerDetails(javaType);
// Get controller annotation
final AnnotationMetadata controllerAnnotation = MemberFindingUtils
.getAnnotationOfType(existing.getAnnotations(),
SCAFFOLD_ANNOTATION);
Validate.isTrue(
controllerAnnotation != null,
javaType.getFullyQualifiedTypeName()
.concat(": Operation for @RooWebScaffold annotated controllers only."));
final AnnotationMetadata jQueryAnnotation = MemberFindingUtils
.getAnnotationOfType(existing.getAnnotations(),
JQUERY_ANNOTATION);
Validate.isTrue(
jQueryAnnotation != null,
javaType.getFullyQualifiedTypeName()
.concat(": Operation for @GvNIXWebJQuery annotated controllers only."));
final boolean isDatatablesAnnotated = MemberFindingUtils
.getAnnotationOfType(existing.getAnnotations(),
DATATABLES_ANNOTATION) != null;
// Check is JPA active record (currently add-on only supports this
// entities)
JavaType entityValueType = getControllerFormBackingObject(controllerAnnotation);
ClassOrInterfaceTypeDetails entity = getTypeLocationService()
.getTypeDetails(entityValueType);
final boolean isActiveRecord = MemberFindingUtils.getAnnotationOfType(
entity.getAnnotations(), JPA_ACTIVE_RECORD_ANNOTATION) != null;
if (!isActiveRecord) {
LOGGER.warning("No JPA active record controller found, probably you'll need to customize 'entityManagerProvider' bean in 'webmvc-config.xml' to provide entity manager resolution.");
}
// TODO support JPA repositories
// Test if the annotation already exists on the target type
if (!isDatatablesAnnotated) {
ClassOrInterfaceTypeDetailsBuilder builder = new ClassOrInterfaceTypeDetailsBuilder(
existing);
AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(
DATATABLES_ANNOTATION);
annotationBuilder.addBooleanAttribute("ajax", ajax);
if (inlineEditing) {
annotationBuilder.addBooleanAttribute("inlineEditing",
inlineEditing);
}
if (StringUtils.isNotBlank(mode)) {
annotationBuilder.addStringAttribute("mode", mode);
}
if (baseFilter != null) {
annotationBuilder.addStringAttribute("baseFilter",
baseFilter.toString());
}
// Add annotation to target type
builder.addAnnotation(annotationBuilder.build());
// Save changes to disk
getTypeManagementService()
.createOrUpdateTypeOnDisk(builder.build());
// doUpdateListMenuUrl(javaType, controllerAnnotation);
}
}
/** {@inheritDoc} */
public void annotateDetailController(JavaType javaType, String property) {
Validate.notNull(javaType, "Controller required");
Validate.notBlank(property, "Property required");
// Get java type controller
ClassOrInterfaceTypeDetails existing = getControllerDetails(javaType);
// Get controller datatables annotation
AnnotationMetadata datatablesAnnotation = MemberFindingUtils
.getAnnotationOfType(existing.getAnnotations(),
DATATABLES_ANNOTATION);
// If no datatables annotation: user message and exit.
// Detail only allowed when datatables already applied into controller.
Validate.isTrue(
datatablesAnnotation != null,
"A detail datatables only can be added into an already datatables controller.\n"
+ "Please, run 'web mvc datatables add' before or select another type.");
// TODO Validate controller related with property already has datatables
@SuppressWarnings({ "unchecked", "rawtypes" })
ArrayAttributeValue<StringAttributeValue> detailFieldsAttributesOld = (ArrayAttributeValue) datatablesAnnotation
.getAttribute(DatatablesConstants.DTTBL_ANN_DET_FIELDS_ATTR);
// Get java type controller builder
ClassOrInterfaceTypeDetailsBuilder builder = new ClassOrInterfaceTypeDetailsBuilder(
existing);
// Initialize string attributes list for detail fields
final List<StringAttributeValue> detailFieldsAttributes = new ArrayList<StringAttributeValue>();
// Create a string attribute for property
final StringAttributeValue detailFieldAttribute = new StringAttributeValue(
new JavaSymbolName("__ARRAY_ELEMENT__"), property);
// Add old (if exists) string attributes into list
if (detailFieldsAttributesOld != null) {
detailFieldsAttributes.addAll(detailFieldsAttributesOld.getValue());
}
// Add new string attribute into list if not included already
if (detailFieldsAttributesOld == null
|| !detailFieldsAttributesOld.getValue().contains(
detailFieldAttribute)) {
detailFieldsAttributes.add(detailFieldAttribute);
}
else {
throw new IllegalArgumentException(
"Property was already included in the controller.");
}
// Create "detailFields" attributes array from string attributes list
ArrayAttributeValue<StringAttributeValue> detailFieldsArray = new ArrayAttributeValue<StringAttributeValue>(
new JavaSymbolName(
DatatablesConstants.DTTBL_ANN_DET_FIELDS_ATTR),
detailFieldsAttributes);
// Get datatables annotation builder and add "detailFields"
AnnotationMetadataBuilder datatablesAnnotationBuilder = new AnnotationMetadataBuilder(
datatablesAnnotation);
datatablesAnnotationBuilder.addAttribute(detailFieldsArray);
datatablesAnnotationBuilder.build();
// Update annotation into controller
builder.updateTypeAnnotation(datatablesAnnotationBuilder);
// Save controller changes to disk
getTypeManagementService().createOrUpdateTypeOnDisk(builder.build());
}
private ClassOrInterfaceTypeDetails getControllerDetails(JavaType controller) {
ClassOrInterfaceTypeDetails existing = getTypeLocationService()
.getTypeDetails(controller);
Validate.notNull(existing, "Can't get Type details");
return existing;
}
/**
* Updates de list.jspx page of target controller to use datatables
* component.
*/
public void updateControllerJspPages(JavaType controller,
DatatablesMetadata datatablesMetadata) {
Validate.notNull(datatablesMetadata, "Datatables metadata required");
String controllerPath = datatablesMetadata
.getWebScaffoldAnnotationValues().getPath();
updateListJspx(controller, controllerPath);
updateCreateJspx(controller, controllerPath);
updateShowJspx(controller, controllerPath);
updateUpdateJspx(controller, controllerPath);
// Note there is no need to update finder jspx because this add-on
// uses "finderNameParam" feature provided by JQuery MVC add-on
}
/**
* Updates the list.jspx page of target controller to use datatables
* component.
*
* @param controller
* @param controllerPath
*/
private void updateListJspx(JavaType controller, String controllerPath) {
Map<String, String> uriMap = new HashMap<String, String>(2);
uriMap.put("xmlns:table", DatatablesConstants.URN_TAG_DATATABLES);
uriMap.put("xmlns:page", DatatablesConstants.URN_TAG_DATATABLES);
updateJspx(controller, controllerPath, uriMap, "list");
}
/**
* Updates the create.jspx page of target controller to use detail
* datatables component.
*
* @param controller
* @param controllerPath
*/
private void updateCreateJspx(JavaType controller, String controllerPath) {
Map<String, String> uriMap = new HashMap<String, String>(1);
uriMap.put("xmlns:form", DatatablesConstants.URN_TAG_DATATABLES);
updateJspx(controller, controllerPath, uriMap, "create");
}
/**
* Updates the show.jspx page of target controller to use detail datatables
* component.
*
* @param controller
* @param controllerPath
*/
private void updateShowJspx(JavaType controller, String controllerPath) {
Map<String, String> uriMap = new HashMap<String, String>(1);
uriMap.put("xmlns:page", DatatablesConstants.URN_TAG_DATATABLES);
updateJspx(controller, controllerPath, uriMap, "show");
}
/**
* Updates the update.jspx page of target controller to use detail
* datatables component.
*
* @param controller
* @param controllerPath
*/
private void updateUpdateJspx(JavaType controller, String controllerPath) {
Map<String, String> uriMap = new HashMap<String, String>(1);
uriMap.put("xmlns:form", DatatablesConstants.URN_TAG_DATATABLES);
updateJspx(controller, controllerPath, uriMap, "update");
}
/**
* @param controller
* @param controllerPath
* @param uriMap
* @param jspxName
*/
private void updateJspx(JavaType controller, String controllerPath,
Map<String, String> uriMap, String jspxName) {
Validate.notBlank(controllerPath,
"Path is not specified in the @RooWebScaffold annotation for '"
+ controller.getSimpleTypeName() + "'");
Validate.isTrue(controllerPath != null && !controllerPath.isEmpty(),
"Path is not specified in the @RooWebScaffold annotation for '"
+ controller.getSimpleTypeName() + "'");
if (controllerPath != null) {
getWebProjectUtils().updateTagxUriInJspx(controllerPath, jspxName,
uriMap, getProjectOperations(), fileManager);
}
}
/**
* Gets attribute value {@code path} from a controller annotation
*
* @param controllerAnnotation
* @return
*/
private String getControllerPath(AnnotationMetadata controllerAnnotation) {
AnnotationAttributeValue<Object> controllerPathAttribute = controllerAnnotation
.getAttribute("path");
if (controllerPathAttribute == null) {
return null;
}
String controllerPath = (String) controllerPathAttribute.getValue();
return controllerPath;
}
/**
* Gets attribute value {@code formBackingObject} from a controller
* annotation
*
* @param controllerAnnotation
* @return
*/
private JavaType getControllerFormBackingObject(
AnnotationMetadata controllerAnnotation) {
AnnotationAttributeValue<Object> controllerAttribute = controllerAnnotation
.getAttribute("formBackingObject");
if (controllerAttribute == null) {
return null;
}
JavaType controllerPath = (JavaType) controllerAttribute.getValue();
return controllerPath;
}
/** {@inheritDoc} */
public void annotateAll(boolean ajax) {
// Locate all controllers and annotate it
for (JavaType type : getTypeLocationService().findTypesWithAnnotation(
SCAFFOLD_ANNOTATION)) {
annotateController(type, ajax);
}
}
/** {@inheritDoc} */
public void setup(JavaPackage webPackage) {
// Setup repository and dependencies
setupProjectPom();
// Install all artifacts
updateTags();
// Copy properties file
copyPropertiesFile();
// Add required i18n keys
addI18nKeys();
// Add all js to
// WEB-INF/tags/util/load-scripts.tagx
addJSToLoadScriptsTag();
// Update webmvc-config.xml
updateWebMvcConfigFile(webPackage);
// Update web.xml
updateWebXmlFile();
// Install EntityManagerProvider
installEntityManagerProvider();
// Installing bootstrap components if necessary
if (getProjectOperations().isFeatureInstalledInFocusedModule(
"gvnix-bootstrap")) {
updateDatatablesAddonToBootstrap();
}
}
/**
* Add a bean in webmvc-config.xml with EMP implementation file
*/
private void installEntityManagerProvider() {
// Load webmvc-config.xml
LogicalPath webappPath = getWebProjectUtils().getWebappPath(
getProjectOperations());
String webMvcXmlPath = getProjectOperations().getPathResolver()
.getIdentifier(webappPath, "WEB-INF/spring/webmvc-config.xml");
Validate.isTrue(fileManager.exists(webMvcXmlPath),
"webmvc-config.xml not found");
InputStream inputStream = fileManager.getInputStream(webMvcXmlPath);
Document docXml = XmlUtils.readXml(inputStream);
// Getting root element
Element document = docXml.getDocumentElement();
// If our bean doesn't exist then create it
Element beanElement = XmlUtils.findFirstElement(
"bean[@id='entityManagerProvider']", document);
if (beanElement == null) {
beanElement = docXml.createElement("bean");
beanElement.setAttribute("id", "entityManagerProvider");
beanElement.setAttribute("class",
"org.gvnix.web.datatables.util.EntityManagerProviderImpl");
document.appendChild(beanElement);
XmlUtils.writeXml(fileManager.updateFile(webMvcXmlPath)
.getOutputStream(), docXml);
}
}
/**
* Update project pom: install repositories and dependencies
*/
private void setupProjectPom() {
// Get add-on configuration file
Element configuration = XmlUtils.getConfiguration(getClass());
// Install the add-on repository needed
List<Element> repos = XmlUtils.findElements(
"/configuration/gvnix/repositories/repository", configuration);
for (Element repo : repos) {
getProjectOperations().addRepositories(
getProjectOperations().getFocusedModuleName(),
Collections.singleton(new Repository(repo)));
}
// Install properties
List<Element> properties = XmlUtils.findElements(
"/configuration/gvnix/properties/*", configuration);
for (Element property : properties) {
getProjectOperations().addProperty(
getProjectOperations().getFocusedModuleName(),
new Property(property));
}
// Install dependencies
List<Element> depens = XmlUtils.findElements(
"/configuration/gvnix/dependencies/dependency", configuration);
DependenciesVersionManager.manageDependencyVersion(
getMetadataService(), getProjectOperations(), depens);
}
@Override
public void updateTags() {
PathResolver pathResolver = getProjectOperations().getPathResolver();
LogicalPath webappPath = getWebProjectUtils().getWebappPath(
getProjectOperations());
// images
getOperationUtils().updateDirectoryContents("images/datatables/*.*",
pathResolver.getIdentifier(webappPath, "/images/datatables"),
fileManager, cContext, getClass());
// install js
copyDirectoryContents("scripts/datatables/*.js",
pathResolver.getIdentifier(webappPath, "/scripts/datatables"),
true);
copyDirectoryContents("scripts/datatables/README.txt",
pathResolver.getIdentifier(webappPath, "/scripts/datatables"),
true);
// install js i18n
copyDirectoryContents("scripts/datatables/i18n/*.txt",
pathResolver.getIdentifier(webappPath,
"/scripts/datatables/i18n"), true);
// install css
copyDirectoryContents("styles/datatables/*.css",
pathResolver.getIdentifier(webappPath, "/styles/datatables"),
true);
// install tags
copyDirectoryContents("tags/datatables/*.tagx",
pathResolver.getIdentifier(webappPath,
"/WEB-INF/tags/datatables"), true);
}
@Deprecated
public void updateListMenuUrl(JavaType controller) {
Validate.notNull(controller, "Controller required");
// Obtain ClassOrInterfaceTypeDetails for this java type
ClassOrInterfaceTypeDetails existing = getControllerDetails(controller);
// Get controller annotation
final AnnotationMetadata controllerAnnotation = MemberFindingUtils
.getAnnotationOfType(existing.getAnnotations(),
SCAFFOLD_ANNOTATION);
doUpdateListMenuUrl(controller, controllerAnnotation);
}
/**
* Remove <code>page</code> and <code>size</code> parameters from list menu
* link for target controller
*
* @param controllerDetails
* @param controllerAnnotation
* @deprecated
*/
public void doUpdateListMenuUrl(JavaType controller,
AnnotationMetadata controllerAnnotation) {
String controllerPath = getControllerPath(controllerAnnotation);
JavaType formBackingType = getControllerFormBackingObject(controllerAnnotation);
Validate.notNull(formBackingType,
"formBackingObject is not specified in the @RooWebScaffold annotation for '"
+ controller.getSimpleTypeName() + "'");
final JavaSymbolName categoryName = new JavaSymbolName(
formBackingType.getSimpleTypeName());
final LogicalPath webappPath = LogicalPath.getInstance(
Path.SRC_MAIN_WEBAPP, "");
final JavaSymbolName listMenuItemId = new JavaSymbolName("list");
menuOperations.cleanUpMenuItem(categoryName, listMenuItemId,
MenuOperations.DEFAULT_MENU_ITEM_PREFIX, webappPath);
getMenuOperations().addMenuItem(categoryName, listMenuItemId,
"global_menu_list", "/" + controllerPath,
MenuOperations.DEFAULT_MENU_ITEM_PREFIX, webappPath);
}
public void addJSToLoadScriptsTag() {
List<Pair<String, String>> cssList = new ArrayList<Pair<String, String>>();
List<Pair<String, String>> jsList = new ArrayList<Pair<String, String>>();
// Add jquery.datatables.css url resolution
cssList.add(new ImmutablePair<String, String>(
"css_jquery_datatables_url",
"/resources/styles/datatables/jquery.dataTables.css"));
// Add gvnix.dataTables.css url resolution
cssList.add(new ImmutablePair<String, String>(
"css_gvnix_datatables_url",
"/resources/styles/datatables/gvnix.dataTables.css"));
// Add jquery.datatables.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_url",
"/resources/scripts/datatables/jquery.dataTables.min.js"));
// Add jquery.dataTables.ext.gvnix.selection.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_selection_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.selection.js"));
// Add jquery.dataTables.ext.gvnix.editing.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_edit_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.editing.js"));
// Add jquery.dataTables.ext.gvnix.rowclick.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_rowclik_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.rowclick.js"));
// Add jquery.dataTables.ext.gvnix.rowontop.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_rowontop_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.rowontop.js"));
// Add jquery.dataTables.ext.gvnix.detail.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_detail_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.detail.js"));
// Add jquery.dataTables.ext.gvnix.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_custom_api_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.js"));
// Add jquery.dataTables.ext.gvnix.js
jsList.add(new ImmutablePair<String, String>(
"js_jquery_datatables_advancedfilter_url",
"/resources/scripts/datatables/jquery.dataTables.ext.gvnix.advancedfilter.js"));
getWebProjectUtils().addJsAndCssToLoadScriptsTag(cssList, jsList,
getProjectOperations(), fileManager);
}
@Override
public void updateWebMvcConfigFile(JavaPackage destinationPackage) {
LogicalPath webappPath = getWebProjectUtils().getWebappPath(
getProjectOperations());
String webMvcXmlPath = getProjectOperations().getPathResolver()
.getIdentifier(webappPath, "WEB-INF/spring/webmvc-config.xml");
Validate.isTrue(fileManager.exists(webMvcXmlPath),
"webmvc-config.xml not found");
MutableFile webMvcXmlMutableFile = null;
Document webMvcXml;
try {
webMvcXmlMutableFile = fileManager.updateFile(webMvcXmlPath);
webMvcXml = XmlUtils.getDocumentBuilder().parse(
webMvcXmlMutableFile.getInputStream());
}
catch (Exception e) {
throw new IllegalStateException(e);
}
Element root = webMvcXml.getDocumentElement();
// Get annotation-driven for conversion service
List<Element> annotationDrivenFound = XmlUtils
.findElements(
"annotation-driven[@conversion-service='applicationConversionService']",
root);
if (annotationDrivenFound.isEmpty()) {
// Delegate on Roo install operation
if (destinationPackage == null) {
throw new IllegalStateException(
"No conversion service found on webmvc-config.xml: package parameter is required.");
}
getWebMvcOperations().installConversionService(destinationPackage);
// commit fileManager changes (so get final webmvc-config.xml
// content)
fileManager.commit();
// reload xml file after Roo update it
try {
webMvcXmlMutableFile = fileManager.updateFile(webMvcXmlPath);
webMvcXml = XmlUtils.getDocumentBuilder().parse(
webMvcXmlMutableFile.getInputStream());
}
catch (Exception e) {
throw new IllegalStateException(e);
}
root = webMvcXml.getDocumentElement();
// Get annotation-driven for conversion service
annotationDrivenFound = XmlUtils
.findElements(
"annotation-driven[@conversion-service='applicationConversionService']",
root);
}
Validate.isTrue(
!annotationDrivenFound.isEmpty(),
"mvc:annotation-driven conversion-service=\"applicationConversionService\" tag not found in webmvc-config.xml");
Validate.isTrue(
annotationDrivenFound.size() == 1,
"too much (1 expected) mvc:annotation-driven conversion-service=\"applicationConversionService\" tag found in webmvc-config.xml");
Element annotationDriven = annotationDrivenFound.get(0);
Element argumentResolver = null;
Element bean = null;
boolean addBean = false;
// Check tag contents
if (!annotationDriven.hasChildNodes()) {
// No children: add bean
addBean = true;
}
else {
// Look for bean
bean = XmlUtils.findFirstElement("argument-resolvers/bean[@class='"
.concat(DATATABLES_CRITERIA_RESOLVER).concat("']"),
annotationDriven);
if (bean == null) {
addBean = true;
// get argument-resolvers tag (if any)
argumentResolver = XmlUtils.findFirstElement(
ARGUMENT_RESOLVERS, annotationDriven);
}
}
boolean writeFile = false;
if (addBean) {
if (argumentResolver == null) {
// Add missing argument-resolvers tag to annotation driven tag
argumentResolver = webMvcXml.createElement("mvc:"
+ ARGUMENT_RESOLVERS);
annotationDriven.appendChild(argumentResolver);
}
// add bean tag to argument-resolvers
bean = webMvcXml.createElement("bean");
bean.setAttribute("class", DATATABLES_CRITERIA_RESOLVER);
argumentResolver.appendChild(bean);
writeFile = true;
}
// Register EntityManagerProvider Service
Element entityManagerProvBeanElement = XmlUtils.findFirstElement(
"bean[@id='entityManagerProvider']", root);
if (entityManagerProvBeanElement == null) {
entityManagerProvBeanElement = webMvcXml.createElement("bean");
entityManagerProvBeanElement.setAttribute("id",
"entityManagerProvider");
entityManagerProvBeanElement
.setAttribute("class",
"org.gvnix.web.datatables.util.impl.EntityManagerProviderImpl");
root.appendChild(entityManagerProvBeanElement);
writeFile = true;
}
// Register DatatableUtilBean bean
Element DatatableUtilsBeanElement = XmlUtils.findFirstElement(
"bean[@id='datatableUtilsBean']", root);
if (DatatableUtilsBeanElement == null) {
DatatableUtilsBeanElement = webMvcXml.createElement("bean");
DatatableUtilsBeanElement.setAttribute("id", "datatableUtilsBean");
DatatableUtilsBeanElement
.setAttribute("class",
"org.gvnix.web.datatables.util.impl.DatatablesUtilsBeanImpl");
root.appendChild(DatatableUtilsBeanElement);
writeFile = true;
}
// Register QuerydslUtilBean bean
Element QuerydslUtilBeanElement = XmlUtils.findFirstElement(
"bean[@id='querydslUtilsBean']", root);
if (QuerydslUtilBeanElement == null) {
QuerydslUtilBeanElement = webMvcXml.createElement("bean");
QuerydslUtilBeanElement.setAttribute("id", "querydslUtilsBean");
QuerydslUtilBeanElement.setAttribute("class",
"org.gvnix.web.datatables.util.impl.QuerydslUtilsBeanImpl");
root.appendChild(QuerydslUtilBeanElement);
writeFile = true;
}
if (writeFile) {
XmlUtils.writeXml(webMvcXmlMutableFile.getOutputStream(), webMvcXml);
}
}
@Override
public void updateWebXmlFile() {
LogicalPath webappPath = getWebProjectUtils().getWebappPath(
getProjectOperations());
String webXmlPath = getProjectOperations().getPathResolver()
.getIdentifier(webappPath, "WEB-INF/web.xml");
Validate.isTrue(fileManager.exists(webXmlPath), "web.xml not found");
MutableFile webXmlMutableFile = null;
Document webXml;
try {
webXmlMutableFile = fileManager.updateFile(webXmlPath);
webXml = XmlUtils.getDocumentBuilder().parse(
webXmlMutableFile.getInputStream());
}
catch (Exception e) {
throw new IllegalStateException(e);
}
Element root = webXml.getDocumentElement();
boolean modified = false;
// look for filter
Element filter = XmlUtils.findFirstElement(
"/filter[filter-name='datatablesFilter']", root);
if (filter == null) {
// Create tag
insertXmlElement(webXml, root, "filter", "filter-name",
"datatablesFilter", "filter-class",
"com.github.dandelion.datatables.core.web.filter.DatatablesFilter");
modified = true;
}
// look for filter-mapping
Element filterMapping = XmlUtils.findFirstElement(
"/filter-mapping[filter-name='datatablesFilter']", root);
if (filterMapping == null) {
// Create tag
insertXmlElement(webXml, root, "filter-mapping", "filter-name",
"datatablesFilter", "url-pattern", "/*");
modified = true;
}
// look for servlet
Element servlet = XmlUtils.findFirstElement(
"/servlet[servlet-name='datatablesController']", root);
if (servlet == null) {
// Create tag
insertXmlElement(webXml, root, "servlet", "servlet-name",
"datatablesController", "servlet-class",
"com.github.dandelion.datatables.core.web.servlet.DatatablesServlet");
modified = true;
}
// look for servlet-mapping
Element servletMapping = XmlUtils.findFirstElement(
"/servlet-mapping[servlet-name='datatablesController']", root);
if (servletMapping == null) {
// Create tag
insertXmlElement(webXml, root, "servlet-mapping", "servlet-name",
"datatablesController", "url-pattern",
"/datatablesController/*");
modified = true;
}
if (modified) {
XmlUtils.writeXml(webXmlMutableFile.getOutputStream(), webXml);
}
}
/**
* Insert a new element of type {@code nodeName} into {@code parent} with
* definition declared in {@code subElementsAndValue}.
* {@code subElementsAndValue} is composed in pairs of <i>nodeName</i> and
* <i>textValue</i>.
*
* @param doc
* @param parent
* @param nodeName
* @param subElementsAndValue
*/
private void insertXmlElement(Document doc, Element parent,
String nodeName, String... subElementsAndValue) {
Validate.isTrue(subElementsAndValue.length % 2 == 0,
"subElementsAndValue must be even");
Element newElement = doc.createElement(nodeName);
Element subElement;
for (int i = 0; i < subElementsAndValue.length - 1; i = i + 2) {
subElement = doc.createElement(subElementsAndValue[i]);
subElement.setTextContent(subElementsAndValue[i + 1]);
newElement.appendChild(subElement);
}
// insert element as last element of the node type
Node inserPosition = null;
// Locate last node of this type
List<Element> elements = XmlUtils.findElements(nodeName, parent);
if (!elements.isEmpty()) {
inserPosition = elements.get(elements.size() - 1).getNextSibling();
}
// Add node
if (inserPosition == null) {
parent.appendChild(newElement);
}
else {
parent.insertBefore(newElement, inserPosition);
}
}
@Override
public void copyPropertiesFile() {
PathResolver pathResolver = getProjectOperations().getPathResolver();
LogicalPath resouresPath = LogicalPath.getInstance(
Path.SRC_MAIN_RESOURCES, getProjectOperations()
.getFocusedModuleName());
copyDirectoryContents("resources/*.properties",
pathResolver.getIdentifier(resouresPath, "/"), true);
}
@Override
public void addI18nKeys() {
// Check if Valencian_Catalan language is supported and add properties
// if so
Set<I18n> supportedLanguages = getI18nSupport().getSupportedLanguages();
for (I18n i18n : supportedLanguages) {
if (i18n.getLocale().equals(new Locale("ca"))) {
getMessageBundleUtils().installI18nMessages(
new ValencianCatalanLanguage(), getProjectOperations(),
fileManager);
getMessageBundleUtils().addPropertiesToMessageBundle("ca",
getClass(), getPropFileOperations(),
getProjectOperations(), fileManager);
break;
}
}
// Add properties to Spanish messageBundle
getMessageBundleUtils().installI18nMessages(new SpanishLanguage(),
getProjectOperations(), fileManager);
getMessageBundleUtils().addPropertiesToMessageBundle("es", getClass(),
getPropFileOperations(), getProjectOperations(), fileManager);
// Add properties to default messageBundle
getMessageBundleUtils().addPropertiesToMessageBundle("en", getClass(),
getPropFileOperations(), getProjectOperations(), fileManager);
}
/**
* Creates an instance with the {@code src/main/webapp} path in the current
* module
*
* @return
*/
public LogicalPath getWebappPath() {
return getWebProjectUtils().getWebappPath(getProjectOperations());
}
/**
* Gets the feature name managed by this operations class.
*
* @return feature name
*/
public String getName() {
return FEATURE_NAME_GVNIX_DATATABLES;
}
/**
* Returns true if the given feature is installed in current project.
*
* @param moduleName feature name to check in current project
* @return true if given feature name is installed, otherwise returns false
*/
public boolean isInstalledInModule(final String moduleName) {
final Pom pom = getProjectOperations().getPomFromModuleName(moduleName);
if (pom == null) {
return false;
}
// Look for datatables taglib dependency
for (final Dependency dependency : pom.getDependencies()) {
if ("org.gvnix.datatables.tags".equals(dependency.getArtifactId())) {
return true;
}
}
return false;
}
// BOOTSTRAP METHODS
/**
* This method modifies Datatables to uses Bootstrap appereance
*/
@Override
public void updateDatatablesAddonToBootstrap() {
PathResolver pathResolver = getProjectOperations().getPathResolver();
/**
* Installing script datatables files
*/
final String scriptFile = pathResolver.getFocusedIdentifier(
Path.SRC_MAIN_WEBAPP,
"scripts/bootstrap/dataTables.bootstrap.js");
createFilesInLocationIfNotExists(fileManager, getClass(), scriptFile,
"dataTables.bootstrap.js", "scripts/bootstrap/");
/**
* Installing css datatable styles
*/
final String styleFile = pathResolver.getFocusedIdentifier(
Path.SRC_MAIN_WEBAPP,
"styles/bootstrap/dataTables.bootstrap.css");
createFilesInLocationIfNotExists(fileManager, getClass(), styleFile,
"dataTables.bootstrap.css", "styles/bootstrap/");
/**
* Adding references to load-scripts-bootsrap.tagx
*/
final String loadScriptsFile = pathResolver.getFocusedIdentifier(
Path.SRC_MAIN_WEBAPP,
"WEB-INF/tags/bootstrap/util/load-scripts-bootstrap.tagx");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = FileUtils.getInputStream(getClass(),
"tags/bootstrap/util/load-scripts-bootstrap.tagx");
if (!fileManager.exists(loadScriptsFile)) {
outputStream = fileManager.createFile(loadScriptsFile)
.getOutputStream();
}
else if (fileManager.exists(loadScriptsFile)
&& !isLoadScriptsModified()) {
outputStream = fileManager.updateFile(loadScriptsFile)
.getOutputStream();
}
if (outputStream != null) {
IOUtils.copy(inputStream, outputStream);
}
}
catch (final IOException ioe) {
throw new IllegalStateException(ioe);
}
finally {
IOUtils.closeQuietly(inputStream);
if (outputStream != null) {
IOUtils.closeQuietly(outputStream);
}
}
}
/**
* This method copy a new file in a directory if the file not exists in the
* system
*
* @param fileManager
* @param loadingClass
* @param filePath
* @param fileName
* @param directory
*/
public static void createFilesInLocationIfNotExists(
FileManager fileManager, Class loadingClass, String filePath,
String fileName, String directory) {
if (!fileManager.exists(filePath)) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = FileUtils.getInputStream(loadingClass,
directory.concat(fileName));
outputStream = fileManager.createFile(filePath)
.getOutputStream();
IOUtils.copy(inputStream, outputStream);
}
catch (final IOException ioe) {
throw new IllegalStateException(ioe);
}
finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
}
/**
* Check if load-scripts-bootstrap.tagx was modified and include datatables
*
* @return
*/
public boolean isLoadScriptsModified() {
PathResolver pathResolver = getProjectOperations().getPathResolver();
String dirPath = pathResolver.getIdentifier(getWebappPath(),
"WEB-INF/tags/bootstrap/util/load-scripts-bootstrap.tagx");
final Document document = XmlUtils.readXml(fileManager
.getInputStream(dirPath));
final Element config = document.getDocumentElement();
final Element urlElement = DomUtils.findFirstElementByName(
"spring:url", config);
String value = urlElement.getAttribute("value");
return value.contains("dataTables.bootstrap.css");
}
public ProjectOperations getProjectOperations() {
if (projectOperations == null) {
// Get all Services implement WebMetadataService interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
ProjectOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (ProjectOperations) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load ProjectOperations on DatatablesOperationsImpl.");
return null;
}
}
else {
return projectOperations;
}
}
public TypeLocationService getTypeLocationService() {
if (typeLocationService == null) {
// Get all Services implement TypeLocationService interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
TypeLocationService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (TypeLocationService) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeLocationService on DatatablesOperationsImpl.");
return null;
}
}
else {
return typeLocationService;
}
}
public TypeManagementService getTypeManagementService() {
if (typeManagementService == null) {
// Get all Services implement TypeManagementService interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
TypeManagementService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (TypeManagementService) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeManagementService on DatatablesOperationsImpl.");
return null;
}
}
else {
return typeManagementService;
}
}
public MetadataService getMetadataService() {
if (metadataService == null) {
// Get all Services implement MetadataService interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
MetadataService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (MetadataService) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load MetadataService on DatatablesOperationsImpl.");
return null;
}
}
else {
return metadataService;
}
}
public MenuOperations getMenuOperations() {
if (menuOperations == null) {
// Get all Services implement MenuOperations interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
MenuOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (MenuOperations) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load MenuOperations on DatatablesOperationsImpl.");
return null;
}
}
else {
return menuOperations;
}
}
public I18nSupport getI18nSupport() {
if (i18nSupport == null) {
// Get all Services implement I18nSupport interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(I18nSupport.class.getName(),
null);
for (ServiceReference<?> ref : references) {
return (I18nSupport) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load I18nSupport on DatatablesOperationsImpl.");
return null;
}
}
else {
return i18nSupport;
}
}
public PropFileOperations getPropFileOperations() {
if (propFileOperations == null) {
// Get all Services implement PropFileOperations interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
PropFileOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (PropFileOperations) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load PropFileOperations on DatatablesOperationsImpl.");
return null;
}
}
else {
return propFileOperations;
}
}
public WebMvcOperations getWebMvcOperations() {
if (webMvcOperations == null) {
// Get all Services implement WebMvcOperations interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
WebMvcOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (WebMvcOperations) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load WebMvcOperations on DatatablesOperationsImpl.");
return null;
}
}
else {
return webMvcOperations;
}
}
public WebProjectUtils getWebProjectUtils() {
if (webProjectUtils == null) {
// Get all Services implement WebProjectUtils interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
WebProjectUtils.class.getName(), null);
for (ServiceReference<?> ref : references) {
webProjectUtils = (WebProjectUtils) this.context
.getService(ref);
return webProjectUtils;
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load WebProjectUtils on DatatablesOperationsImpl.");
return null;
}
}
else {
return webProjectUtils;
}
}
public MessageBundleUtils getMessageBundleUtils() {
if (messageBundleUtils == null) {
// Get all Services implement MessageBundleUtils interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
MessageBundleUtils.class.getName(), null);
for (ServiceReference<?> ref : references) {
messageBundleUtils = (MessageBundleUtils) this.context
.getService(ref);
return messageBundleUtils;
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load MessageBundleUtils on DatatablesOperationsImpl.");
return null;
}
}
else {
return messageBundleUtils;
}
}
public OperationUtils getOperationUtils() {
if (operationUtils == null) {
// Get all Services implement OperationUtils interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
OperationUtils.class.getName(), null);
for (ServiceReference<?> ref : references) {
operationUtils = (OperationUtils) this.context
.getService(ref);
return operationUtils;
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load OperationUtils on DatatablesOperationsImpl.");
return null;
}
}
else {
return operationUtils;
}
}
}