blob: 878bd1b29832d7f9af870d24f107652f7468cc4e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.websvc.rest.codegen;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.project.Project;
import org.netbeans.modules.j2ee.core.api.support.java.GenerationUtils;
import org.netbeans.modules.j2ee.core.api.support.java.JavaIdentifiers;
import org.netbeans.modules.j2ee.persistence.wizard.fromdb.FacadeGenerator;
import org.netbeans.modules.websvc.rest.RestUtils;
import org.netbeans.modules.websvc.rest.codegen.model.EntityClassInfo;
import org.netbeans.modules.websvc.rest.codegen.model.EntityClassInfo.FieldInfo;
import org.netbeans.modules.websvc.rest.codegen.model.EntityResourceBeanModel;
import org.netbeans.modules.websvc.rest.codegen.model.GenericResourceBean;
import org.netbeans.modules.websvc.rest.model.api.RestConstants;
import org.netbeans.modules.websvc.rest.support.JavaSourceHelper;
import org.netbeans.modules.websvc.rest.support.PersistenceHelper;
import org.netbeans.modules.websvc.rest.support.PersistenceHelper.PersistenceUnit;
import org.netbeans.modules.websvc.rest.wizard.Util;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
/**
*
* @author ads
*/
public abstract class EntityResourcesGenerator extends AbstractGenerator
implements FacadeGenerator
{
public static final String RESOURCE_FOLDER = "service"; //NOI18N
public static final String CONTROLLER_FOLDER = "controller"; //NOI18N
public static final String RESOURCE_SUFFIX = GenericResourceBean.RESOURCE_SUFFIX;
protected static final String REST_FACADE_SUFFIX = "RESTFacade"; //NOI18N
private PersistenceUnit persistenceUnit;
private String targetPackageName;
private FileObject targetFolder;
private FileObject resourceFolder;
private String resourcePackageName;
private FileObject controllerFolder;
private String controllerPackageName;
private EntityResourceBeanModel model;
private Project project;
/** Creates a new instance of EntityRESTServicesCodeGenerator */
public void initialize(EntityResourceBeanModel model, Project project,
FileObject targetFolder, String targetPackageName,
String resourcePackage, String controllerPackage,
PersistenceUnit persistenceUnit) {
this.model = model;
this.project = project;
this.persistenceUnit = persistenceUnit;
this.targetFolder = targetFolder;
this.targetPackageName = targetPackageName;
if (resourcePackage == null) {
this.resourcePackageName = targetPackageName + "." + RESOURCE_FOLDER;
} else {
this.resourcePackageName = resourcePackage;
}
if (controllerPackage == null) {
this.controllerPackageName = targetPackageName + "." + CONTROLLER_FOLDER;
} else {
this.controllerPackageName = controllerPackage;
}
}
/**
* Generates RESTful resources
* @param pHandle ProgressHandle. May be null, e.g., if method is called in conjunction with other generators that have ProgressHandles
* that are already running.
* @return
* @throws java.io.IOException
*/
@Override
public Set<FileObject> generate(ProgressHandle pHandle) throws IOException {
if (pHandle != null) {
initProgressReporting(pHandle);
}
createFolders();
//Make necessary changes to the persistence.xml
new PersistenceHelper(project).configure(model.getBuilder().getAllEntityNames(),
!RestUtils.hasJTASupport(project));
//
//Delegate to J2eeEntityResourcesGenerator or SpringEntityResourceGenerator to
// perform the rest of the persistence configuration.
//
configurePersistence();
Set<String> fqnEntities = new HashSet<String>();
for (EntityClassInfo info : model.getEntityInfos()) {
if ( !fqnEntities.contains( info.getEntityFqn() )){
fqnEntities.add( info.getEntityFqn() );
Util.modifyEntity( info.getEntityFqn() , project);
}
}
preGenerate(new ArrayList<String>( fqnEntities ));
Util.generateRESTFacades(project, fqnEntities, model, resourceFolder,
getResourcePackageName(), this);
finishProgressReporting();
return new HashSet<FileObject>();
}
/* (non-Javadoc)
* @see org.netbeans.modules.j2ee.persistence.wizard.fromdb.FacadeGenerator#generate(org.netbeans.api.project.Project, java.util.Map, org.openide.filesystems.FileObject, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, boolean)
*/
@Override
public Set<FileObject> generate( Project project,
Map<String, String> entityNames, FileObject targetFolder,
String entityFQN, String idClass, String pkg, boolean hasRemote,
boolean hasLocal, boolean overrideExisting ) throws IOException
{
final Set<FileObject> createdFiles = new HashSet<FileObject>();
final String entitySimpleName = JavaIdentifiers.unqualify(entityFQN);
// create the facade
String resourceName = entitySimpleName + REST_FACADE_SUFFIX;
reportProgress( resourceName );
FileObject existingFO = targetFolder.getFileObject(resourceName, "java");
if (existingFO != null) {
if (overrideExisting) {
existingFO.delete();
} else {
throw new IOException("file alerady exists exception: "+existingFO);
}
}
final FileObject facade = GenerationUtils.createClass(targetFolder,
entitySimpleName + REST_FACADE_SUFFIX, null);
createdFiles.add(facade);
if ( !generateInfrastracture(createdFiles, entityFQN, facade) ){
return createdFiles;
}
generateResourceMethods( facade , entityFQN , idClass );
return createdFiles;
}
protected List<String> getResourceImports( String entityFqn ) {
List<String> result = new LinkedList<String>();
result.add( entityFqn );
result.add( URI.class.getCanonicalName() );
return result;
}
protected boolean generateInfrastracture( final Set<FileObject> createdFiles,
final String entityFqn, final FileObject facade )
throws IOException
{
Util.generatePrimaryKeyMethod(facade, entityFqn, model);
return true;
}
protected void preGenerate( List<String> fqnEntities ) throws IOException {
}
protected ModifiersTree addRestMethodAnnotations( GenerationUtils genUtils,
TreeMaker maker, RestGenerationOptions option,
ModifiersTree modifiers )
{
String method = option.getRestMethod().getMethod();
ModifiersTree modifiersTree = modifiers;
if ( method != null ){
modifiersTree = maker.addModifiersAnnotation(modifiers,
genUtils.createAnnotation(method));
}
// add @Path annotation
String uriPath = option.getRestMethod().getUriPath();
if (uriPath != null) {
ExpressionTree uriValue = maker.Literal(uriPath);
modifiersTree =
maker.addModifiersAnnotation(modifiersTree,
genUtils.createAnnotation(RestConstants.PATH,
Collections.<ExpressionTree>singletonList(
uriValue)));
}
// add @Override
if ( option.getRestMethod().overrides() ){
modifiersTree =
maker.addModifiersAnnotation(modifiersTree,
genUtils.createAnnotation(
Override.class.getCanonicalName()));
}
// add @Produces annotation
modifiersTree = addMimeHandlerAnnotation(genUtils, maker,
modifiersTree, RestConstants.PRODUCE_MIME, option.getProduces());
// add @Consumes annotation
modifiersTree = addMimeHandlerAnnotation(genUtils, maker,
modifiersTree, RestConstants.CONSUME_MIME, option.getConsumes());
return modifiersTree;
}
protected ModifiersTree addResourceAnnotation(
final String entityFQN, ClassTree classTree,
GenerationUtils genUtils, TreeMaker maker )
{
// Add @Path annotation to REST resource class
ExpressionTree resourcePath = maker.Literal(entityFQN.toLowerCase());
ModifiersTree modifiersTree = classTree.getModifiers();
modifiersTree =
maker.addModifiersAnnotation(modifiersTree,
genUtils.createAnnotation(RestConstants.PATH,
Collections.<ExpressionTree>singletonList(
resourcePath)));
return modifiersTree;
}
protected List<RestGenerationOptions> getRestFacadeMethodOptions(
String entityFQN, String idClass )
{
String paramArg;
String idType = idClass;
if ( Character.class.getCanonicalName().equals(idClass) ){
paramArg = "id.charAt(0)"; // NOI18N
idType = String.class.getCanonicalName();
}
else {
paramArg = "id"; // NOI18N
}
RestFacadeMethod[] methods = RestFacadeMethod.values();
List<RestGenerationOptions> result = new ArrayList<RestGenerationOptions>(
methods.length);
for (RestFacadeMethod method : methods) {
RestGenerationOptions options = getGenerationOptions( method ,
entityFQN, paramArg , idType );
result.add( options );
}
return result;
}
protected void generateRestMethod( TypeElement classElement,
GenerationUtils genUtils, TreeMaker maker,
List<Tree> members, RestGenerationOptions option )
{
ModifiersTree paramModifier = maker.Modifiers(
Collections.<Modifier>emptySet());
ModifiersTree modifiers ;
if ( option.getRestMethod().getMethod() == null ){
modifiers = genUtils.createModifiers(
Modifier.PRIVATE);
}
else {
modifiers = genUtils.createModifiers(
Modifier.PUBLIC);
}
ModifiersTree modifiersTree = addRestMethodAnnotations(genUtils, maker,
option, modifiers);
// create arguments list
List<VariableTree> vars = addRestArguments(classElement, genUtils,
maker, option, paramModifier);
Tree returnType = (option.getReturnType() == null ||
option.getReturnType().equals("void"))? //NOI18N
maker.PrimitiveType(TypeKind.VOID):
genUtils.createType(
option.getReturnType(),
classElement);
members.add(
maker.Method(
modifiersTree,
option.getRestMethod().getMethodName(),
returnType,
Collections.<TypeParameterTree>emptyList(),
vars,
Collections.<ExpressionTree>emptyList(),
"{"+option.getBody()+"}", //NOI18N
null)
);
}
protected List<VariableTree> addRestArguments( TypeElement classElement,
GenerationUtils genUtils, TreeMaker maker,
RestGenerationOptions option, ModifiersTree paramModifier )
{
List<VariableTree> vars = new ArrayList<VariableTree>();
String[] paramNames = option.getParameterNames();
int paramLength = paramNames == null ? 0 : option.getParameterNames().length ;
if (paramLength > 0) {
String[] paramTypes = option.getParameterTypes();
String[] pathParams = option.getPathParams();
for (int i = 0; i<paramLength; i++) {
ModifiersTree pathParamTree = paramModifier;
if (pathParams != null && pathParams[i] != null) {
List<ExpressionTree> annArguments =
Collections.<ExpressionTree>singletonList(
maker.Literal(pathParams[i]));
pathParamTree =
maker.addModifiersAnnotation(paramModifier,
genUtils.createAnnotation(
RestConstants.PATH_PARAM,
annArguments));
}
Tree paramTree = genUtils.createType(paramTypes[i],
classElement);
VariableTree var = maker.Variable(pathParamTree,
paramNames[i], paramTree, null);
vars.add(var);
}
}
return vars;
}
protected void createFolders(boolean createController) {
FileObject sourceRootFolder = getSourceRootFolder(targetFolder,
targetPackageName);
File sourceRootDir = FileUtil.toFile(sourceRootFolder);
try {
String resourceFolderPath = toFilePath(getResourcePackageName());
resourceFolder = sourceRootFolder.getFileObject(resourceFolderPath);
if (resourceFolder == null) {
resourceFolder = FileUtil.createFolder(new File(sourceRootDir,
resourceFolderPath));
}
if (createController) {
String controllerFolderPath = toFilePath(controllerPackageName);
controllerFolder = sourceRootFolder
.getFileObject(controllerFolderPath);
if (controllerFolder == null) {
controllerFolder = FileUtil.createFolder(new File(
sourceRootDir, controllerFolderPath));
}
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
protected void configurePersistence() {
}
protected RestGenerationOptions getGenerationOptions(
RestFacadeMethod method, String entityFQN, String paramArg,
String idType )
{
boolean needPathSegment = false;
EntityClassInfo entityInfo = model.getEntityInfo(entityFQN);
if ( entityInfo!= null ){
FieldInfo idFieldInfo = entityInfo.getIdFieldInfo();
needPathSegment = idFieldInfo!= null && idFieldInfo.isEmbeddedId()&&
idFieldInfo.getType()!= null;
}
RestGenerationOptions options = new RestGenerationOptions();
switch ( method ){
case CREATE:
options.setRestMethod(RestFacadeMethod.CREATE);
options.setReturnType(RestConstants.HTTP_RESPONSE);
options.setParameterNames(new String[]{"entity"}); //NOI18N
options.setParameterTypes(new String[]{entityFQN});
options.setConsumes(new String[]{Constants.MimeType.XML.value(),
Constants.MimeType.JSON.value()});
return options;
case EDIT:
options.setRestMethod(RestFacadeMethod.EDIT);
options.setReturnType(RestConstants.HTTP_RESPONSE);
options.setParameterNames(new String[]{"entity"}); //NOI18N
options.setParameterTypes(new String[]{entityFQN}); //NOI18N
options.setConsumes(new String[]{Constants.MimeType.XML.value(),
Constants.MimeType.JSON.value()});
return options;
case REMOVE:
options.setRestMethod(RestFacadeMethod.REMOVE);
options.setReturnType(RestConstants.HTTP_RESPONSE);
options.setParameterNames(new String[]{"id"}); //NOI18N
if ( needPathSegment ){
options.setParameterTypes(new String[]{"javax.ws.rs.core.PathSegment"}); //NOI18N
}
else {
options.setParameterTypes(new String[]{idType}); //NOI18N
}
options.setPathParams(new String[]{"id"}); //NOI18N
return options;
case FIND:
options.setRestMethod(RestFacadeMethod.FIND);
options.setReturnType(entityFQN);
options.setProduces(new String[]{Constants.MimeType.XML.value(),
Constants.MimeType.JSON.value()});
options.setParameterNames(new String[]{"id"}); //NOI18N
if ( needPathSegment ){
options.setParameterTypes(new String[]{"javax.ws.rs.core.PathSegment"}); //NOI18N
}
else {
options.setParameterTypes(new String[]{idType}); //NOI18N
}
options.setPathParams(new String[]{"id"}); //NOI18N
return options;
case FIND_ALL:
options.setRestMethod(RestFacadeMethod.FIND_ALL);
options.setReturnType(List.class.getCanonicalName()
+"<" + entityFQN + ">");//NOI18N
options.setProduces(new String[]{Constants.MimeType.XML.value(),
Constants.MimeType.JSON.value()});
return options;
case FIND_RANGE:
options.setRestMethod(RestFacadeMethod.FIND_RANGE);
options.setReturnType(List.class.getCanonicalName()+
"<" + entityFQN + ">");//NOI18N
options.setProduces(new String[]{Constants.MimeType.XML.value(),
Constants.MimeType.JSON.value()});
options.setParameterNames(new String[]{"max", "first"}); //NOI18N
options.setParameterTypes(new String[]{Integer.class.getCanonicalName(),
Integer.class.getCanonicalName()});
options.setPathParams(new String[]{"max", "first"}); //NOI18N
return options;
case COUNT:
options.setRestMethod(RestFacadeMethod.COUNT);
options.setReturnType(String.class.getCanonicalName());
options.setProduces(new String[]{Constants.MimeType.TEXT.value()});
return options;
}
return null;
}
protected EntityClassInfo getEntityClassInfo(String className) {
return model.getBuilder().getEntityClassInfo(className);
}
protected Project getProject(){
return project;
}
protected EntityResourceBeanModel getModel(){
return model;
}
protected FileObject getTargetFolder(){
return targetFolder;
}
protected FileObject getResourceFolder(){
return resourceFolder;
}
protected PersistenceUnit getPersistenceUnit(){
return persistenceUnit;
}
protected String getResourcePackageName() {
return resourcePackageName;
}
protected String getControllerPackageName() {
return controllerPackageName;
}
protected FileObject getControllerFolder() {
return controllerFolder;
}
protected int getEntitiesCount(){
return getModel().getEntityInfos().size();
}
protected String getIdFieldToUriStmt(FieldInfo idField) {
String getterName = getGetterName(idField);
if (idField.isEmbeddedId()) {
Collection<FieldInfo> fields = idField.getFieldInfos();
StringBuilder stmt = new StringBuilder();
int index = 0;
for (FieldInfo f : fields) {
if (index++ > 0) {
stmt.append(" + \",\" + "); // NOI18N
}
stmt.append( "entity." ); // NOI18N
stmt.append( getterName );
stmt.append( "()." ); // NOI18N
stmt.append( getGetterName(f) );
stmt.append( "()" ); // NOI18N
}
return stmt.toString();
} else {
return "entity." + getterName + "()"; // NOI18N
}
}
/* (non-Javadoc)
* @see org.netbeans.modules.websvc.rest.codegen.AbstractGenerator#getTotalWorkUnits()
*/
@Override
protected int getTotalWorkUnits() {
return getEntitiesCount();
}
protected void createFolders() {
createFolders( true );
}
private void generateResourceMethods( FileObject fileObject ,
final String entityFQN, final String idClass) throws IOException
{
JavaSource javaSource = JavaSource.forFileObject( fileObject );
Task<WorkingCopy> task = new Task<WorkingCopy>() {
@Override
public void run(WorkingCopy workingCopy) throws Exception {
workingCopy.toPhase(Phase.RESOLVED);
CompilationUnitTree tree = workingCopy.getCompilationUnit();
ClassTree classTree = (ClassTree)tree.getTypeDecls().get(0);
TypeElement classElement = (TypeElement)workingCopy.getTrees().
getElement(TreePath.getPath( tree, classTree));
List<String> imports = getResourceImports( entityFQN );
JavaSourceHelper.addImports(workingCopy, imports.toArray(
new String[ imports.size()]));
GenerationUtils genUtils = GenerationUtils.newInstance(workingCopy);
TreeMaker maker = workingCopy.getTreeMaker();
List<Tree> members = new ArrayList<Tree>(classTree.getMembers());
// make empty CTOR
MethodTree constructor = maker.Constructor(
genUtils.createModifiers(Modifier.PUBLIC),
Collections.<TypeParameterTree>emptyList(),
Collections.<VariableTree>emptyList(),
Collections.<ExpressionTree>emptyList(),
"{}"); //NOI18N
members.add(constructor);
List<RestGenerationOptions> restGenerationOptions =
getRestFacadeMethodOptions(entityFQN, idClass);
for(RestGenerationOptions option: restGenerationOptions) {
generateRestMethod(classElement, genUtils, maker, members,
option);
}
ModifiersTree modifiersTree = addResourceAnnotation(entityFQN,
classTree, genUtils, maker);
// final step : generate new class tree
List<Tree> implementsClause = new ArrayList<Tree>(
classTree.getImplementsClause());
ClassTree newClassTree = maker.Class(
modifiersTree,
classTree.getSimpleName(),
classTree.getTypeParameters(),
classTree.getExtendsClause(),
implementsClause,
members);
workingCopy.rewrite(classTree, newClassTree);
}
};
javaSource.runModificationTask(task).commit();
}
private ModifiersTree addMimeHandlerAnnotation(GenerationUtils genUtils,
TreeMaker maker, ModifiersTree modifiersTree, String handlerAnnotation, String[] mimes) {
if (mimes == null) {
return modifiersTree;
}
ExpressionTree annArguments;
if (mimes.length == 1) {
annArguments = mimeTypeTree(maker, mimes[0]);
} else {
List<ExpressionTree> mimeTypes = new ArrayList<ExpressionTree>();
for (int i=0; i< mimes.length; i++) {
mimeTypes.add(mimeTypeTree(maker, mimes[i]));
}
annArguments = maker.NewArray(null,
Collections.<ExpressionTree>emptyList(),
mimeTypes);
}
return maker.addModifiersAnnotation(modifiersTree,
genUtils.createAnnotation(
handlerAnnotation,
Collections.<ExpressionTree>singletonList(
annArguments)));
}
private ExpressionTree mimeTypeTree(TreeMaker maker, String mimeType) {
Constants.MimeType type = Constants.MimeType.find(mimeType);
ExpressionTree result;
if (type == null) {
result = maker.Literal(mimeType);
} else {
result = type.expressionTree(maker);
}
return result;
}
private String getGetterName(FieldInfo fieldInfo) {
return Util.getGetterName(fieldInfo);
}
private String toFilePath(String packageName) {
return packageName.replace('.', '/');
}
private FileObject getSourceRootFolder(FileObject packageFolder,
String packageName)
{
String[] segments = packageName.split("\\."); // NOI18N
FileObject ret = packageFolder;
for (int i = segments.length - 1; i >= 0; i--) {
String segment = segments[i];
if (segment.length() == 0) {
return ret;
}
if (ret == null || !segments[i].equals(ret.getNameExt())) {
throw new IllegalArgumentException("Unmatched folder: " +
packageFolder.getPath() + " and package name: " +
packageName); // NOI18N
}
ret = ret.getParent();
}
return ret;
}
}