Enterprise JavaBeans is a powerful technique to encapsulate and reuse business logic in the framework of remote method invocation. EJB sets up Java classes used to implement server APIs that are ultimately used by remote clients . These objects and services are configured using XML-based control files. Once the Java classes and XML control files are written, they must be bundled together in a jar. Then a special EJB compiler builds stubs and ties to implement the RPC support code.
The following code can be plugged into Example 9-1 to provide generic EJB support:
EJB_TMP_JAR = $(EJB_TMP_DIR)/temp.jar META_INF = $(EJB_TMP_DIR)/META-INF # $(call compile-bean, jar-name, # bean-files-wildcard, manifest-name-opt) define compile-bean $(eval EJB_TMP_DIR := $(shell mktemp -d $(TMPDIR)/compile-bean.XXXXXXXX)) $(MKDIR) $(META_INF) $(if $(filter %.xml, ),cp $(filter %.xml, ) $(META_INF)) cd $(OUTPUT_DIR) && \ $(JAR) -cf0 $(EJB_TMP_JAR) \ $(call jar-file-arg,$(META_INF)) \ $(filter-out %.xml, ) $(JVM) weblogic.ejbc $(EJB_TMP_JAR) $(call add-manifest,$(if ,,),,) $(RM) $(EJB_TMP_DIR) endef # $(call jar-file-arg, jar-file) jar-file-arg = -C "$(patsubst %/,%,$(dir ))" $(notdir )
The compile-bean function comaccepts three parameters: the name of the jar to create, the list of files in the jar, and an optional manifest file. The function first creates a clean temporary directory using the mktemp program and saves the directory name in the variable EJB_TMP_DIR . By embedding the assignment in an eval , we ensure that EJB_TMP_DIR is reset to a new temporary directory once for each expansion of compile-bean . Since compile-bean is used in the command script part of a rule, the function is expanded only when the command script is executed. Next, it copies any XML files in the bean file list into the META-INF directory. This is where EJB configuration files live. Then, the function builds a temporary jar that is used as input to the EJB compiler. The jar-file-arg function converts filenames of the form dir1/dir2/dir3 into -C dir1/dir2 dir3 so the relative path to the file in the jar is correct. This is the appropriate format for indicating the META-INF directory to the jar command. The bean file list contains .xml files that have already been placed in the META-INF directory, so we filter these files out. After building the temporary jar, the WebLogic EJB compiler is invoked, generating the output jar. A manifest is then added to the compiled jar. Finally, our temporary directory is removed.
Using the new function is straightforward:
bean_files = com/company/bean/FooInterface.class \ com/company/bean/FooHome.class \ src/com/company/bean/ejb-jar.xml \ src/com/company/bean/weblogic-ejb-jar.xml .PHONY: ejb_jar $(EJB_JAR) ejb_jar: $(EJB_JAR) $(EJB_JAR): $(call compile-bean, $@, $(bean_files), weblogic.mf)
The bean_files list is a little confusing. The .class files it references will be accessed relative to the classes directory, while the .xml files will be accessed relative to the directory of the makefile .
This is fine, but what if you have lots of bean files in your bean jar. Can we build the file list automatically? Certainly:
src_dirs := $(SOURCE_DIR)/com/company/... bean_files = \ $(patsubst $(SOURCE_DIR)/%,%, \ $(addsuffix /*.class, \ $(sort \ $(dir \ $(wildcard \ $(addsuffix /*Home.java,$(src_dirs))))))) .PHONY: ejb_jar $(EJB_JAR) ejb_jar: $(EJB_JAR) $(EJB_JAR): $(call compile-bean, $@, $(bean_files), weblogic.mf)
This assumes that all the directories with EJB source are contained in the src_dirs variable (there can also be directories that do not contain EJB source) and that any file ending in Home.java identifies a package containing EJB code. The expression for setting the bean_files variable first adds the wildcard suffix to the directories, then invokes wildcard to gather the list of Home.java files. The filenames are discarded to leave the directories, which are sorted to remove duplicates. The wildcard /*.class suffix is added so that the shell will expand the list to the actual class files. Finally, the source directory prefix (which is not valid in the classes tree) is removed. Shell wildcard expansion is used instead of make 's wildcard because we can't rely on make to perform its expansion after the class files have been compiled. If make evaluated the wildcard function too early it would find no files and directory caching would prevent it from ever looking again. The wildcard in the source tree is perfectly safe because (we assume) no source files will be added while make is running.
The above code works when we have a small number of bean jars. Another style of development places each EJB in its own jar. Large projects may have dozens of jars. To handle this case automatically, we need to generate an explicit rule for each EJB jar. In this example, EJB source code is self-contained: each EJB is located in a single directory with its associated XML files. EJB directories can be identified by files that end with Session.java .
The basic approach is to search the source tree for EJBs, then build an explicit rule to create each EJB and write these rules into a file. The EJB rules file is then included in our makefile . The creation of the EJB rules file is triggered by make 's own dependency handling of include files.
# session_jars - The EJB jars with their relative source path. session_jars = $(subst .java,.jar, \ $(wildcard \ $(addsuffix /*Session.java, $(COMPILATION_DIRS)))) # EJBS - A list of all EJB jars we need to build. EJBS = $(addprefix $(TMP_DIR)/,$(notdir $(session_jars))) # ejbs - Create all EJB jar files. .PHONY: ejbs ejbs: $(EJBS) $(EJBS): $(call compile-bean,$@,$^,)
We find the Session.java files by calling a wildcard on all the compilation directories. In this example, the jar file is the name of the Session file with the .jar suffix. The jars themselves will be placed in a temporary binary directory. The EJBS variable contains the list of jars with their binary directory path. These EJB jars are the targets we want to update. The actual command script is our compile-bean function. The tricky part is that the file list is recorded in the prerequisites for each jar file. Let's see how they are created.
-include $(OUTPUT_DIR)/ejb.d # $(call ejb-rule, ejb-name) ejb-rule = $(TMP_DIR)/$(notdir ): \ $(addprefix $(OUTPUT_DIR)/, \ $(subst .java,.class, \ $(wildcard $(dir )*.java))) \ $(wildcard $(dir )*.xml) # ejb.d - EJB dependencies file. $(OUTPUT_DIR)/ejb.d: Makefile @echo Computing ejb dependencies... @for f in $(session_jars); \ do \ echo "$$(call ejb-rule,$$f)"; \ done > $@
The dependencies for each EJB jar are recorded in a separate file, ejb.d , that is included by the makefile . The first time make looks for this include file it does not exist. So make invokes the rule for updating the include file. This rule writes one line for each EJB, something like:
The function ejb-rule will expand to the target jar and its list of prerequisites, something like:
classes/lib/FooSession.jar: classes/com/company/foo/FooHome.jar \ classes/com/company/foo/FooInterface.jar \ classes/com/company/foo/FooSession.jar \ src/com/company/foo/ejb-jar.xml \ src/com/company/foo/ejb-weblogic-jar.xml
In this way, a large number of jars can be managed in make without incurring the overhead of maintaining a set of explicit rules by hand.